diff --git a/html/de/CONTRIBUTING.html b/html/de/CONTRIBUTING.html deleted file mode 100644 index cd3a88f2..00000000 --- a/html/de/CONTRIBUTING.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - - Tips for developers — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Tips for developers

-

It is recommended to run the project with python 3.11.

-
-

Start service

-

The different notebooks in ./notebooks provide more complete documentation regarding the set up and administration of the server.

-
-

On local machine

-

To start the container on a local machine, first create a mondodb volume -with docker volume create mongodata, then go to lomas/server/ and run docker compose up. -If you encounter any issue, you might want to run docker compose down first.

-
-

Additional services

-

Running docker compose up will also start two additional services automatically:

-
    -
  • a jupyter notebook environment that will be available at the address http://127.0.0.1:8888/ to interact as a user with the server

  • -
  • a streamlit application that will be available at the address http://localhost:8501/ to interact with the server and the administration database as an administrator.

  • -
-
-
-
-

On a kubernetes cluster

-

To start the server on a kubernetes cluster, first add the repo with: -helm repo add lomas https://dscc-admin-ch.github.io/helm-charts -and then install the chart with: -helm install lomas-sever lomas/lomas-server

-
-
-

On Onyxia

-

To start the server on Onyxia, select the lomas service, (optionnally adapt the administration and runtime parameters) and click on ‚Lancer‘.

-
-
-
-

Tests

-

It is possible to test the server with standard tests or integration tests (for the mongodb). -The run_integration_tests.sh script runs the integration tests, make sure to have an activated python venv in a linux environment with the server requirements installed for it to work.

-

Local tests (except for those using the mongodb_admin) can be run with a simple python -m unittest discover -s . from the lomas_server directory. The tests will be based on the config in lomas/server/lomas_server/tests/test_configs/test_config.yaml and be executed with the AdminYamlDatabase. -For local tests on the mongodb_admin, first get in the container with docker exec -it lomas_server_dev bash and then run the tests.

-

A github workflow is configured to run the integration tests script for pull requests on the develop and master branches.

-
-
-

Linting and other checks

-

Here is a list of the checks performed:

-
- Use black to automatically format the code: `black .`
-- Use flake to verify formating and performing a static code analysis: `flake8 .`
-- Use mypy for static type checking: `mypy .`
-- Use pylint for further static analysis: `pylint --disable=E0401 --disable=C0114 --disable=R0903 --disable=E0611 --disable=W0511 --disable=duplicate-code tests/ .`
-
-    - `--disable=E0401` to ignore import-error
-    - `--disable=C0114` to ignore missing-module-docstring
-    - `--disable=R0903` to ignore too-few-public-methods
-    - `--disable=E0611` to ignore no-name-in-module
-    - `--disable=W0511` to ignore fixme (TODOs)
-    - `--disable=duplicate-code tests/` to ignore duplicate-code statements related to the tests
-
-
-

We rely on a github workflow to automatically run the checks on pull requests.

-
-
-

Documentation

-

Documentation is automatically generated from the code docstrings with sphinx, as well as the resulting html pages. -The process goes in two steps -- sphinx-apidoc -o ./docs/source ./lomas_server for generating the .rst files. -- make html from the ./docs folder to generate the webpages.

-

We do not check the documentation files into the repo, but rather rely on a github workflow to generate and publish it -on a dedicated repo’s github pages for easy access from the web.

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_images/notebooks_Demo_Client_Notebook_2_0.png b/html/de/_images/notebooks_Demo_Client_Notebook_2_0.png deleted file mode 100644 index c96f3b92..00000000 Binary files a/html/de/_images/notebooks_Demo_Client_Notebook_2_0.png and /dev/null differ diff --git a/html/de/_images/notebooks_Demo_Client_Notebook_polars_2_0.png b/html/de/_images/notebooks_Demo_Client_Notebook_polars_2_0.png deleted file mode 100644 index c96f3b92..00000000 Binary files a/html/de/_images/notebooks_Demo_Client_Notebook_polars_2_0.png and /dev/null differ diff --git a/html/de/_images/notebooks_demo_kubernetes_admin_notebook_2_0.png b/html/de/_images/notebooks_demo_kubernetes_admin_notebook_2_0.png deleted file mode 100644 index bd6b29db..00000000 Binary files a/html/de/_images/notebooks_demo_kubernetes_admin_notebook_2_0.png and /dev/null differ diff --git a/html/de/_images/notebooks_demo_kubernetes_deployment_notebook_16_0.png b/html/de/_images/notebooks_demo_kubernetes_deployment_notebook_16_0.png deleted file mode 100644 index a1a8e1bb..00000000 Binary files a/html/de/_images/notebooks_demo_kubernetes_deployment_notebook_16_0.png and /dev/null differ diff --git a/html/de/_images/notebooks_demo_kubernetes_deployment_notebook_2_0.png b/html/de/_images/notebooks_demo_kubernetes_deployment_notebook_2_0.png deleted file mode 100644 index 061d54d9..00000000 Binary files a/html/de/_images/notebooks_demo_kubernetes_deployment_notebook_2_0.png and /dev/null differ diff --git a/html/de/_images/notebooks_demo_kubernetes_deployment_notebook_8_0.png b/html/de/_images/notebooks_demo_kubernetes_deployment_notebook_8_0.png deleted file mode 100644 index f20ceac2..00000000 Binary files a/html/de/_images/notebooks_demo_kubernetes_deployment_notebook_8_0.png and /dev/null differ diff --git a/html/de/_modules/index.html b/html/de/_modules/index.html deleted file mode 100644 index 62db2795..00000000 --- a/html/de/_modules/index.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - Überblick: Modul-Quellcode — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- - -
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_client/client.html b/html/de/_modules/lomas_client/client.html deleted file mode 100644 index 9a33e11f..00000000 --- a/html/de/_modules/lomas_client/client.html +++ /dev/null @@ -1,626 +0,0 @@ - - - - - - lomas_client.client — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_client.client

-import json
-from enum import StrEnum
-from io import StringIO
-from typing import Dict, List, Optional, Union
-
-import opendp as dp
-import pandas as pd
-import requests
-from opendp.mod import enable_features
-from opendp_logger import enable_logging, make_load_json
-
-
-# Opendp_logger
-enable_logging()
-enable_features("contrib")
-
-# Client constants: may be modified
-DUMMY_NB_ROWS = 100
-DUMMY_SEED = 42
-
-
-
-[Doku] -class DPLibraries(StrEnum): - """Enum of the DP librairies used in the server - WARNING: MUST match those of lomas_server - """ - - SMARTNOISE_SQL = "smartnoise_sql" - OPENDP = "opendp"
- - - -
-[Doku] -def error_message(res: requests.Response) -> str: - """Generates an error message based on the HTTP response. - - Args: - res (requests.Response): The response object from an HTTP request. - - Returns: - str: A formatted string describing the server error, - including the status code and response text. - """ - return f"Server error status {res.status_code}: {res.text}"
- - - -
-[Doku] -class Client: - """Client class to send requests to the server - Handle all serialisation and deserialisation steps - """ - - def __init__(self, url: str, user_name: str, dataset_name: str) -> None: - """Initializes the Client with the specified URL, user name, and dataset name. - - Args: - url (str): The base URL for the API server. - user_name (str): The name of the user allowed to perform queries. - dataset_name (str): The name of the dataset to be accessed or manipulated. - """ - self.url = url - self.headers = {"Content-type": "application/json", "Accept": "*/*"} - self.headers["user-name"] = user_name - self.dataset_name = dataset_name - -
-[Doku] - def get_dataset_metadata( - self, - ) -> Optional[Dict[str, Union[int, bool, Dict[str, Union[str, int]]]]]: - """This function retrieves metadata for the dataset. - - Returns: - Optional[Dict[str, Union[int, bool, Dict[str, Union[str, int]]]]]: - A dictionary containing dataset metadata. - """ - res = self._exec( - "get_dataset_metadata", {"dataset_name": self.dataset_name} - ) - if res.status_code == 200: - data = res.content.decode("utf8") - metadata = json.loads(data) - return metadata - - print(error_message(res)) - return None
- - -
-[Doku] - def get_dummy_dataset( - self, - nb_rows: int = DUMMY_NB_ROWS, - seed: int = DUMMY_SEED, - ) -> Optional[pd.DataFrame]: - """This function retrieves a dummy dataset with optional parameters. - - Args: - nb_rows (int, optional): The number of rows in the dummy dataset. - - Defaults to DUMMY_NB_ROWS. - - seed (int, optional): The random seed for generating the dummy dataset. - - Defaults to DUMMY_SEED. - - Returns: - Optional[pd.DataFrame]: A Pandas DataFrame representing the dummy dataset. - """ - res = self._exec( - "get_dummy_dataset", - { - "dataset_name": self.dataset_name, - "dummy_nb_rows": nb_rows, - "dummy_seed": seed, - }, - ) - - if res.status_code == 200: - data = res.content.decode("utf8") - df = pd.read_csv(StringIO(data)) - return df - print(error_message(res)) - return None
- - -
-[Doku] - def smartnoise_query( - self, - query: str, - epsilon: float, - delta: float, - mechanisms: dict[str, str] = {}, - postprocess: bool = True, - dummy: bool = False, - nb_rows: int = DUMMY_NB_ROWS, - seed: int = DUMMY_SEED, - ) -> Optional[dict]: - """This function executes a SmartNoise query. - - Args: - query (str): The SQL query to execute. - NOTE: the table name is df, the query must end with “FROM df”. - epsilon (float): Privacy parameter (e.g., 0.1). - delta (float): Privacy parameter (e.g., 1e-5). - mechanisms (dict[str, str], optional): Dictionary of mechanisms for the\ - query `See Smartnoise-SQL postprocessing documentation. - <https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms>`__ - - Defaults to {}. - postprocess (bool, optional): Whether to postprocess the query results.\ - `See Smartnoise-SQL postprocessing documentation. - <https://docs.smartnoise.org/sql/advanced.html#postprocess>`__ - - Defaults to True. - dummy (bool, optional): Whether to use a dummy dataset. - - Defaults to False. - nb_rows (int, optional): The number of rows in the dummy dataset. - - Defaults to DUMMY_NB_ROWS. - seed (int, optional): The random seed for generating the dummy dataset. - - Defaults to DUMMY_SEED. - - Returns: - Optional[dict]: A Pandas DataFrame containing the query results. - """ - body_json = { - "query_str": query, - "dataset_name": self.dataset_name, - "epsilon": epsilon, - "delta": delta, - "mechanisms": mechanisms, - "postprocess": postprocess, - } - if dummy: - endpoint = "dummy_smartnoise_query" - body_json["dummy_nb_rows"] = nb_rows - body_json["dummy_seed"] = seed - else: - endpoint = "smartnoise_query" - - res = self._exec(endpoint, body_json) - - if res.status_code == 200: - data = res.content.decode("utf8") - response_dict = json.loads(data) - response_dict["query_response"] = pd.DataFrame.from_dict( - response_dict["query_response"], orient="tight" - ) - return response_dict - - print(error_message(res)) - return None
- - -
-[Doku] - def estimate_smartnoise_cost( - self, - query: str, - epsilon: float, - delta: float, - mechanisms: dict[str, str] = {}, - ) -> Optional[dict[str, float]]: - """This function estimates the cost of executing a SmartNoise query. - - Args: - query (str): The SQL query to estimate the cost for. NOTE: the table name \ - is df, the query must end with “FROM df”. - epsilon (float): Privacy parameter (e.g., 0.1). - delta (float): Privacy parameter (e.g., 1e-5). - mechanisms (dict[str, str], optional): Dictionary of mechanisms for the\ - query `See Smartnoise-SQL postprocessing documentation. - <https://docs.smartnoise.org/sql/advanced.html#postprocess>`__ - Defaults to {}. - - Returns: - Optional[dict[str, float]]: A dictionary containing the estimated cost. - """ - body_json = { - "query_str": query, - "dataset_name": self.dataset_name, - "epsilon": epsilon, - "delta": delta, - "mechanisms": mechanisms, - } - res = self._exec("estimate_smartnoise_cost", body_json) - - if res.status_code == 200: - return json.loads(res.content.decode("utf8")) - print(error_message(res)) - return None
- - -
-[Doku] - def opendp_query( - self, - opendp_pipeline: dp.Measurement, - fixed_delta: Optional[float] = None, - dummy: bool = False, - nb_rows: int = DUMMY_NB_ROWS, - seed: int = DUMMY_SEED, - ) -> Optional[dict]: - """This function executes an OpenDP query. - - Args: - opendp_pipeline (dp.Measurement): The OpenDP pipeline for the query. - fixed_delta (Optional[float], optional): If the pipeline measurement is of\ - type “ZeroConcentratedDivergence” (e.g. with make_gaussian) then it is\ - converted to “SmoothedMaxDivergence” with make_zCDP_to_approxDP\ - (`See Smartnoise-SQL postprocessing documentation. - <https://docs.smartnoise.org/sql/advanced.html#postprocess>`__). - In that case a fixed_delta must be provided by the user. - Defaults to None. - dummy (bool, optional): Whether to use a dummy dataset. Defaults to False. - nb_rows (int, optional): The number of rows in the dummy dataset.\ - Defaults to DUMMY_NB_ROWS. - seed (int, optional): The random seed for generating the dummy dataset.\ - Defaults to DUMMY_SEED. - - Raises: - Exception: If the server returns dataframes - - Returns: - Optional[dict]: A Pandas DataFrame containing the query results. - """ - opendp_json = opendp_pipeline.to_json() - body_json = { - "dataset_name": self.dataset_name, - "opendp_json": opendp_json, - "fixed_delta": fixed_delta, - } - if dummy: - endpoint = "dummy_opendp_query" - body_json["dummy_nb_rows"] = nb_rows - body_json["dummy_seed"] = seed - else: - endpoint = "opendp_query" - - res = self._exec(endpoint, body_json) - if res.status_code == 200: - data = res.content.decode("utf8") - response_dict = json.loads(data) - - # Opendp outputs can be single numbers or dataframes, - # we handle the latter here. - # This is a hack for now, maybe use parquet to send results over. - # if isinstance(response_dict["query_response"], str): - # raise Exception("Not implemented: should not return dataframes") - # Note: leaving this here. Support for opendp_polars - # response_dict["query_response"] = polars.read_json( - # StringIO(response_dict["query_response"]) - # ) - - return response_dict - - print(error_message(res)) - return None
- - -
-[Doku] - def estimate_opendp_cost( - self, - opendp_pipeline: dp.Measurement, - fixed_delta: Optional[float] = None, - ) -> Optional[dict[str, float]]: - """This function estimates the cost of executing an OpenDP query. - - Args: - opendp_pipeline (dp.Measurement): The OpenDP pipeline for the query. - fixed_delta (Optional[float], optional): If the pipeline measurement is of\ - type “ZeroConcentratedDivergence” (e.g. with make_gaussian) then it is\ - converted to “SmoothedMaxDivergence” with make_zCDP_to_approxDP\ - (`See Smartnoise-SQL postprocessing documentation.\ - <https://docs.smartnoise.org/sql/advanced.html#postprocess>`__).\ - In that case a fixed_delta must be provided by the user.\ - Defaults to None. - - - Returns: - Optional[dict[str, float]]: A dictionary containing the estimated cost. - """ - opendp_json = opendp_pipeline.to_json() - body_json = { - "dataset_name": self.dataset_name, - "opendp_json": opendp_json, - "fixed_delta": fixed_delta, - } - res = self._exec("estimate_opendp_cost", body_json) - - if res.status_code == 200: - return json.loads(res.content.decode("utf8")) - - print(error_message(res)) - return None
- - -
-[Doku] - def get_initial_budget(self) -> Optional[dict[str, float]]: - """This function retrieves the initial budget. - - Returns: - Optional[dict[str, float]]: A dictionary containing the initial budget. - """ - body_json = { - "dataset_name": self.dataset_name, - } - res = self._exec("get_initial_budget", body_json) - - if res.status_code == 200: - return json.loads(res.content.decode("utf8")) - - print(error_message(res)) - return None
- - -
-[Doku] - def get_total_spent_budget(self) -> Optional[dict[str, float]]: - """This function retrieves the total spent budget. - - Returns: - Optional[dict[str, float]]: A dictionary containing the total spent budget. - """ - body_json = { - "dataset_name": self.dataset_name, - } - res = self._exec("get_total_spent_budget", body_json) - - if res.status_code == 200: - return json.loads(res.content.decode("utf8")) - - print(error_message(res)) - return None
- - -
-[Doku] - def get_remaining_budget(self) -> Optional[dict[str, float]]: - """This function retrieves the remaining budget. - - Returns: - Optional[dict[str, float]]: A dictionary containing the remaining budget. - """ - body_json = { - "dataset_name": self.dataset_name, - } - res = self._exec("get_remaining_budget", body_json) - - if res.status_code == 200: - return json.loads(res.content.decode("utf8")) - - print(error_message(res)) - return None
- - -
-[Doku] - def get_previous_queries(self) -> Optional[List[dict]]: - """This function retrieves the previous queries of the user. - - Raises: - ValueError: If an unknown query type is encountered during deserialization. - - Returns: - Optional[List[dict]]: A list of dictionary containing the different queries - on the private dataset. - """ - body_json = { - "dataset_name": self.dataset_name, - } - res = self._exec("get_previous_queries", body_json) - - if res.status_code == 200: - queries = json.loads(res.content.decode("utf8"))[ - "previous_queries" - ] - - if not queries: - return queries - - deserialised_queries = [] - for query in queries: - match query["dp_librairy"]: - case DPLibraries.SMARTNOISE_SQL: - pass - case DPLibraries.OPENDP: - opdp_query = make_load_json( - query["client_input"]["opendp_json"] - ) - query["client_input"]["opendp_json"] = opdp_query - case _: - raise ValueError( - "Cannot deserialise unknown query type:" - + f"{query['dp_librairy']}" - ) - - deserialised_queries.append(query) - - return deserialised_queries - - print(error_message(res)) - return None
- - - def _exec(self, endpoint: str, body_json: dict = {}) -> requests.Response: - """Executes a POST request to the specified endpoint with the provided - JSON body. - - Args: - endpoint (str): The API endpoint to which the request will be sent. - body_json (dict, optional): The JSON body to include in the POST request.\ - Defaults to {}. - - Returns: - requests.Response: The response object resulting from the POST request. - """ - r = requests.post( - self.url + "/" + endpoint, - json=body_json, - headers=self.headers, - timeout=50, - ) - return r
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/admin_database/admin_database.html b/html/de/_modules/lomas_server/admin_database/admin_database.html deleted file mode 100644 index b54b5481..00000000 --- a/html/de/_modules/lomas_server/admin_database/admin_database.html +++ /dev/null @@ -1,711 +0,0 @@ - - - - - - lomas_server.admin_database.admin_database — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.admin_database.admin_database

-import argparse
-import functools
-import time
-from abc import ABC, abstractmethod
-from typing import Callable, Dict, List
-
-from constants import DPLibraries
-from utils.error_handler import (
-    InternalServerException,
-    InvalidQueryException,
-    UnauthorizedAccessException,
-)
-
-
-
-[Doku] -def user_must_exist(func: Callable) -> Callable: # type: ignore - """ - Decorator function to verify that a user exists. - - Args: - func (Callable): Function to be decorated. - Wrapped function arguments must include: - - args[0] (str): username - - Raises: - UnauthorizedAccessException: If the user does not exist. - - Returns: - Callable: Wrapper function that verifies the user exists - before calling func. - """ - - @functools.wraps(func) - def wrapper_decorator( - self, *args: argparse.Namespace, **kwargs: Dict[str, str] - ) -> None: - user_name = args[0] - if not self.does_user_exist(user_name): - raise UnauthorizedAccessException( - f"User {user_name} does not exist. " - + "Please, verify the client object initialisation.", - ) - return func(self, *args, **kwargs) - - return wrapper_decorator
- - - -
-[Doku] -def dataset_must_exist(func: Callable) -> Callable: # type: ignore - """ - Decorator function to verify that a dataset exists. - - Args: - func (Callable): Function to be decorated. - Wrapped function arguments must include: - - args[0] (str): dataset name - - Raises: - InvalidQueryException: If the dataset does not exist. - - Returns: - Callable: Wrapper function that checks if the dataset exists - before calling the wrapped function. - """ - - @functools.wraps(func) - def wrapper_decorator( - self, *args: argparse.Namespace, **kwargs: Dict[str, str] - ) -> None: - dataset_name = args[0] - if not self.does_dataset_exist(dataset_name): - raise InvalidQueryException( - f"Dataset {dataset_name} does not exists. " - + "Please, verify the client object initialisation.", - ) - return func(self, *args, **kwargs) - - return wrapper_decorator
- - - -
-[Doku] -def user_must_have_access_to_dataset( - func: Callable, -) -> Callable: # type: ignore - """ - Decorator function to enforce a user has access to a dataset - - Args: - func (Callable): Function to be decorated. - Wrapped function arguments must include: - - args[0] (str): user name - - args[1] (str): dataset name - - Raises: - UnauthorizedAccessException: If the user does not have - access to the dataset. - - Returns: - Callable: Wrapper function that checks if the user has access - to the dataset before calling the wrapped function. - """ - - @functools.wraps(func) - def wrapper_decorator( - self, *args: argparse.Namespace, **kwargs: Dict[str, str] - ) -> None: - user_name = args[0] - dataset_name = args[1] - if not self.has_user_access_to_dataset(user_name, dataset_name): - raise UnauthorizedAccessException( - f"{user_name} does not have access to {dataset_name}.", - ) - return func(self, *args, **kwargs) - - return wrapper_decorator
- - - -
-[Doku] -class AdminDatabase(ABC): - """ - Overall database management for server state. - - This is an abstract class. - """ - - @abstractmethod - def __init__(self, **connection_parameters: Dict[str, str]) -> None: - """ - Connects to the DB - - Args: - **connection_parameters (Dict[str, str]): parameters required - to access the db - """ - -
-[Doku] - @abstractmethod - def does_user_exist(self, user_name: str) -> bool: - """ - Checks if user exist in the database - - Args: - user_name (str): name of the user to check - - Returns: - bool: True if the user exists, False otherwise. - """
- - -
-[Doku] - @abstractmethod - def does_dataset_exist(self, dataset_name: str) -> bool: - """ - Checks if dataset exist in the database - - Args: - dataset_name (str): name of the dataset to check - - Returns: - bool: True if the dataset exists, False otherwise. - """
- - -
-[Doku] - @abstractmethod - @dataset_must_exist - @user_must_have_access_to_dataset - def get_dataset_metadata(self, dataset_name: str) -> dict: - """ - Returns the metadata dictionnary of the dataset. - - Wrapped by :py:func:`dataset_must_exist`. - - Args: - dataset_name (str): name of the dataset to get the metadata - - Returns: - dict: The metadata dict. - """
- - -
-[Doku] - @abstractmethod - @user_must_exist - def may_user_query(self, user_name: str) -> bool: - """ - Checks if a user may query the server. - Cannot query if already querying. - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - - Returns: - bool: True if the user exists, False otherwise. - """
- - -
-[Doku] - @abstractmethod - @user_must_exist - def set_may_user_query(self, user_name: str, may_query: bool) -> bool: - """ - Sets if a user may query the server.. - - (Set False before querying and True after updating budget) - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - may_query (bool): flag give or remove access to user - """
- - -
-[Doku] - @abstractmethod - @user_must_exist - def get_and_set_may_user_query( - self, user_name: str, may_query: bool - ) -> bool: - """ - Atomic operation to check and set if the user may query the server. - - (Set False before querying and True after updating budget) - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - may_query (bool): flag give or remove access to user - - Returns: - bool: The may_query status of the user before the update. - """
- - -
-[Doku] - @abstractmethod - @user_must_exist - def has_user_access_to_dataset( - self, user_name: str, dataset_name: str - ) -> bool: - """ - Checks if a user may access a particular dataset - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - - Returns: - bool: True if the user has access, False otherwise. - """
- - -
-[Doku] - @abstractmethod - def get_epsilon_or_delta( - self, user_name: str, dataset_name: str, parameter: str - ) -> float: - """ - Get the total spent epsilon or delta by a specific user - on a specific dataset - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - parameter (str): total_spent_epsilon or total_spent_delta - - Returns: - float: The requested budget value. - """
- - -
-[Doku] - @user_must_have_access_to_dataset - def get_total_spent_budget( - self, user_name: str, dataset_name: str - ) -> List[float]: - """ - Get the total spent epsilon and delta spent by a specific user - on a specific dataset (since the initialisation) - - Wrapped by :py:func:`user_must_have_access_to_dataset`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - - Returns: - List[float]: The first value of the list is the epsilon value, - the second value is the delta value. - """ - return [ - self.get_epsilon_or_delta( - user_name, dataset_name, "total_spent_epsilon" - ), - self.get_epsilon_or_delta( - user_name, dataset_name, "total_spent_delta" - ), - ]
- - -
-[Doku] - @user_must_have_access_to_dataset - def get_initial_budget( - self, user_name: str, dataset_name: str - ) -> List[float]: - """ - Get the initial epsilon and delta budget - - Wrapped by :py:func:`user_must_have_access_to_dataset`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - - Returns: - List[float]: The first value of the list is the epsilon value, - the second value is the delta value. - """ - return [ - self.get_epsilon_or_delta( - user_name, dataset_name, "initial_epsilon" - ), - self.get_epsilon_or_delta( - user_name, dataset_name, "initial_delta" - ), - ]
- - -
-[Doku] - @user_must_have_access_to_dataset - def get_remaining_budget( - self, user_name: str, dataset_name: str - ) -> List[float]: - """ - Get the remaining epsilon and delta budget (initial - total spent) - - Wrapped by :py:func:`user_must_have_access_to_dataset`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - - Returns: - List[float]: The first value of the list is the epsilon value, - the second value is the delta value. - """ - init_eps, init_delta = self.get_initial_budget(user_name, dataset_name) - spent_eps, spent_delta = self.get_total_spent_budget( - user_name, dataset_name - ) - return [init_eps - spent_eps, init_delta - spent_delta]
- - -
-[Doku] - @abstractmethod - def update_epsilon_or_delta( - self, - user_name: str, - dataset_name: str, - parameter: str, - spent_value: float, - ) -> None: - """ - Update the current budget spent by a specific user - with the last spent budget. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - parameter (str): "current_epsilon" or "current_delta" - spent_value (float): spending of epsilon or delta on last query - """
- - -
-[Doku] - def update_epsilon( - self, user_name: str, dataset_name: str, spent_epsilon: float - ) -> None: - """ - Update the spent epsilon by a specific user - with the total spent epsilon - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - spent_epsilon (float): value of epsilon spent on last query - """ - return self.update_epsilon_or_delta( - user_name, dataset_name, "total_spent_epsilon", spent_epsilon - )
- - -
-[Doku] - def update_delta( - self, user_name: str, dataset_name: str, spent_delta: float - ) -> None: - """ - Update the spent delta spent by a specific user - with the total spent delta of the user - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - spent_delta (float): value of delta spent on last query - """ - self.update_epsilon_or_delta( - user_name, dataset_name, "total_spent_delta", spent_delta - )
- - -
-[Doku] - @user_must_have_access_to_dataset - def update_budget( - self, - user_name: str, - dataset_name: str, - spent_epsilon: float, - spent_delta: float, - ) -> None: - """ - Update the current epsilon and delta spent by a specific user - with the last spent delta - - Wrapped by :py:func:`user_must_have_access_to_dataset`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - spent_epsilon (float): value of epsilon spent on last query - spent_delta (float): value of delta spent on last query - """ - self.update_epsilon(user_name, dataset_name, spent_epsilon) - self.update_delta(user_name, dataset_name, spent_delta)
- - -
-[Doku] - @abstractmethod - @dataset_must_exist - def get_dataset_field(self, dataset_name: str, key: str) -> str: - """ - Get dataset field type based on dataset name and key - - Wrapped by :py:func:`dataset_must_exist`. - - Args: - dataset_name (str): Name of the dataset. - key (str): Key for the value to get in the dataset dict. - - Returns: - str: The requested value. - """
- - -
-[Doku] - @abstractmethod - @user_must_have_access_to_dataset - def get_user_previous_queries( - self, - user_name: str, - dataset_name: str, - ) -> List[dict]: - """ - Retrieves and return the queries already done by a user - - Wrapped by :py:func:`user_must_have_access_to_dataset`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - - Returns: - List[dict]: List of previous queries. - """
- - -
-[Doku] - def prepare_save_query( - self, user_name: str, query_json: dict, response: dict - ) -> dict: - """ - Prepare the query to save in archives - - Args: - user_name (str): name of the user - query_json (dict): json received from client - response (dict): response sent to the client - - Raises: - InternalServerException: If the type of query is unknown. - - Returns: - dict: The query archive dictionary. - """ - to_archive = { - "user_name": user_name, - "dataset_name": query_json.dataset_name, - "client_input": query_json.model_dump(), - "response": response, - "timestamp": time.time(), - } - match query_json.__class__.__name__: - case "SNSQLInp": - to_archive["dp_librairy"] = DPLibraries.SMARTNOISE_SQL - case "OpenDPInp": - to_archive["dp_librairy"] = DPLibraries.OPENDP - case _: - raise InternalServerException( - f"Unknown query input: {query_json.__class__.__name__}" - ) - return to_archive
- - -
-[Doku] - @abstractmethod - def save_query( - self, user_name: str, query_json: dict, response: dict - ) -> None: - """ - Save queries of user on datasets in a separate collection (table) - named "queries_archives" in the DB - - Args: - user_name (str): name of the user - query_json (dict): json received from client - response (dict): response sent to the client - """
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/admin_database/mongodb_database.html b/html/de/_modules/lomas_server/admin_database/mongodb_database.html deleted file mode 100644 index ebbb7d74..00000000 --- a/html/de/_modules/lomas_server/admin_database/mongodb_database.html +++ /dev/null @@ -1,486 +0,0 @@ - - - - - - lomas_server.admin_database.mongodb_database — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.admin_database.mongodb_database

-from typing import List
-
-from pymongo import MongoClient
-from pymongo.database import Database
-from pymongo.errors import WriteConcernError
-from pymongo.results import _WriteResult
-
-from admin_database.admin_database import (
-    AdminDatabase,
-    dataset_must_exist,
-    user_must_exist,
-    user_must_have_access_to_dataset,
-)
-
-
-
-[Doku] -class AdminMongoDatabase(AdminDatabase): - """ - Overall MongoDB database management for server state. - """ - - def __init__(self, connection_string: str, database_name: str) -> None: - """Connect to database. - - Args: - connection_string (str): Connection string to the mongodb - database_name (str): Mongodb database name. - """ - self.db: Database = MongoClient(connection_string)[database_name] - -
-[Doku] - def does_user_exist(self, user_name: str) -> bool: - """Checks if user exist in the database - - Args: - user_name (str): name of the user to check - - Returns: - bool: True if the user exists, False otherwise. - """ - doc_count = self.db.users.count_documents( - {"user_name": f"{user_name}"} - ) - return doc_count > 0
- - -
-[Doku] - def does_dataset_exist(self, dataset_name: str) -> bool: - """Checks if dataset exist in the database - - Args: - dataset_name (str): name of the dataset to check - - Returns: - bool: True if the dataset exists, False otherwise. - """ - collection_query = self.db.datasets.find({}) - for document in collection_query: - if document["dataset_name"] == dataset_name: - return True - - return False
- - -
-[Doku] - @dataset_must_exist - def get_dataset_metadata(self, dataset_name: str) -> dict: - """Returns the metadata dictionnary of the dataset. - - Wrapped by :py:func:`dataset_must_exist`. - - Args: - dataset_name (str): name of the dataset to get the metadata - - Returns: - dict: The metadata dict. - """ - metadatas = self.db.metadata.find_one( - {dataset_name: {"$exists": True}} - ) - return metadatas[dataset_name] # type: ignore
- - -
-[Doku] - @user_must_exist - def may_user_query(self, user_name: str) -> bool: - """Checks if a user may query the server. - Cannot query if already querying. - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - - Returns: - bool: True if the user exists, False otherwise. - """ - user = self.db.users.find_one({"user_name": user_name}) - return user["may_query"] # type: ignore
- - -
-[Doku] - @user_must_exist - def set_may_user_query(self, user_name: str, may_query: bool) -> None: - """Sets if a user may query the server. - - (Set False before querying and True after updating budget) - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - may_query (bool): flag give or remove access to user - - Raises: - WriteConcernError: If the result is not acknowledged. - """ - res = self.db.users.update_one( - {"user_name": f"{user_name}"}, - {"$set": {"may_query": may_query}}, - ) - check_result_acknowledged(res)
- - -
-[Doku] - @user_must_exist - def get_and_set_may_user_query( - self, user_name: str, may_query: bool - ) -> bool: - """ - Atomic operation to check and set if the user may query the server. - - (Set False before querying and True after updating budget) - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - may_query (bool): flag give or remove access to user - - Returns: - bool: The may_query status of the user before the update. - """ - res = self.db.users.find_one_and_update( - {"user_name": user_name}, {"$set": {"may_query": may_query}} - ) - - return res["may_query"] # type: ignore
- - -
-[Doku] - @user_must_exist - def has_user_access_to_dataset( - self, user_name: str, dataset_name: str - ) -> bool: - """Checks if a user may access a particular dataset - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - - Returns: - bool: True if the user has access, False otherwise. - """ - doc_count = self.db.users.count_documents( - { - "user_name": f"{user_name}", - "datasets_list.dataset_name": f"{dataset_name}", - } - ) - return doc_count > 0
- - -
-[Doku] - def get_epsilon_or_delta( - self, user_name: str, dataset_name: str, parameter: str - ) -> float: - """Get the total spent epsilon or delta by a specific user - on a specific dataset - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - parameter (str): total_spent_epsilon or total_spent_delta - - Returns: - float: The requested budget value. - """ - return list( - self.db.users.aggregate( - [ - {"$unwind": "$datasets_list"}, - { - "$match": { - "user_name": f"{user_name}", - "datasets_list.dataset_name": f"{dataset_name}", - } - }, - ] - ) - )[0]["datasets_list"][parameter]
- - -
-[Doku] - def update_epsilon_or_delta( - self, - user_name: str, - dataset_name: str, - parameter: str, - spent_value: float, - ) -> None: - """Update the current budget spent by a specific user - with the last spent budget. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - parameter (str): "current_epsilon" or "current_delta" - spent_value (float): spending of epsilon or delta on last query - - Raises: - WriteConcernError: If the result is not acknowledged. - - """ - res = self.db.users.update_one( - { - "user_name": f"{user_name}", - "datasets_list.dataset_name": f"{dataset_name}", - }, - {"$inc": {f"datasets_list.$.{parameter}": spent_value}}, - ) - check_result_acknowledged(res)
- - -
-[Doku] - @dataset_must_exist - def get_dataset_field(self, dataset_name: str, key: str) -> str: - """Get dataset field type based on dataset name and key - - Wrapped by :py:func:`dataset_must_exist`. - - Args: - dataset_name (str): Name of the dataset. - key (str): Key for the value to get in the dataset dict. - - Returns: - str: The requested value. - """ - dataset = self.db.datasets.find_one({"dataset_name": dataset_name}) - return dataset[key] # type: ignore
- - -
-[Doku] - @user_must_have_access_to_dataset - def get_user_previous_queries( - self, - user_name: str, - dataset_name: str, - ) -> List[dict]: - """Retrieves and return the queries already done by a user - - Wrapped by :py:func:`user_must_have_access_to_dataset`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - - Returns: - List[dict]: List of previous queries. - """ - queries = self.db.queries_archives.find( - { - "user_name": f"{user_name}", - "dataset_name": f"{dataset_name}", - }, - {"_id": 0}, - ) - return list(queries)
- - -
-[Doku] - def save_query( - self, user_name: str, query_json: dict, response: dict - ) -> None: - """Save queries of user on datasets in a separate collection (table) - named "queries_archives" in the DB - - Args: - user_name (str): name of the user - query_json (dict): json received from client - response (dict): response sent to the client - - Raises: - WriteConcernError: If the result is not acknowledged. - """ - to_archive = super().prepare_save_query( - user_name, query_json, response - ) - res = self.db.queries_archives.insert_one(to_archive) - check_result_acknowledged(res)
-
- - - -
-[Doku] -def check_result_acknowledged(res: _WriteResult) -> None: - """Raises an exception if the result is not acknowledged. - - Args: - res (_WriteResult): The PyMongo WriteResult to check. - - Raises: - WriteConcernError: If the result is not acknowledged. - """ - if not res.acknowledged: - raise WriteConcernError( - "Write request not acknowledged by MongoDB database." - )
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/admin_database/utils.html b/html/de/_modules/lomas_server/admin_database/utils.html deleted file mode 100644 index fdcbf571..00000000 --- a/html/de/_modules/lomas_server/admin_database/utils.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - lomas_server.admin_database.utils — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.admin_database.utils

-from types import SimpleNamespace
-
-from pymongo import MongoClient
-from pymongo.database import Database
-
-from admin_database.admin_database import AdminDatabase
-from admin_database.mongodb_database import AdminMongoDatabase
-from admin_database.yaml_database import AdminYamlDatabase
-from constants import AdminDBType
-from utils.config import DBConfig, get_config
-from utils.error_handler import InternalServerException
-
-
-
-[Doku] -def database_factory(config: DBConfig) -> AdminDatabase: - """Instantiates and returns the correct database type described in the - provided config. - - Args: - config (DBConfig): An instance of DBconfig. - - Raises: - InternalServerException: If the specified database type - is not supported. - - Returns: - AdminDatabase: A instance of the correct type of AdminDatabase. - """ - db_type = config.db_type - - match db_type: - case AdminDBType.MONGODB: - db_url = get_mongodb_url(config) - db_name = config.db_name - return AdminMongoDatabase(db_url, db_name) - - case AdminDBType.YAML: - yaml_database_file = config.db_file - return AdminYamlDatabase(yaml_database_file) - - case _: - raise InternalServerException( - f"Database type {db_type} not supported." - )
- - - -
-[Doku] -def get_mongodb_url(config: DBConfig) -> str: - """Get URL of the administration MongoDB. - - Args: - config (DBConfig): An instance of DBConfig. - - Returns: - str: A correctly formatted url for connecting to the - MongoDB database. - """ - db_username = config.username - db_password = config.password - db_address = config.address - db_port = config.port - db_name = config.db_name - - db_url = ( - f"mongodb://{db_username}:{db_password}@{db_address}:" - f"{db_port}/{db_name}?authSource=defaultdb" - ) - - return db_url
- - - -
-[Doku] -def get_mongodb() -> Database: - """Get URL of the administration MongoDB. - - Args: - config (DBConfig): An instance of DBConfig. - - Returns: - str: A correctly formatted url for connecting to the - MongoDB database. - """ - db_args = SimpleNamespace(**vars(get_config().admin_database)) - db_url = get_mongodb_url(db_args) - return MongoClient(db_url)[db_args.db_name]
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/admin_database/yaml_database.html b/html/de/_modules/lomas_server/admin_database/yaml_database.html deleted file mode 100644 index 972f4741..00000000 --- a/html/de/_modules/lomas_server/admin_database/yaml_database.html +++ /dev/null @@ -1,482 +0,0 @@ - - - - - - lomas_server.admin_database.yaml_database — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.admin_database.yaml_database

-from datetime import datetime
-from typing import List
-
-import yaml
-
-from admin_database.admin_database import (
-    AdminDatabase,
-    dataset_must_exist,
-    user_must_exist,
-    user_must_have_access_to_dataset,
-)
-from utils.error_handler import InternalServerException
-
-
-
-[Doku] -class AdminYamlDatabase(AdminDatabase): - """ - Overall Yaml database management for server state - """ - - def __init__(self, yaml_db_path: str) -> None: - """Load DB from disk. - - Args: - yaml_db_path (str): path to yaml db file. - """ - self.path: str = yaml_db_path - with open(yaml_db_path, mode="r", encoding="utf-8") as f: - self.database = yaml.safe_load(f) - -
-[Doku] - def does_user_exist(self, user_name: str) -> bool: - """Checks if user exist in the database - - Args: - user_name (str): name of the user to check - - Returns: - bool: True if the user exists, False otherwise. - """ - for user in self.database["users"]: - if user["user_name"] == user_name: - return True - - return False
- - -
-[Doku] - def does_dataset_exist(self, dataset_name: str) -> bool: - """Checks if dataset exist in the database - - Args: - dataset_name (str): name of the dataset to check - - Returns: - bool: True if the dataset exists, False otherwise. - """ - for dt in self.database["datasets"]: - if dt["dataset_name"] == dataset_name: - return True - - return False
- - -
-[Doku] - @dataset_must_exist - def get_dataset_metadata(self, dataset_name: str) -> dict: - """Returns the metadata dictionnary of the dataset. - - Wrapped by :py:func:`dataset_must_exist`. - - Args: - dataset_name (str): name of the dataset to get the metadata - - Returns: - dict: The metadata dict. - """ - for dt in self.database["datasets"]: - if dt["dataset_name"] == dataset_name: - metadata_path = dt["metadata"]["metadata_path"] - - with open(metadata_path, mode="r", encoding="utf-8") as f: - metadata = yaml.safe_load(f) - - return metadata
- - -
-[Doku] - @user_must_exist - def may_user_query(self, user_name: str) -> bool: - """Checks if a user may query the server. - Cannot query if already querying. - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - - Returns: - bool: True if the user exists, False otherwise. - """ - for user in self.database["users"]: - if user["user_name"] == user_name: - return user["may_query"] - # if user not found, return false - return False
- - -
-[Doku] - @user_must_exist - def set_may_user_query(self, user_name: str, may_query: bool) -> None: - """Sets if a user may query the server. - - (Set False before querying and True after updating budget) - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - may_query (bool): flag give or remove access to user - """ - users = self.database["users"] - for user in users: - if user["user_name"] == user_name: - user["may_query"] = may_query - self.database["users"] = users
- - -
-[Doku] - @user_must_exist - def get_and_set_may_user_query( - self, user_name: str, may_query: bool - ) -> bool: - """ - Atomic operation to check and set if the user may query the server. - - (Set False before querying and True after updating budget) - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - may_query (bool): flag give or remove access to user - - Returns: - bool: The may_query status of the user before the update. - """ - previous_may_query = False - - users = self.database["users"] - new_users = [] - for user in users: - if user["user_name"] == user_name: - previous_may_query = user["may_query"] - user["may_query"] = may_query - new_users.append(user) - self.database["users"] = new_users - - return previous_may_query
- - -
-[Doku] - @user_must_exist - def has_user_access_to_dataset( - self, user_name: str, dataset_name: str - ) -> bool: - """Checks if a user may access a particular dataset - - Wrapped by :py:func:`user_must_exist`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - - Returns: - bool: True if the user has access, False otherwise. - """ - for user in self.database["users"]: - if user["user_name"] == user_name: - for dataset in user["datasets_list"]: - if dataset["dataset_name"] == dataset_name: - return True - return False
- - -
-[Doku] - def get_epsilon_or_delta( - self, user_name: str, dataset_name: str, parameter: str - ) -> float: - """Get the total spent epsilon or delta by a specific user - on a specific dataset - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - parameter (str): total_spent_epsilon or total_spent_delta - - Returns: - float: The requested budget value. - """ - for user in self.database["users"]: - if user["user_name"] == user_name: - for dataset in user["datasets_list"]: - if dataset["dataset_name"] == dataset_name: - return dataset[parameter] - return False
- - -
-[Doku] - def update_epsilon_or_delta( - self, - user_name: str, - dataset_name: str, - parameter: str, - spent_value: float, - ) -> None: - """Update the current budget spent by a specific user - with the last spent budget. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - parameter (str): "current_epsilon" or "current_delta" - spent_value (float): spending of epsilon or delta on last query - """ - users = self.database["users"] - for user in users: - if user["user_name"] == user_name: - for dataset in user["datasets_list"]: - if dataset["dataset_name"] == dataset_name: - dataset[parameter] += spent_value - self.database["users"] = users
- - -
-[Doku] - @dataset_must_exist - def get_dataset_field( - self, dataset_name: str, key: str - ) -> str: # type: ignore - """Get dataset field type based on dataset name and key - - Wrapped by :py:func:`dataset_must_exist`. - - Args: - dataset_name (str): Name of the dataset. - key (str): Key for the value to get in the dataset dict. - - Returns: - str: The requested value. - """ - for dt in self.database["datasets"]: - if dt["dataset_name"] == dataset_name: - return dt[key] - raise InternalServerException( - f"Field {key} does not exist for dataset {dataset_name}." - )
- - -
-[Doku] - @user_must_have_access_to_dataset - def get_user_previous_queries( - self, - user_name: str, - dataset_name: str, - ) -> List[dict]: - """Retrieves and return the queries already done by a user - - Wrapped by :py:func:`user_must_have_access_to_dataset`. - - Args: - user_name (str): name of the user - dataset_name (str): name of the dataset - - Returns: - List[dict]: List of previous queries. - """ - previous_queries = [] - for q in self.database["queries"]: - if ( - q["user_name"] == user_name - and q["dataset_name"] == dataset_name - ): - previous_queries.append(q) - return previous_queries
- - -
-[Doku] - def save_query( - self, user_name: str, query_json: dict, response: dict - ) -> None: - """Save queries of user on datasets in a separate collection (table) - named "queries_archives" in the DB - - Args: - user_name (str): name of the user - query_json (dict): json received from client - response (dict): response sent to the client - """ - to_archive = super().prepare_save_query( - user_name, query_json, response - ) - self.database["queries"].append(to_archive)
- - -
-[Doku] - def save_current_database(self) -> None: - """Saves the current database with updated parameters in new yaml - with the date and hour in the path - Might be useful to verify state of DB during development - """ - new_path = self.path.replace( - ".yaml", f'_{datetime.now().strftime("%m_%d_%Y__%H_%M_%S")}.yaml' - ) - with open(new_path, mode="w", encoding="utf-8") as file: - yaml.dump(self.database, file)
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/app.html b/html/de/_modules/lomas_server/app.html deleted file mode 100644 index 431c4bdb..00000000 --- a/html/de/_modules/lomas_server/app.html +++ /dev/null @@ -1,1074 +0,0 @@ - - - - - - lomas_server.app — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.app

-from collections.abc import AsyncGenerator
-from contextlib import asynccontextmanager
-from typing import Callable
-
-from fastapi import Body, Depends, FastAPI, Header, Request, Response
-from fastapi.responses import JSONResponse, StreamingResponse
-
-from admin_database.utils import database_factory
-from constants import (
-    CONFIG_NOT_LOADED,
-    DB_NOT_LOADED,
-    QUERY_HANDLER_NOT_LOADED,
-    SERVER_LIVE,
-    AdminDBType,
-    DPLibraries,
-)
-from dataset_store.utils import dataset_store_factory
-from dp_queries.dp_libraries.utils import querier_factory
-from dp_queries.dp_logic import QueryHandler
-from dp_queries.dummy_dataset import (
-    get_dummy_dataset_for_query,
-    make_dummy_dataset,
-)
-from utils.anti_timing_att import anti_timing_att
-from utils.config import get_config
-from utils.error_handler import (
-    CUSTOM_EXCEPTIONS,
-    InternalServerException,
-    add_exception_handlers,
-)
-from utils.example_inputs import (
-    example_dummy_opendp,
-    example_dummy_smartnoise_sql,
-    example_get_admin_db_data,
-    example_get_dummy_dataset,
-    example_opendp,
-    example_smartnoise_sql,
-    example_smartnoise_sql_cost,
-)
-from utils.input_models import (
-    DummyOpenDPInp,
-    DummySNSQLInp,
-    GetDbData,
-    GetDummyDataset,
-    OpenDPInp,
-    SNSQLInp,
-    SNSQLInpCost,
-)
-from utils.loggr import LOG
-from utils.utils import add_demo_data_to_admindb, server_live, stream_dataframe
-
-
-
-[Doku] -@asynccontextmanager -async def lifespan( - app: FastAPI, -) -> ( - AsyncGenerator -): # pylint: disable=redefined-outer-name, too-many-statements - """ - Lifespan function for the server. - - This function is executed once on server startup, yields and - finishes running at server shutdown. - - Server initialization is performed (config loading, etc.) and - the server state is updated accordingly. This can have potential - side effects on the return values of the "depends" - functions, which check the server state. - """ - # Startup - LOG.info("Startup message") - - # Set some app state - app.state.admin_database = None - app.state.query_handler = None - app.state.dataset_store = None - - # General server state, can add fields if need be. - app.state.server_state = { - "state": [], - "message": [], - "LIVE": False, - } - app.state.server_state["state"].append("Startup event") - - status_ok = True - # Load config - try: - LOG.info("Loading config") - app.state.server_state["message"].append("Loading config") - config = get_config() - except InternalServerException: - LOG.info("Config could not loaded") - app.state.server_state["state"].append(CONFIG_NOT_LOADED) - app.state.server_state["message"].append( - "Server could not be started!" - ) - app.state.server_state["LIVE"] = False - status_ok = False - - # Fill up user database if in develop mode ONLY - if status_ok and config.develop_mode: - LOG.info("!! Develop mode ON !!") - LOG.info("Adding demo data to AdminDB") - app.state.server_state["message"].append("!! Develop mode ON !!") - app.state.server_state["message"].append("Adding demo data to AdminDB") - add_demo_data_to_admindb() - - # Load admin database - if status_ok: - try: - LOG.info("Loading admin database") - app.state.server_state["message"].append("Loading admin database") - app.state.admin_database = database_factory(config.admin_database) - except InternalServerException as e: - LOG.exception("Failed at startup:" + str(e)) - app.state.server_state["state"].append(DB_NOT_LOADED) - app.state.server_state["message"].append( - f"Admin database could not be loaded: {str(e)}" - ) - app.state.server_state["LIVE"] = False - status_ok = False - - # Load query handler - if status_ok: - LOG.info("Loading query handler") - app.state.server_state["message"].append("Loading dataset store") - app.state.dataset_store = dataset_store_factory( - config.dataset_store, app.state.admin_database - ) - - app.state.server_state["message"].append("Loading query handler") - app.state.query_handler = QueryHandler( - app.state.admin_database, app.state.dataset_store - ) - - app.state.server_state["state"].append("Startup completed") - app.state.server_state["message"].append("Startup completed") - - if app.state.query_handler is None: - LOG.info("QueryHandler not loaded") - app.state.server_state["state"].append(QUERY_HANDLER_NOT_LOADED) - app.state.server_state["message"].append( - "Server could not be started!" - ) - app.state.server_state["LIVE"] = False - status_ok = False - - if status_ok: - LOG.info("Server start condition OK") - app.state.server_state["state"].append(SERVER_LIVE) - app.state.server_state["message"].append("Server start condition OK") - app.state.server_state["LIVE"] = True - - yield # app is handling requests - - # Shutdown event - if ( - config is not None - and app.state.admin_database is not None - and config.admin_database.db_type == AdminDBType.YAML - ): - app.state.admin_database.save_current_database()
- - - -# This object holds the server object -app = FastAPI(lifespan=lifespan) - - -# A simple hack to hinder the timing attackers -
-[Doku] -@app.middleware("http") -async def middleware( - request: Request, call_next: Callable[[Request], Response] -) -> Response: - """Adds delays to requests response to protect against timing attack""" - return await anti_timing_att(request, call_next, get_config())
- - - -# Add custom exception handlers -add_exception_handlers(app) - -# API Endpoints -# ----------------------------------------------------------------------------- - - -# Get server state -
-[Doku] -@app.get("/state", tags=["ADMIN_USER"]) -async def get_state( - user_name: str = Header(None), -) -> JSONResponse: - """Returns the current state dict of this server instance. - - Args: - user_name (str, optional): The user name. Defaults to Header(None). - - Returns: - JSONResponse: The state of the server instance. - """ - return JSONResponse( - content={ - "requested_by": user_name, - "state": app.state.server_state, - } - )
- - - -
-[Doku] -@app.get( - "/get_memory_usage", - dependencies=[Depends(server_live)], - tags=["ADMIN_USER"], -) -async def get_memory_usage() -> JSONResponse: - """Return the dataset store object memory usage - Args: - user_name (str, optional): The user name. Defaults to Header(None). - - Returns: - JSONResponse: with DatasetStore object memory usage - """ - return JSONResponse( - content={ - "memory_usage": app.state.dataset_store.memory_usage, - } - )
- - - -# Metadata query -
-[Doku] -@app.post( - "/get_dataset_metadata", - dependencies=[Depends(server_live)], - tags=["USER_METADATA"], -) -def get_dataset_metadata( - _request: Request, - query_json: GetDbData = Body(example_get_admin_db_data), -) -> JSONResponse: - """ - Retrieves metadata for a given dataset. - - Args: - request (Request): Raw request object - query_json (GetDbData, optional): A JSON object containing - the dataset_name key for indicating the dataset. - Defaults to Body(example_get_admin_db_data). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - - Returns: - JSONResponse: The metadata dictionary for the specified - dataset_name. - """ - try: - ds_metadata = app.state.admin_database.get_dataset_metadata( - query_json.dataset_name - ) - - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return ds_metadata
- - - -# Dummy dataset query -
-[Doku] -@app.post( - "/get_dummy_dataset", - dependencies=[Depends(server_live)], - tags=["USER_DUMMY"], -) -def get_dummy_dataset( - _request: Request, - query_json: GetDummyDataset = Body(example_get_dummy_dataset), -) -> StreamingResponse: - """ - Generates and returns a dummy dataset. - - Args: - request (Request): Raw request object - query_json (GetDummyDataset, optional): - A JSON object containing the following: - - nb_rows (int, optional): The number of rows in the - dummy dataset (default: 100). - - seed (int, optional): The random seed for generating - the dummy dataset (default: 42). - Defaults to Body(example_get_dummy_dataset). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - - Returns: - StreamingResponse: a pd.DataFrame representing the dummy dataset. - """ - try: - ds_metadata = app.state.admin_database.get_dataset_metadata( - query_json.dataset_name - ) - - dummy_df = make_dummy_dataset( - ds_metadata, query_json.dummy_nb_rows, query_json.dummy_seed - ) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return stream_dataframe(dummy_df)
- - - -# Smartnoise SQL query -
-[Doku] -@app.post( - "/smartnoise_query", - dependencies=[Depends(server_live)], - tags=["USER_QUERY"], -) -def smartnoise_sql_handler( - _request: Request, - query_json: SNSQLInp = Body(example_smartnoise_sql), - user_name: str = Header(None), -) -> JSONResponse: - """ - Handles queries for the SmartNoiseSQL library. - - Args: - request (Request): Raw request object - query_json (SNSQLInp): A JSON object containing: - - query: The SQL query to execute. NOTE: the table name is "df", - the query must end with "FROM df". - - epsilon (float): Privacy parameter (e.g., 0.1). - - delta (float): Privacy parameter (e.g., 1e-5). - - mechanisms (dict, optional): Dictionary of mechanisms for the - query (default: {}). See "Smartnoise-SQL mechanisms documentation - https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms. - - postprocess (bool, optional): Whether to postprocess the query - results (default: True). - See "Smartnoise-SQL postprocessing documentation - https://docs.smartnoise.org/sql/advanced.html#postprocess. - - Defaults to Body(example_smartnoise_sql). - - user_name (str, optional): The user name. - Defaults to Header(None). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: If there is not enough budget or the dataset - does not exist. - UnauthorizedAccessException: A query is already ongoing for this user, - the user does not exist or does not have access to the dataset. - - Returns: - JSONResponse: A JSON object containing the following: - - requested_by (str): The user name. - - query_response (pd.DataFrame): A DataFrame containing - the query response. - - spent_epsilon (float): The amount of epsilon budget spent - for the query. - - spent_delta (float): The amount of delta budget spent - for the query. - """ - try: - response = app.state.query_handler.handle_query( - DPLibraries.SMARTNOISE_SQL, query_json, user_name - ) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return response
- - - -# Smartnoise SQL Dummy query -
-[Doku] -@app.post( - "/dummy_smartnoise_query", - dependencies=[Depends(server_live)], - tags=["USER_DUMMY"], -) -def dummy_smartnoise_sql_handler( - _request: Request, - query_json: DummySNSQLInp = Body(example_dummy_smartnoise_sql), -) -> JSONResponse: - """ - Handles queries on dummy datasets for the SmartNoiseSQL library. - - Args: - request (Request): Raw request object - query_json (DummySNSQLInp, optional): A JSON object containing: - - query: The SQL query to execute. NOTE: the table name is "df", - the query must end with "FROM df". - - epsilon (float): Privacy parameter (e.g., 0.1). - - delta (float): Privacy parameter (e.g., 1e-5). - - mechanisms (dict, optional): Dictionary of mechanisms for the - query (default: {}). See Smartnoise-SQL mechanisms documentation - https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms. - - postprocess (bool, optional): Whether to postprocess the query - results (default: True). - See Smartnoise-SQL postprocessing documentation - https://docs.smartnoise.org/sql/advanced.html#postprocess. - - dummy (bool, optional): Whether to use a dummy dataset - (default: False). - - nb_rows (int, optional): The number of rows in the dummy dataset - (default: 100). - - seed (int, optional): The random seed for generating - the dummy dataset (default: 42). - - Defaults to Body(example_dummy_smartnoise_sql). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: If there is not enough budget or the dataset - does not exist. - - Returns: - JSONResponse: A JSON object containing: - - query_response (pd.DataFrame): a DataFrame containing - the query response. - """ - ds_private_dataset = get_dummy_dataset_for_query( - app.state.admin_database, query_json - ) - dummy_querier = querier_factory( - DPLibraries.SMARTNOISE_SQL, private_dataset=ds_private_dataset - ) - try: - _ = dummy_querier.cost(query_json) # verify cost works - response_df = dummy_querier.query(query_json) - response = JSONResponse(content={"query_response": response_df}) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return response
- - - -
-[Doku] -@app.post( - "/estimate_smartnoise_cost", - dependencies=[Depends(server_live)], - tags=["USER_QUERY"], -) -def estimate_smartnoise_cost( - _request: Request, - query_json: SNSQLInpCost = Body(example_smartnoise_sql_cost), -) -> JSONResponse: - """ - Estimates the privacy loss budget cost of a SmartNoiseSQL query. - - Args: - request (Request): Raw request object - query_json (SNSQLInpCost, optional): - A JSON object containing the following: - - query: The SQL query to estimate the cost for. - NOTE: the table name is "df", the query must end with "FROM df". - - epsilon (float): Privacy parameter (e.g., 0.1). - - delta (float): Privacy parameter (e.g., 1e-5). - - mechanisms (dict, optional): Dictionary of mechanisms - for the query (default: {}). - See Smartnoise-SQL mechanisms documentation - https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms. - - Defaults to Body(example_smartnoise_sql_cost). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: The dataset does not exist. - - Returns: - JSONResponse: A JSON object containing: - - epsilon_cost (float): The estimated epsilon cost. - - delta_cost (float): The estimated delta cost. - """ - try: - response = app.state.query_handler.estimate_cost( - DPLibraries.SMARTNOISE_SQL, - query_json, - ) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return JSONResponse(content=response)
- - - -
-[Doku] -@app.post( - "/opendp_query", dependencies=[Depends(server_live)], tags=["USER_QUERY"] -) -def opendp_query_handler( - _request: Request, - query_json: OpenDPInp = Body(example_opendp), - user_name: str = Header(None), -) -> JSONResponse: - """ - Handles queries for the OpenDP Library. - - Args: - request (Request): Raw request object. - query_json (OpenDPInp, optional): A JSON object containing the following: - - opendp_pipeline: The OpenDP pipeline for the query. - - fixed_delta: If the pipeline measurement is of type - "ZeroConcentratedDivergence" (e.g. with "make_gaussian") then it is - converted to "SmoothedMaxDivergence" with "make_zCDP_to_approxDP" - (see "opendp measurements documentation at - https://docs.opendp.org/en/stable/api/python/opendp.combinators.html#opendp.combinators.make_zCDP_to_approxDP). # noqa # pylint: disable=C0301 - In that case a "fixed_delta" must be provided by the user. - - Defaults to Body(example_opendp). - - user_name (str, optional): The user name. - Defaults to Header(None). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: The pipeline does not contain a "measurement", - there is not enough budget or the dataset does not exist. - UnauthorizedAccessException: A query is already ongoing for this user, - the user does not exist or does not have access to the dataset. - - Returns: - JSONResponse: A JSON object containing the following: - - requested_by (str): The user name. - - query_response (pd.DataFrame): A DataFrame containing - the query response. - - spent_epsilon (float): The amount of epsilon budget spent - for the query. - - spent_delta (float): The amount of delta budget spent - for the query. - """ - try: - response = app.state.query_handler.handle_query( - DPLibraries.OPENDP, query_json, user_name - ) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return JSONResponse(content=response)
- - - -
-[Doku] -@app.post( - "/dummy_opendp_query", - dependencies=[Depends(server_live)], - tags=["USER_DUMMY"], -) -def dummy_opendp_query_handler( - _request: Request, - query_json: DummyOpenDPInp = Body(example_dummy_opendp), -) -> JSONResponse: - """ - Handles queries on dummy datasets for the OpenDP library. - - Args: - request (Request): Raw request object. - query_json (DummyOpenDPInp, optional): - A JSON object containing the following: - - opendp_pipeline: The OpenDP pipeline for the query. - - fixed_delta: If the pipeline measurement is of type\ - "ZeroConcentratedDivergence" (e.g. with "make_gaussian") then - it is converted to "SmoothedMaxDivergence" with - "make_zCDP_to_approxDP" (see opendp measurements documentation at - https://docs.opendp.org/en/stable/api/python/opendp.combinators.html#opendp.combinators.make_zCDP_to_approxDP). # noqa # pylint: disable=C0301 - In that case a "fixed_delta" must be provided by the user. - - dummy (bool, optional): Whether to use a dummy dataset - (default: False). - - nb_rows (int, optional): The number of rows - in the dummy dataset (default: 100). - - seed (int, optional): The random seed for generating - the dummy dataset (default: 42). - - Defaults to Body(example_dummy_opendp). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: If there is not enough budget or the dataset - does not exist. - - Returns: - JSONResponse: A JSON object containing: - - query_response (pd.DataFrame): a DataFrame containing - the query response. - """ - ds_private_dataset = get_dummy_dataset_for_query( - app.state.admin_database, query_json - ) - dummy_querier = querier_factory( - DPLibraries.OPENDP, private_dataset=ds_private_dataset - ) - - try: - _ = dummy_querier.cost(query_json) # verify cost works - response_df = dummy_querier.query(query_json) - response = {"query_response": response_df} - - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return JSONResponse(content=response)
- - - -
-[Doku] -@app.post( - "/estimate_opendp_cost", - dependencies=[Depends(server_live)], - tags=["USER_QUERY"], -) -def estimate_opendp_cost( - _request: Request, - query_json: OpenDPInp = Body(example_opendp), -) -> JSONResponse: - """ - Estimates the privacy loss budget cost of an OpenDP query. - - Args: - request (Request): Raw request object - query_json (OpenDPInp, optional): - A JSON object containing the following: - - "opendp_pipeline": The OpenDP pipeline for the query. - - Defaults to Body(example_opendp). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: The dataset does not exist or the - pipeline does not contain a measurement. - - Returns: - JSONResponse: A JSON object containing: - - epsilon_cost (float): The estimated epsilon cost. - - delta_cost (float): The estimated delta cost. - """ - try: - response = app.state.query_handler.estimate_cost( - DPLibraries.OPENDP, - query_json, - ) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return JSONResponse(content=response)
- - - -# MongoDB get initial budget -
-[Doku] -@app.post( - "/get_initial_budget", - dependencies=[Depends(server_live)], - tags=["USER_BUDGET"], -) -def get_initial_budget( - _request: Request, - query_json: GetDbData = Body(example_get_admin_db_data), - user_name: str = Header(None), -) -> JSONResponse: - """ - Returns the initial budget for a user and dataset. - - Args: - request (Request): Raw request object - query_json (GetDbData, optional): A JSON object containing: - - dataset_name (str): The name of the dataset. - - Defaults to Body(example_get_admin_db_data). - - user_name (str, optional): The user name. - Defaults to Header(None). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: The dataset does not exist. - UnauthorizedAccessException: The user does not exist or - the user does not have access to the dataset. - Returns: - JSONResponse: a JSON object with: - - initial_epsilon (float): initial epsilon budget. - - initial_delta (float): initial delta budget. - """ - try: - ( - initial_epsilon, - initial_delta, - ) = app.state.admin_database.get_initial_budget( - user_name, query_json.dataset_name - ) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return JSONResponse( - content={ - "initial_epsilon": initial_epsilon, - "initial_delta": initial_delta, - } - )
- - - -# MongoDB get total spent budget -
-[Doku] -@app.post( - "/get_total_spent_budget", - dependencies=[Depends(server_live)], - tags=["USER_BUDGET"], -) -def get_total_spent_budget( - _request: Request, - query_json: GetDbData = Body(example_get_admin_db_data), - user_name: str = Header(None), -) -> JSONResponse: - """ - Returns the spent budget for a user and dataset. - - Args: - request (Request): Raw request object - query_json (GetDbData, optional): A JSON object containing: - - dataset_name (str): The name of the dataset. - - Defaults to Body(example_get_admin_db_data). - - user_name (str, optional): The user name. - Defaults to Header(None). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: The dataset does not exist. - UnauthorizedAccessException: The user does not exist or - the user does not have access to the dataset. - Returns: - JSONResponse: a JSON object with: - - total_spent_epsilon (float): total spent epsilon budget. - - total_spent_delta (float): total spent delta budget. - """ - try: - ( - total_spent_epsilon, - total_spent_delta, - ) = app.state.admin_database.get_total_spent_budget( - user_name, query_json.dataset_name - ) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return JSONResponse( - content={ - "total_spent_epsilon": total_spent_epsilon, - "total_spent_delta": total_spent_delta, - } - )
- - - -# MongoDB get remaining budget -
-[Doku] -@app.post( - "/get_remaining_budget", - dependencies=[Depends(server_live)], - tags=["USER_BUDGET"], -) -def get_remaining_budget( - _request: Request, - query_json: GetDbData = Body(example_get_admin_db_data), - user_name: str = Header(None), -) -> JSONResponse: - """ - Returns the remaining budget for a user and dataset. - - Args: - request (Request): Raw request object - query_json (GetDbData, optional): A JSON object containing: - - dataset_name (str): The name of the dataset. - - Defaults to Body(example_get_admin_db_data). - - user_name (str, optional): The user name. - Defaults to Header(None). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: The dataset does not exist. - UnauthorizedAccessException: The user does not exist or - the user does not have access to the dataset. - Returns: - JSONResponse: a JSON object with: - - remaining_epsilon (float): remaining epsilon budget. - - remaining_delta (float): remaining delta budget. - """ - try: - rem_epsilon, rem_delta = app.state.admin_database.get_remaining_budget( - user_name, query_json.dataset_name - ) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return JSONResponse( - content={ - "remaining_epsilon": rem_epsilon, - "remaining_delta": rem_delta, - } - )
- - - -# MongoDB get archives -
-[Doku] -@app.post( - "/get_previous_queries", - dependencies=[Depends(server_live)], - tags=["USER_BUDGET"], -) -def get_user_previous_queries( - _request: Request, - query_json: GetDbData = Body(example_get_admin_db_data), - user_name: str = Header(None), -) -> JSONResponse: - """ - Returns the query history of a user on a specific dataset. - - Args: - request (Request): Raw request object - query_json (GetDbData, optional): A JSON object containing: - - dataset_name (str): The name of the dataset. - - Defaults to Body(example_get_admin_db_data). - - user_name (str, optional): The user name. - Defaults to Header(None). - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: The dataset does not exist. - UnauthorizedAccessException: The user does not exist or - the user does not have access to the dataset. - - Returns: - JSONResponse: A JSON object containing: - - previous_queries (list[dict]): a list of dictionaries - containing the previous queries. - """ - try: - previous_queries = app.state.admin_database.get_user_previous_queries( - user_name, query_json.dataset_name - ) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - return JSONResponse(content={"previous_queries": previous_queries})
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/constants.html b/html/de/_modules/lomas_server/constants.html deleted file mode 100644 index 2fc22828..00000000 --- a/html/de/_modules/lomas_server/constants.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - lomas_server.constants — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.constants

-import os
-import string
-from enum import StrEnum
-
-# Get config and secrets from correct location
-if "LOMAS_CONFIG_PATH" in os.environ:
-    CONFIG_PATH = f"""{os.environ.get("LOMAS_CONFIG_PATH")}"""
-    print(CONFIG_PATH)
-else:
-    CONFIG_PATH = "/usr/lomas_server/runtime.yaml"
-
-if "LOMAS_SECRETS_PATH" in os.environ:
-    SECRETS_PATH = f"""{os.environ.get("LOMAS_SECRETS_PATH")}"""
-else:
-    SECRETS_PATH = "/usr/lomas_server/secrets.yaml"
-
-
-
-[Doku] -class ConfigKeys(StrEnum): - """Keys of the configuration file""" - - RUNTIME_ARGS: str = "runtime_args" - SERVER: str = "server" - SETTINGS: str = "settings" - DEVELOP_MODE: str = "develop_mode" - TIME_ATTACK: str = "time_attack" - SUBMIT_LIMIT: str = "submit_limit" - DB: str = "admin_database" - DB_TYPE: str = "db_type" - DB_TYPE_MONGODB: str = "mongodb" - MONGODB_ADDR: str = "address" - MONGODB_PORT: str = "port" - DATASET_STORE: str = "dataset_store" - DATASET_STORE_TYPE: str = "ds_store_type" - LRU_DATASET_STORE_MAX_SIZE: str = "max_memory_usage"
- - - -
-[Doku] -class AdminDBType(StrEnum): - """Types of administration databases""" - - YAML: str = "yaml" - MONGODB: str = "mongodb"
- - - -
-[Doku] -class DatasetStoreType(StrEnum): - """Types of classes to handle datasets in memory""" - - BASIC: str = "basic" - LRU: str = "LRU_cache"
- - - -
-[Doku] -class TimeAttackMethod(StrEnum): - """Possible methods against timing attacks""" - - JITTER = "jitter" - STALL = "stall"
- - - -# Server states -QUERY_HANDLER_NOT_LOADED = "QueryHander not loaded" -DB_NOT_LOADED = "User database not loaded" -CONFIG_NOT_LOADED = "Config not loaded" -SERVER_LIVE = "LIVE" - -# Server error messages -INTERNAL_SERVER_ERROR = ( - "Internal server error. Please contact the administrator of this service." -) - -# DP constants -EPSILON_LIMIT: float = 5.0 -DELTA_LIMIT: float = 0.0004 - - -# Supported DP libraries -
-[Doku] -class DPLibraries(StrEnum): - """Name of DP Library used in the query""" - - SMARTNOISE_SQL = "smartnoise_sql" - OPENDP = "opendp"
- - - -# Private Databases -
-[Doku] -class PrivateDatabaseType(StrEnum): - """Type of Private Database for the private data""" - - PATH = "PATH_DB" - S3 = "S3_DB"
- - - -# OpenDP Measurement Divergence Type -
-[Doku] -class OpenDPMeasurement(StrEnum): - """Type of divergence for opendp measurement""" - - FIXED_SMOOTHED_MAX_DIVERGENCE = "fixed_smoothed_max_divergence" - MAX_DIVERGENCE = "max_divergence" - SMOOTHED_MAX_DIVERGENCE = "smoothed_max_divergence" - ZERO_CONCENTRATED_DIVERGENCE = "zero_concentrated_divergence"
- - - -# Dummy dataset generation -DUMMY_NB_ROWS = 100 -DUMMY_SEED = 42 -DEFAULT_NUMERICAL_MIN = -10000 -DEFAULT_NUMERICAL_MAX = 10000 -RANDOM_STRINGS = list( - string.ascii_lowercase + string.ascii_uppercase + string.digits -) -RANDOM_DATE_START = "01/01/2000" -RANDOM_DATE_RANGE = 50 * 365 * 24 * 60 * 60 # 50 years -NB_RANDOM_NONE = 5 # if nullable, how many random none to add - -# Smartnoise sql -STATS = ["count", "sum_int", "sum_large_int", "sum_float", "threshold"] -MAX_NAN_ITERATION = 5 -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dataset_store/basic_dataset_store.html b/html/de/_modules/lomas_server/dataset_store/basic_dataset_store.html deleted file mode 100644 index e87c9f57..00000000 --- a/html/de/_modules/lomas_server/dataset_store/basic_dataset_store.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - lomas_server.dataset_store.basic_dataset_store — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Quellcode für lomas_server.dataset_store.basic_dataset_store

-from typing import Dict
-
-from admin_database.admin_database import AdminDatabase
-from constants import DPLibraries
-from dataset_store.dataset_store import DatasetStore
-from dp_queries.dp_libraries.utils import querier_factory
-from dp_queries.dp_querier import DPQuerier
-from private_dataset.utils import private_dataset_factory
-
-
-
-[Doku] -class BasicDatasetStore(DatasetStore): - """ - Basic implementation of the QuerierManager interface. - - The queriers are initialized lazily and put into a dict. - There is no memory management => The manager will fail if the datasets are - too large to fit in memory. - """ - - dp_queriers: Dict[str, Dict[str, DPQuerier]] = {} - - def __init__(self, admin_database: AdminDatabase) -> None: - """Initializer. - - Args: - admin_database (AdminDatabase): An initialized AdminDatabase. - """ - super().__init__(admin_database) - self.dp_queriers = {} - self.admin_database = admin_database - - def _add_dataset(self, dataset_name: str) -> None: - """Adds a dataset to the manager. - - Args: - dataset_name (str): The name of the dataset. - """ - # Should not call this function if dataset already present. - assert ( - dataset_name not in self.dp_queriers - ), "BasicQuerierManager: \ - Trying to add a dataset already in self.dp_queriers" - - # Metadata and data getter - private_dataset = private_dataset_factory( - dataset_name, self.admin_database - ) - - # Initialize dict - self.dp_queriers[dataset_name] = {} - - for lib in DPLibraries: - querier = querier_factory(lib.value, private_dataset) - self.dp_queriers[dataset_name][lib.value] = querier - -
-[Doku] - def get_querier(self, dataset_name: str, query_type: str) -> DPQuerier: - """ - Returns the querier for the given dataset and library - - Args: - dataset_name (str): The dataset name. - query_type (str): The type of DP library. - One of :py:class:`constants.DPLibraries` - - Returns: - DPQuerier: The DPQuerier for the specified dataset and library. - """ - if dataset_name not in self.dp_queriers: - self._add_dataset(dataset_name) - - return self.dp_queriers[dataset_name][query_type]
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dataset_store/dataset_store.html b/html/de/_modules/lomas_server/dataset_store/dataset_store.html deleted file mode 100644 index 01933ffa..00000000 --- a/html/de/_modules/lomas_server/dataset_store/dataset_store.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - lomas_server.dataset_store.dataset_store — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.dataset_store.dataset_store

-from abc import ABC, abstractmethod
-
-from admin_database.admin_database import AdminDatabase
-from dp_queries.dp_querier import DPQuerier
-
-
-
-[Doku] -class DatasetStore(ABC): - """ - Manages the DPQueriers for the different datasets and libraries - - Holds a reference to the user database in order to get information - about users. - - We make the _add_dataset function private to enforce lazy loading - of queriers. - """ - - admin_database: AdminDatabase - - def __init__(self, admin_database: AdminDatabase) -> None: - self.admin_database = admin_database - - @abstractmethod - def _add_dataset(self, dataset_name: str) -> None: - """Adds a dataset to the manager - - Args: - dataset_name (str): The dataset name. - """ - -
-[Doku] - @abstractmethod - def get_querier(self, dataset_name: str, library: str) -> DPQuerier: - """Returns the querier for the given dataset and library - - Args: - dataset_name (str): The dataset name. - library (str): The type of DP library. - One of :py:class:`constants.DPLibraries` - - Returns: - DPQuerier: The DPQuerier for the specified dataset and library. - """
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dataset_store/lru_dataset_store.html b/html/de/_modules/lomas_server/dataset_store/lru_dataset_store.html deleted file mode 100644 index 61dc13fd..00000000 --- a/html/de/_modules/lomas_server/dataset_store/lru_dataset_store.html +++ /dev/null @@ -1,283 +0,0 @@ - - - - - - lomas_server.dataset_store.lru_dataset_store — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.dataset_store.lru_dataset_store

-from collections import OrderedDict
-
-from admin_database.admin_database import AdminDatabase
-from dataset_store.dataset_store import DatasetStore
-from dataset_store.private_dataset_observer import PrivateDatasetObserver
-from dp_queries.dp_libraries.utils import querier_factory
-from dp_queries.dp_querier import DPQuerier
-from private_dataset.private_dataset import PrivateDataset
-from private_dataset.utils import private_dataset_factory
-from utils.error_handler import InternalServerException
-from utils.loggr import LOG
-
-
-
-[Doku] -class LRUDatasetStore(DatasetStore, PrivateDatasetObserver): - """ - Implementation of the DatasetStore interface, with an LRU cache. - - Subscribes to the PrivateDatasets to get notified if their memory usage - changes and then clears the cache accordingly in order stay below - the maximum memory usage. - """ - - dataset_cache: OrderedDict[str, PrivateDataset] - - def __init__( - self, admin_database: AdminDatabase, max_memory_usage: int = 1024 - ) -> None: - """Initializer. - - Args: - admin_database (AdminDatabase): An initialized AdminDatabase. - max_memory_usage (int, optional): Maximum memory usage limit - for the manager.. Defaults to 1024. - """ - super().__init__(admin_database) - self.admin_database = admin_database - self.max_memory_usage = max_memory_usage - - self.dataset_cache = OrderedDict() - self.memory_usage = 0 - - def _add_dataset(self, dataset_name: str) -> None: - """Adds a dataset to the manager. - - Makes sure the memory usage limit is not exceeded. - - Args: - dataset_name (str): The name of the dataset. - """ - # Should not call this function if dataset already present. - assert ( - dataset_name not in self.dataset_cache.keys() - ), "BasicQuerierManager: \ - Trying to add a dataset already in self.dp_queriers" - - # Make private dataset - private_dataset = private_dataset_factory( - dataset_name, self.admin_database - ) - private_dataset.subscribe_for_memory_usage_updates(self) - - # Remove least recently used dataset from cache if not enough space - private_dataset_mem_usage = private_dataset.get_memory_usage() - - if private_dataset_mem_usage > self.max_memory_usage: - raise InternalServerException( - f"Dataset {dataset_name} too large" - "to fit in dataset manager memory." - ) - - while ( - self.memory_usage + private_dataset_mem_usage - > self.max_memory_usage - ): - evicted_ds_name, evicted_ds = self.dataset_cache.popitem( - last=False - ) - self.memory_usage -= evicted_ds.get_memory_usage() - LOG.info(f"Dataset {evicted_ds_name} was evicted from cache.") - - self.dataset_cache[dataset_name] = private_dataset - self.memory_usage += private_dataset_mem_usage - - LOG.info(f"New dataset cache size: {self.memory_usage} MiB") - -
-[Doku] - def update_memory_usage(self) -> None: - """Remove least recently used datasets until the cache - is back to below or equal to its maximum size. - """ - self.memory_usage = sum( - private_ds.get_memory_usage() - for private_ds in self.dataset_cache.values() - ) - - while self.memory_usage > self.max_memory_usage: - evicted_ds_name, evicted_ds = self.dataset_cache.popitem( - last=False - ) - self.memory_usage -= evicted_ds.get_memory_usage() - LOG.info(f"Dataset {evicted_ds_name} was evicted from cache.") - - LOG.info(f"New dataset cache size: {self.memory_usage} MiB")
- - -
-[Doku] - def get_querier(self, dataset_name: str, library: str) -> DPQuerier: - """Returns the querier for the given dataset and library - - Args: - dataset_name (str): The dataset name. - library (str): The type of DP library. - One of :py:class:`constants.DPLibraries` - - Returns: - DPQuerier: The DPQuerier for the specified dataset and library. - """ - # Add dataset to cache if not present and get it. - if dataset_name not in self.dataset_cache: - self._add_dataset(dataset_name) - else: - self.dataset_cache.move_to_end(dataset_name) - assert dataset_name in self.dataset_cache.keys() - - private_dataset = self.dataset_cache[dataset_name] - return querier_factory(library, private_dataset)
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dataset_store/private_dataset_observer.html b/html/de/_modules/lomas_server/dataset_store/private_dataset_observer.html deleted file mode 100644 index 9c25cfd3..00000000 --- a/html/de/_modules/lomas_server/dataset_store/private_dataset_observer.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - lomas_server.dataset_store.private_dataset_observer — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Quellcode für lomas_server.dataset_store.private_dataset_observer

-from abc import ABC, abstractmethod
-
-
-
-[Doku] -class PrivateDatasetObserver(ABC): - """ - We use this abstract class to "subscribe" to object instances - (PrivateDataset) so that they can notify instances of this - abstract class (LRUDatasetStore or other DatasetStore implementing - caching) when their memory usage changes. - """ - -
-[Doku] - @abstractmethod - def update_memory_usage(self) -> None: - """Abstract method to update total memory used by datasets"""
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dataset_store/utils.html b/html/de/_modules/lomas_server/dataset_store/utils.html deleted file mode 100644 index 4171a2e9..00000000 --- a/html/de/_modules/lomas_server/dataset_store/utils.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - lomas_server.dataset_store.utils — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.dataset_store.utils

-from admin_database.admin_database import AdminDatabase
-from constants import DatasetStoreType
-from dataset_store.basic_dataset_store import BasicDatasetStore
-from dataset_store.dataset_store import DatasetStore
-from dataset_store.lru_dataset_store import LRUDatasetStore
-from utils.config import DatasetStoreConfig
-from utils.error_handler import InternalServerException
-
-
-
-[Doku] -def dataset_store_factory( - config: DatasetStoreConfig, admin_database: AdminDatabase -) -> DatasetStore: - """ - Instantiates and returns the correct DatasetStore based on the config. - - Args: - config (DatasetStoreConfig): A valid DatasetStoreConfig. - admin_database (AdminDatabase): An initialized AdminDatabase instance. - - Raises: - InternalServerException: If the dataset store type from the config - does not exist. - - Returns: - DatasetStore: The correct DatasetStore instance. - """ - ds_store_type = config.ds_store_type - - match config.ds_store_type: - case DatasetStoreType.BASIC: - return BasicDatasetStore(admin_database) - case DatasetStoreType.LRU: - return LRUDatasetStore(admin_database, config.max_memory_usage) - case _: - raise InternalServerException( - f"Dataset Store type {ds_store_type} does not exist." - )
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dp_queries/dp_libraries/opendp.html b/html/de/_modules/lomas_server/dp_queries/dp_libraries/opendp.html deleted file mode 100644 index 4b00456f..00000000 --- a/html/de/_modules/lomas_server/dp_queries/dp_libraries/opendp.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - - lomas_server.dp_queries.dp_libraries.opendp — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.dp_queries.dp_libraries.opendp

-import opendp as dp
-from opendp.mod import enable_features
-from opendp_logger import make_load_json
-
-from constants import DPLibraries, OpenDPMeasurement
-from dp_queries.dp_querier import DPQuerier
-from utils.error_handler import (
-    ExternalLibraryException,
-    InternalServerException,
-    InvalidQueryException,
-)
-from utils.input_models import OpenDPInp
-from utils.loggr import LOG
-
-enable_features("contrib")
-
-PT_TYPE = "^py_type:*"
-
-
-
-[Doku] -class OpenDPQuerier(DPQuerier): - """ - Concrete implementation of the DPQuerier ABC for the OpenDP library. - """ - -
-[Doku] - def cost(self, query_json: OpenDPInp) -> tuple[float, float]: - """ - Estimate cost of query - - Args: - query_json (BaseModel): The JSON request object for the query. - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InternalServerException: For any other unforseen exceptions. - InvalidQueryException: The pipeline does not contain a - "measurement", there is not enough budget or the dataset - does not exist. - - Returns: - tuple[float, float]: The tuple of costs, the first value - is the epsilon cost, the second value is the delta value. - """ - opendp_pipe = reconstruct_measurement_pipeline(query_json.opendp_json) - - measurement_type = get_output_measure(opendp_pipe) - # https://docs.opendp.org/en/stable/user/combinators.html#measure-casting - if measurement_type == OpenDPMeasurement.ZERO_CONCENTRATED_DIVERGENCE: - opendp_pipe = dp.combinators.make_zCDP_to_approxDP(opendp_pipe) - measurement_type = OpenDPMeasurement.SMOOTHED_MAX_DIVERGENCE - - max_ids = self.private_dataset.get_metadata()["max_ids"] - try: - cost = opendp_pipe.map(d_in=int(max_ids)) - except TypeError: - try: - cost = opendp_pipe.map(d_in=float(max_ids)) - except Exception as e: - LOG.exception(e) - raise ExternalLibraryException( - DPLibraries.OPENDP, - "Error obtaining cost:" + str(e), - ) from e - except Exception as e: - LOG.exception(e) - raise ExternalLibraryException( - DPLibraries.OPENDP, "Error obtaining cost:" + str(e) - ) from e - - # Cost interpretation - match measurement_type: - case ( - OpenDPMeasurement.FIXED_SMOOTHED_MAX_DIVERGENCE - ): # Approximate DP with fix delta - epsilon, delta = cost - case OpenDPMeasurement.MAX_DIVERGENCE: # Pure DP - epsilon, delta = cost, 0 - case OpenDPMeasurement.SMOOTHED_MAX_DIVERGENCE: # Approximate DP - if query_json.fixed_delta is None: - raise InvalidQueryException( - "fixed_delta must be set for smooth max divergence." - ) - epsilon = cost.epsilon(delta=query_json.fixed_delta) - delta = query_json.fixed_delta - case _: - raise InternalServerException( - f"Invalid measurement type: {measurement_type}" - ) - - return epsilon, delta
- - -
-[Doku] - def query(self, query_json: OpenDPInp) -> str: - """Perform the query and return the response. - - Args: - query_json (BaseModel): The JSON request object for the query. - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - - Returns: - TODO Check this, probably float or int. - str: The JSON encoded string representation of the query result. - """ - opendp_pipe = reconstruct_measurement_pipeline(query_json.opendp_json) - - input_data = self.private_dataset.get_pandas_df().to_csv( - header=False, index=False - ) - - try: - release_data = opendp_pipe(input_data) - except Exception as e: - LOG.exception(e) - raise ExternalLibraryException( - DPLibraries.OPENDP, - "Error executing query:" + str(e), - ) from e - - # Note: leaving this here, support for opendp_polars - # if isinstance(release_data, polars.dataframe.frame.DataFrame): - # release_data = release_data.write_json(file=None) - - return release_data
-
- - - -
-[Doku] -def is_measurement(value: dp.Measurement) -> bool: - """Check if the value is a measurement. - - Args: - value (dp.Measurement): The measurement to check. - - Returns: - bool: True if the value is a measurement, False otherwise. - """ - return isinstance(value, dp.Measurement)
- - - -
-[Doku] -def reconstruct_measurement_pipeline(pipeline: str) -> dp.Measurement: - """Reconstruct OpenDP pipeline from json representation. - - Args: - pipeline (str): The JSON string encoding of the pipeline. - - Raises: - InvalidQueryException: If the pipeline is not a measurement. - - Returns: - dp.Measurement: The reconstructed pipeline. - """ - opendp_pipe = make_load_json(pipeline) - - if not is_measurement(opendp_pipe): - e = ( - "The pipeline provided is not a measurement. " - + "It cannot be processed in this server." - ) - LOG.exception(e) - raise InvalidQueryException(e) - - return opendp_pipe
- - - -
-[Doku] -def get_output_measure(opendp_pipe: dp.Measurement) -> str: - """Get output measure type. - - Args: - opendp_pipe (dp.Measurement): Pipeline to get measure type. - - Raises: - InternalServerException: If the measure type is unknown. - - Returns: - str: One of :py:class:`OpenDPMeasurement`. - """ - output_type = opendp_pipe.output_distance_type - output_measure = opendp_pipe.output_measure - - if output_measure == dp.measures.fixed_smoothed_max_divergence( - T=output_type - ): - measurement = OpenDPMeasurement.FIXED_SMOOTHED_MAX_DIVERGENCE - elif output_measure == dp.measures.max_divergence(T=output_type): - measurement = OpenDPMeasurement.MAX_DIVERGENCE - elif output_measure == dp.measures.smoothed_max_divergence(T=output_type): - measurement = OpenDPMeasurement.SMOOTHED_MAX_DIVERGENCE - elif output_measure == dp.measures.zero_concentrated_divergence( - T=output_type - ): - measurement = OpenDPMeasurement.ZERO_CONCENTRATED_DIVERGENCE - else: - raise InternalServerException( - f"Unknown type of output measure divergence: {output_measure}" - ) - return measurement
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dp_queries/dp_libraries/smartnoise_sql.html b/html/de/_modules/lomas_server/dp_queries/dp_libraries/smartnoise_sql.html deleted file mode 100644 index 27ccf8b6..00000000 --- a/html/de/_modules/lomas_server/dp_queries/dp_libraries/smartnoise_sql.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - lomas_server.dp_queries.dp_libraries.smartnoise_sql — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Quellcode für lomas_server.dp_queries.dp_libraries.smartnoise_sql

-import pandas as pd
-from snsql import Mechanism, Privacy, Stat, from_connection
-
-from constants import MAX_NAN_ITERATION, STATS, DPLibraries
-from dp_queries.dp_querier import DPQuerier
-from private_dataset.private_dataset import PrivateDataset
-from utils.error_handler import ExternalLibraryException, InvalidQueryException
-from utils.input_models import SNSQLInp, SNSQLInpCost
-
-
-
-[Doku] -class SmartnoiseSQLQuerier(DPQuerier): - """ - Concrete implementation of the DPQuerier ABC for the SmartNoiseSQL library. - """ - - def __init__( - self, - private_dataset: PrivateDataset, - ) -> None: - """Initializer. - - Args: - private_dataset (PrivateDataset): Private dataset to query. - """ - super().__init__(private_dataset) - - # Reformat metadata - metadata = dict(self.private_dataset.get_metadata()) - metadata.update(metadata["columns"]) - del metadata["columns"] - self.snsql_metadata = {"": {"": {"df": metadata}}} - -
-[Doku] - def cost(self, query_json: SNSQLInpCost) -> tuple[float, float]: - """Estimate cost of query - - Args: - query_json (BaseModel): The JSON request object for the query. - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - - Returns: - tuple[float, float]: The tuple of costs, the first value - is the epsilon cost, the second value is the delta value. - """ - privacy = Privacy(epsilon=query_json.epsilon, delta=query_json.delta) - privacy = set_mechanisms(privacy, query_json.mechanisms) - - reader = from_connection( - self.private_dataset.get_pandas_df(), - privacy=privacy, - metadata=self.snsql_metadata, - ) - - try: - result = reader.get_privacy_cost(query_json.query_str) - except Exception as e: - raise ExternalLibraryException( - DPLibraries.SMARTNOISE_SQL, - "Error obtaining cost: " + str(e), - ) from e - - return result
- - -
-[Doku] - def query(self, query_json: SNSQLInp, nb_iter: int = 0) -> dict: - """Perform the query and return the response. - - Args: - query_json (BaseModel): The JSON request object for the query. - nb_iter (int, optional): Number of trials if output is Nan. - Defaults to 0. - - Raises: - ExternalLibraryException: For exceptions from libraries - external to this package. - InvalidQueryException: If the budget values are too small to - perform the query. - - Returns: - dict: The dictionary encoding of the resulting pd.DataFrame. - """ - epsilon, delta = query_json.epsilon, query_json.delta - - privacy = Privacy(epsilon=epsilon, delta=delta) - privacy = set_mechanisms(privacy, query_json.mechanisms) - - reader = from_connection( - self.private_dataset.get_pandas_df(), - privacy=privacy, - metadata=self.snsql_metadata, - ) - - try: - result = reader.execute( - query_json.query_str, postprocess=query_json.postprocess - ) - except Exception as e: - raise ExternalLibraryException( - DPLibraries.SMARTNOISE_SQL, - "Error executing query:" + str(e), - ) from e - - if not query_json.postprocess: - result = list(result) - cols = [f"res_{i}" for i in range(len(result))] - else: - cols = result.pop(0) - if result == []: - raise ExternalLibraryException( - DPLibraries.SMARTNOISE_SQL, - f"SQL Reader generated empty results," - f"Epsilon: {epsilon} and Delta: {delta} are too small" - " to generate output.", - ) - - df_res = pd.DataFrame(result, columns=cols) - - if df_res.isnull().values.any(): - # Try again up to MAX_NAN_ITERATION - if nb_iter < MAX_NAN_ITERATION: - nb_iter += 1 - return self.query(query_json, nb_iter) - - raise InvalidQueryException( - f"SQL Reader generated NAN results." - f" Epsilon: {epsilon} and Delta: {delta} are too small" - " to generate output.", - ) - - return df_res.to_dict(orient="tight")
-
- - - -
-[Doku] -def set_mechanisms(privacy: Privacy, mechanisms: dict[str, str]) -> Privacy: - """Set privacy mechanisms on the Privacy object. - - For more information see: - https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms - - Args: - privacy (Privacy): Privacy object. - mechanisms (dict[str, str]): Mechanisms to set. - - Returns: - Privacy: The updated Privacy object. - """ - for stat in STATS: - if stat in mechanisms.keys(): - privacy.mechanisms.map[Stat[stat]] = Mechanism[mechanisms[stat]] - return privacy
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dp_queries/dp_libraries/utils.html b/html/de/_modules/lomas_server/dp_queries/dp_libraries/utils.html deleted file mode 100644 index 8e06fccf..00000000 --- a/html/de/_modules/lomas_server/dp_queries/dp_libraries/utils.html +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - lomas_server.dp_queries.dp_libraries.utils — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.dp_queries.dp_libraries.utils

-from constants import DPLibraries
-from dp_queries.dp_libraries.opendp import OpenDPQuerier
-from dp_queries.dp_libraries.smartnoise_sql import SmartnoiseSQLQuerier
-from dp_queries.dp_querier import DPQuerier
-from private_dataset.private_dataset import PrivateDataset
-from utils.error_handler import InternalServerException
-
-
-
-[Doku] -def querier_factory(lib: str, private_dataset: PrivateDataset) -> DPQuerier: - """Builds the correct DPQuerier instance. - - Args: - lib (str): The library to build the querier for. - One of :py:class:`DPLibraries`. - private_dataset (PrivateDataset): The dataset to query. - - Raises: - InternalServerException: If the library is unknown. - - Returns: - DPQuerier: The built DPQuerier. - """ - match lib: - case DPLibraries.SMARTNOISE_SQL: - querier = SmartnoiseSQLQuerier(private_dataset) - - case DPLibraries.OPENDP: - querier = OpenDPQuerier(private_dataset) - - case _: - raise InternalServerException(f"Unknown library: {lib}") - return querier
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dp_queries/dp_logic.html b/html/de/_modules/lomas_server/dp_queries/dp_logic.html deleted file mode 100644 index 9dd16830..00000000 --- a/html/de/_modules/lomas_server/dp_queries/dp_logic.html +++ /dev/null @@ -1,364 +0,0 @@ - - - - - - lomas_server.dp_queries.dp_logic — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.dp_queries.dp_logic

-from pydantic import BaseModel
-
-from admin_database.admin_database import AdminDatabase
-from constants import DPLibraries
-from dataset_store.dataset_store import DatasetStore
-from dp_queries.dp_querier import DPQuerier
-from utils.error_handler import (
-    CUSTOM_EXCEPTIONS,
-    InternalServerException,
-    InvalidQueryException,
-    UnauthorizedAccessException,
-)
-
-
-
-[Doku] -class QueryHandler: - """ - Query handler for the server. - - Holds a reference to the admin database and the DatasetStore. - """ - - admin_database: AdminDatabase - dataset_store: DatasetStore - - def __init__( - self, admin_database: AdminDatabase, dataset_store: DatasetStore - ) -> None: - """Initializer. - - Args: - admin_database (AdminDatabase): An initialized instance of - an AdminDatabase. - dataset_store (DatasetStore): A dataset store. - """ - self.admin_database = admin_database - self.dataset_store = dataset_store - - def _get_querier( - self, - query_type: str, - query_json: BaseModel, - ) -> DPQuerier: - """Internal function to get a correct querier. - - Get the querier for the query_type and dataset_name in - the query_json. - - Args: - query_type (str): The type of DP library, one of - :py:class:DPLibraries` - query_json (BasicModel): The JSON request object for the query. - - Raises: - InternalServerException: If the query type does not exist. - InternalServerException: If the querier cannot be received from - the querier manager. - - Returns: - DPQuerier: The correct querier instance for the given - request and libray. - """ - # Check query type - supported_lib = [lib.value for lib in DPLibraries] - if query_type not in supported_lib: - raise InternalServerException( - f"Query type {query_type} not supported in QueryHandler" - ) - - # Get querier - try: - dp_querier = self.dataset_store.get_querier( - query_json.dataset_name, query_type - ) - except InvalidQueryException as e: - raise e - except Exception as e: - raise InternalServerException( - "Failed to get querier for dataset " - + f"{query_json.dataset_name}: {str(e)}" - ) from e - return dp_querier - -
-[Doku] - def estimate_cost( - self, - query_type: str, - query_json: BaseModel, - ) -> dict[str, float]: - """Estimate query cost. - - Args: - query_type (str): The type of DP library, - one of :py:class:DPLibraries` - query_json (BasicModel): The JSON request object for the query. - - Returns: - dict[str, float]: Dictionary containing: - - epsilon_cost (float): The estimated epsilon cost. - - delta_cost (float): The estimated delta cost. - """ - # Get querier - dp_querier = self._get_querier(query_type, query_json) - - # Get cost of the query - eps_cost, delta_cost = dp_querier.cost(query_json) - - return {"epsilon_cost": eps_cost, "delta_cost": delta_cost}
- - -
-[Doku] - def handle_query( - self, - query_type: str, - query_json: BaseModel, - user_name: str, - ) -> dict: - """ - Handle DP query. - - Args: - query_type (str): The type of DP library, - one of :py:class:DPLibraries` - query_json (BasicModel): The JSON request object for the query. - user_name (str, optional): User name. - - Raises: - UnauthorizedAccessException: A query is already - ongoing for this user, - the user does not exist or does not have access to the dataset. - InvalidQueryException: If the query is not valid. - InternalServerException: For any other unforseen exceptions. - - Returns: - dict: A dictionary containing: - - requested_by (str): The user name. - - query_response (pd.DataFrame): A DataFrame containing - the query response. - - spent_epsilon (float): The amount of epsilon budget spent - for the query. - - spent_delta (float): The amount of delta budget spent - for the query. - - """ - # Block access to other queries to user - if not self.admin_database.get_and_set_may_user_query( - user_name, False - ): - raise UnauthorizedAccessException( - f"User {user_name} is trying to query" - + " before end of previous query." - ) - - try: - # Get querier - dp_querier = self._get_querier(query_type, query_json) - - # Get cost of the query - eps_cost, delta_cost = dp_querier.cost(query_json) - - # Check that enough budget to do the query - try: - ( - eps_remain, - delta_remain, - ) = self.admin_database.get_remaining_budget( - user_name, query_json.dataset_name - ) - except UnauthorizedAccessException as e: - raise e - - if (eps_remain < eps_cost) or (delta_remain < delta_cost): - raise InvalidQueryException( - "Not enough budget for this query epsilon remaining " - f"{eps_remain}, delta remaining {delta_remain}." - ) - - # Query - try: - query_response = dp_querier.query(query_json) - except CUSTOM_EXCEPTIONS as e: - raise e - except Exception as e: - raise InternalServerException(e) from e - - # Deduce budget from user - self.admin_database.update_budget( - user_name, query_json.dataset_name, eps_cost, delta_cost - ) - response = { - "requested_by": user_name, - "query_response": query_response, - "spent_epsilon": eps_cost, - "spent_delta": delta_cost, - } - - # Add query to db (for archive) - self.admin_database.save_query(user_name, query_json, response) - - except Exception as e: - self.admin_database.set_may_user_query(user_name, True) - raise e - - # Re-enable user to query - self.admin_database.set_may_user_query(user_name, True) - - # Return response - return response
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dp_queries/dp_querier.html b/html/de/_modules/lomas_server/dp_queries/dp_querier.html deleted file mode 100644 index 1f06bcfa..00000000 --- a/html/de/_modules/lomas_server/dp_queries/dp_querier.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - lomas_server.dp_queries.dp_querier — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.dp_queries.dp_querier

-from abc import ABC, abstractmethod
-
-from pydantic import BaseModel
-
-from private_dataset.private_dataset import PrivateDataset
-
-
-
-[Doku] -class DPQuerier(ABC): - """ - Abstract Base Class for Queriers to external DP library. - - A querier type is specific to a DP library and - a querier instance is specific to a PrivateDataset instance. - """ - - def __init__( - self, - private_dataset: PrivateDataset, - ) -> None: - """Initialise with specific dataset - - Args: - private_dataset (PrivateDataset): The private dataset to query. - """ - self.private_dataset = private_dataset - -
-[Doku] - @abstractmethod - def cost(self, query_json: BaseModel) -> tuple[float, float]: - """ - Estimate cost of query. - - Args: - query_json (BaseModel): The JSON request object for the query. - - Returns: - tuple[float, float]: The tuple of costs, the first value is - the epsilon cost, the second value is the delta value. - """
- - -
-[Doku] - @abstractmethod - def query(self, query_json: BaseModel) -> str: - """ - Perform the query and return the response. - - Args: - query_json (BaseModel): The JSON request object for the query. - - Returns: - TODO check this. - str: The JSON encoded string representation of the query result. - """
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/dp_queries/dummy_dataset.html b/html/de/_modules/lomas_server/dp_queries/dummy_dataset.html deleted file mode 100644 index 4d8295e5..00000000 --- a/html/de/_modules/lomas_server/dp_queries/dummy_dataset.html +++ /dev/null @@ -1,309 +0,0 @@ - - - - - - lomas_server.dp_queries.dummy_dataset — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.dp_queries.dummy_dataset

-import datetime
-import random
-
-import numpy as np
-import pandas as pd
-
-from admin_database.admin_database import AdminDatabase
-from constants import (
-    DEFAULT_NUMERICAL_MAX,
-    DEFAULT_NUMERICAL_MIN,
-    DUMMY_NB_ROWS,
-    DUMMY_SEED,
-    NB_RANDOM_NONE,
-    RANDOM_DATE_RANGE,
-    RANDOM_DATE_START,
-    RANDOM_STRINGS,
-)
-from private_dataset.in_memory_dataset import InMemoryDataset
-from utils.error_handler import InternalServerException
-from utils.input_models import GetDummyDataset
-
-
-
-[Doku] -def make_dummy_dataset( - metadata: dict, nb_rows: int = DUMMY_NB_ROWS, seed: int = DUMMY_SEED -) -> pd.DataFrame: - """ - Create a dummy dataset based on a metadata dictionnary - - Args: - metadata (dict): dictionnary of the metadata of the real dataset - nb_rows (int, optional): _description_. Defaults to DUMMY_NB_ROWS. - seed (int, optional): _description_. Defaults to DUMMY_SEED. - - Raises: - InternalServerException: If any unknown column type occurs. - - Returns: - pd.DataFrame: dummy dataframe based on metadata - """ - # Setting seed - random.seed(seed) - np.random.seed(seed) - - # Create dataframe - df = pd.DataFrame() - for col_name, data in metadata["columns"].items(): - # Create a random serie based on the data type - match data["type"]: - case "string": - if "cardinality" in data.keys(): - cardinality = data["cardinality"] - if "categories" in data.keys(): - categories = data["categories"] - serie = pd.Series( - random.choices(categories, k=nb_rows) - ) - else: - serie = pd.Series( - random.choices( - RANDOM_STRINGS[:cardinality], k=nb_rows - ) - ) - else: - serie = pd.Series( - random.choices(RANDOM_STRINGS, k=nb_rows) - ) - case "boolean": - # type boolean instead of bool will allow null values - serie = pd.Series( - random.choices([True, False], k=nb_rows), dtype="boolean" - ) - case "int" | "float": - column_min = ( - data["lower"] - if "lower" in data.keys() - else DEFAULT_NUMERICAL_MIN - ) - column_max = ( - data["upper"] - if "upper" in data.keys() - else DEFAULT_NUMERICAL_MAX - ) - if data["type"] == "int": - # pd.Series to ensure consistency between different types - serie = pd.Series( - np.random.randint(column_min, column_max, size=nb_rows) - ) - else: - serie = pd.Series( - np.random.uniform(column_min, column_max, size=nb_rows) - ) - case "datetime": - # From start date and random on a range above - start = datetime.datetime.strptime( - RANDOM_DATE_START, "%m/%d/%Y" - ) - serie = pd.Series( - [ - start - + datetime.timedelta( - seconds=random.randrange(RANDOM_DATE_RANGE) - ) - for _ in range(nb_rows) - ] - ) - case "unknown": - # Unknown column are ignored by smartnoise sql - continue - case _: - raise InternalServerException( - f"unknown column type in metadata: \ - {data['type']} in column {col_name}" - ) - - # Add None value if the column is nullable - nullable = data["nullable"] if "nullable" in data.keys() else False - - if nullable: - # Get the indexes of 'serie' - indexes = serie.index.tolist() - for _ in range(0, NB_RANDOM_NONE): - index_to_insert = random.choice(indexes) - serie.at[index_to_insert] = None - - # Add randomly generated data as new column of dataframe - df[col_name] = serie - - return df
- - - -
-[Doku] -def get_dummy_dataset_for_query( - admin_database: AdminDatabase, query_json: GetDummyDataset -) -> InMemoryDataset: - """Get a dummy dataset for a given query. - - Args: - admin_database (AdminDatabase): An initialized instance - of AdminDatabase. - query_json (GetDummyDataset): The JSON request object for the query. - - - Returns: - InMemoryDataset: An in memory dummy dataset instance. - """ - # Create dummy dataset based on seed and number of rows - ds_metadata = admin_database.get_dataset_metadata(query_json.dataset_name) - ds_df = make_dummy_dataset( - ds_metadata, query_json.dummy_nb_rows, query_json.dummy_seed - ) - ds_private_dataset = InMemoryDataset(ds_metadata, ds_df) - - return ds_private_dataset
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/mongodb_admin.html b/html/de/_modules/lomas_server/mongodb_admin.html deleted file mode 100644 index f9a0f4d4..00000000 --- a/html/de/_modules/lomas_server/mongodb_admin.html +++ /dev/null @@ -1,1091 +0,0 @@ - - - - - - lomas_server.mongodb_admin — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.mongodb_admin

-import argparse
-import functools
-from typing import Callable, Dict, List, Optional, Union
-from warnings import warn
-
-import boto3
-import yaml
-from pymongo.database import Database
-from pymongo.results import _WriteResult
-
-from admin_database.mongodb_database import check_result_acknowledged
-from constants import PrivateDatabaseType
-from utils.collections_models import DatasetsCollection, UserCollection
-from utils.error_handler import InternalServerException
-from utils.loggr import LOG
-
-
-
-[Doku] -def check_user_exists(enforce_true: bool) -> Callable: - """Creates a wrapper function that raises a ValueError if the supplied - user does (not) exist in the user collection depending on the - enforce_true parameter. - - Args: - enforce_true (bool): If set to True, the wrapper will enforce - the user is already in the database. If set to False, it - will enforce the user is NOT in the database. - - Returns: - Callable: The wrapper function that enforces user presence - (or absence) before calling the suplied function. - """ - - def inner_func( - function: Callable[[Database, argparse.Namespace], None] - ) -> Callable: - @functools.wraps(function) - def wrapper_decorator( - *arguments: argparse.Namespace, **kwargs: Dict - ) -> None: - db = arguments[0] - user = arguments[1] - - user_count = db.users.count_documents({"user_name": user}) - - if enforce_true and user_count == 0: - raise ValueError( - f"User {user} does not exist in user collection" - ) - if not enforce_true and user_count > 0: - raise ValueError( - f"User {user} already exists in user collection" - ) - - return function(*arguments, **kwargs) # type: ignore - - return wrapper_decorator - - return inner_func
- - - -
-[Doku] -def check_user_has_dataset(enforce_true: bool) -> Callable: - """Creates a wrapper function that raises a ValueError if the supplied - user has access (or not) to the supplied dataset depending on the - enforce_true parameter. - - Args: - enforce_true (bool): If set to true, the wrapper function will - enforce the user has access to the dataset. If set to False, - the wrapper function will enforce the user has NOT access - to the specified dataset. - - Returns: - Callable: The wrapper function that asserts user access (or not) - to the provided dataset. - """ - - def inner_func( - function: Callable[[Database, argparse.Namespace], None] - ) -> Callable: - @functools.wraps(function) - def wrapper_decorator( - *arguments: argparse.Namespace, **kwargs: Dict - ) -> None: - db = arguments[0] - user = arguments[1] - dataset = arguments[2] - - user_and_ds_count = db.users.count_documents( - { - "user_name": user, - "datasets_list": {"$elemMatch": {"dataset_name": dataset}}, - } - ) - - if enforce_true and user_and_ds_count == 0: - raise ValueError( - f"User {user} does not have dataset {dataset}" - ) - if not enforce_true and user_and_ds_count > 0: - raise ValueError(f"User {user} already has dataset {dataset}") - - return function(*arguments, **kwargs) # type: ignore - - return wrapper_decorator - - return inner_func
- - - -
-[Doku] -def check_dataset_and_metadata_exist(enforce_true: bool) -> Callable: - """Creates a wrapper function that raises a ValueError - if the supplied user does not already exist in the user collection. - """ - - def inner_func( - function: Callable[[Database, argparse.Namespace], None] - ) -> Callable: - @functools.wraps(function) - def wrapper_decorator( - *arguments: argparse.Namespace, **kwargs: Dict - ) -> None: - db = arguments[0] - dataset = arguments[1] - - dataset_count = db.datasets.count_documents( - {"dataset_name": dataset} - ) - - if enforce_true and dataset_count == 0: - raise ValueError( - f"Dataset {dataset} does not exist in dataset collection" - ) - if not enforce_true and dataset_count > 0: - raise ValueError( - f"Dataset {dataset} already exists in dataset collection" - ) - - metadata_count = db.metadata.count_documents( - {dataset: {"$exists": True}} - ) - - if enforce_true and metadata_count == 0: - raise ValueError( - f"Metadata for dataset {dataset} does" - " not exist in metadata collection" - ) - if not enforce_true and metadata_count > 0: - raise ValueError( - f"Metadata for dataset {dataset} already" - " exists in metadata collection" - ) - - return function(*arguments, **kwargs) # type: ignore - - return wrapper_decorator - - return inner_func
- - - -########################## USERS ########################## # noqa: E266 -
-[Doku] -@check_user_exists(False) -def add_user(db: Database, user: str) -> None: - """Add new user in users collection with initial values for all fields - set by default. - - Args: - db (Database): mongo database object - user (str): username to be added - - Raises: - ValueError: If the user already exists. - WriteConcernError: If the result is not acknowledged. - - Returns: - None - """ - - res = db.users.insert_one( - { - "user_name": user, - "may_query": True, - "datasets_list": [], - } - ) - - check_result_acknowledged(res) - - LOG.info(f"Added user {user}.")
- - - -
-[Doku] -@check_user_exists(False) -def add_user_with_budget( - db: Database, user: str, dataset: str, epsilon: float, delta: float -) -> None: - """Add new user in users collection with initial values - for all fields set by default. - - Args: - db (Database): mongo database object - user (str): username to be added - dataset (str): name of the dataset to add to user - epsilon (float): epsilon value for initial budget of user - delta (float): delta value for initial budget of user - - Raises: - ValueError: _description_ - - Returns: - None - """ - res = db.users.insert_one( - { - "user_name": user, - "may_query": True, - "datasets_list": [ - { - "dataset_name": dataset, - "initial_epsilon": epsilon, - "initial_delta": delta, - "total_spent_epsilon": 0.0, - "total_spent_delta": 0.0, - } - ], - } - ) - - check_result_acknowledged(res) - - LOG.info( - f"Added access to user {user} with dataset {dataset}, " - + f"budget epsilon {epsilon} and delta {delta}." - )
- - - -
-[Doku] -@check_user_exists(True) -def del_user(db: Database, user: str) -> None: - """Delete all related information for user from the users collection. - - Args: - db (Database): mongo database object - user (str): username to be deleted - - Returns: - None - """ - res = db.users.delete_many({"user_name": user}) - check_result_acknowledged(res) - - LOG.info(f"Deleted user {user}.")
- - - -
-[Doku] -@check_user_exists(True) -@check_user_has_dataset(False) -def add_dataset_to_user( - db: Database, user: str, dataset: str, epsilon: float, delta: float -) -> None: - """Add dataset with initialized budget values to list of datasets - that the user has access to. - Will not add if already added (no error will be raised in that case). - - Args: - db (Database): mongo database object - user (str): username of the user to check - dataset (str): name of the dataset to add to user - epsilon (float): epsilon value for initial budget of user - delta (float): delta value for initial budget of user - - Raises: - ValueError: _description_ - - Returns: - None - """ - res = db.users.update_one( - { - "user_name": user, - "datasets_list.dataset_name": {"$ne": dataset}, - }, - { - "$push": { - "datasets_list": { - "dataset_name": dataset, - "initial_epsilon": epsilon, - "initial_delta": delta, - "total_spent_epsilon": 0.0, - "total_spent_delta": 0.0, - } - } - }, - ) - - check_result_acknowledged(res) - - LOG.info( - f"Added access to dataset {dataset}" - f" to user {user}" - f" with budget epsilon {epsilon}" - f" and delta {delta}." - )
- - - -
-[Doku] -@check_user_exists(True) -@check_user_has_dataset(True) -def del_dataset_to_user(db: Database, user: str, dataset: str) -> None: - """Remove if exists the dataset (and all related budget info) - from list of datasets that user has access to. - - Args: - db (Database): mongo database object - user (str): username of the user to which to delete a dataset - dataset (str): name of the dataset to remove from user - - Returns: - None - """ - res = db.users.update_one( - {"user_name": user}, - {"$pull": {"datasets_list": {"dataset_name": {"$eq": dataset}}}}, - ) - - check_result_acknowledged(res) - - LOG.info(f"Remove access to dataset {dataset}" + f" from user {user}.")
- - - -
-[Doku] -@check_user_exists(True) -@check_user_has_dataset(True) -def set_budget_field( - db: Database, user: str, dataset: str, field: str, value: float -) -> None: - """Set (for some reason) a budget field to a given value - if given user exists and has access to given dataset. - - Args: - db (Database): mongo database object - user (str): username of the user to set budget to - dataset (str): name of the dataset to set budget to - field (str): one of 'epsilon' or 'delta' - value (float): value to set as epsilon or delta - - Returns: - None - """ - res = db.users.update_one( - { - "user_name": user, - "datasets_list.dataset_name": dataset, - }, - {"$set": {f"datasets_list.$.{field}": value}}, - ) - - check_result_acknowledged(res) - - LOG.info( - f"Set budget of {user} for dataset {dataset}" - f" of {field} to {value}." - )
- - - -
-[Doku] -@check_user_exists(True) -def set_may_query(db: Database, user: str, value: bool) -> None: - """Set (for some reason) the 'may query' field to a given value - if given user exists. - - Args: - db (Database): mongo database object - user (str): username of the user to enable/disable - value (bool): may query value (True or False) - - Returns: - None - """ - res = db.users.update_one( - {"user_name": user}, - {"$set": {"may_query": (value == "True")}}, - ) - - check_result_acknowledged(res) - - LOG.info(f"Set user {user} may query to {value}.")
- - - -
-[Doku] -@check_user_exists(True) -def show_user(db: Database, user: str) -> dict: - """Show a user - - Args: - db (Database): mongo database object - user (str): username of the user to show - - Returns: - user (dict): all information of user from 'users' collection - """ - user_info = list(db.users.find({"user_name": user}))[0] - user_info.pop("_id", None) - LOG.info(user_info) - return user_info
- - - -
-[Doku] -def add_users_via_yaml( - db: Database, - yaml_file: Union[str, Dict], - clean: bool, - overwrite: bool, -) -> None: - """Add all users from yaml file to the user collection - - Args: - db (Database): mongo database object - yaml_file (Union[str, Dict]): - if str: a path to the YAML file location - if Dict: a dictionnary containing the collection data - clean (bool): boolean flag - True if drop current user collection - False if keep current user collection - overwrite (bool): boolean flag - True if overwrite already existing users - False errors if new values for already existing users - - Returns: - None - """ - if clean: - # Collection created from scratch - db.users.drop() - LOG.info("Cleaning done. \n") - - # Load yaml data and insert it - if isinstance(yaml_file, str): - with open(yaml_file, encoding="utf-8") as f: - yaml_file = yaml.safe_load(f) - user_dict = UserCollection(**yaml_file) - - # Filter out duplicates - new_users = [] - existing_users = [] - for user in user_dict.users: - if not db.users.find_one({"user_name": user.user_name}): - new_users.append(user) - else: - existing_users.append(user) - - # Overwrite values for existing user with values from yaml - if existing_users: - if overwrite: - for user in existing_users: - user_filter = {"user_name": user.user_name} - update_operation = {"$set": user.dict()} - res: _WriteResult = db.users.update_many( - user_filter, update_operation - ) - check_result_acknowledged(res) - LOG.info("Existing users updated. ") - else: - warn( - "Some users already present in database." - "Overwrite is set to False." - ) - - if new_users: - # Insert new users - new_users_dicts = [user.dict() for user in new_users] - res = db.users.insert_many(new_users_dicts) - check_result_acknowledged(res) - LOG.info("Added user data from yaml.") - else: - LOG.info("No new users added, they already exist in the server")
- - - -
-[Doku] -def show_archives_of_user(db: Database, user: str) -> List[dict]: # TODO test - """Show all previous queries from a user - - Args: - db (Database): mongo database object - user (str): username of the user to show archives - - Returns: - archives (List): list of previous queries from the user - """ - archives_infos: List[dict] = list( - db.queries_archives.find({"user_name": user}) - ) - LOG.info(archives_infos) - return archives_infos
- - - -
-[Doku] -def get_list_of_users(db: Database) -> list: # TODO test - """Get the list of all users is 'users' collection - - Args: - db (Database): mongo database object - - Returns: - user_names (list): list of names of all users - """ - user_names = [] - for elem in db.users.find(): - user_names.append(elem["user_name"]) - LOG.info(user_names) - return user_names
- - - -
-[Doku] -def get_list_of_datasets_from_user( - db: Database, user: str -) -> list: # TODO test - """Get the list of all datasets from the user - - Args: - db (Database): mongo database object - user (str): username of the user to show archives - - Returns: - user_datasets (list): list of names of all users - """ - user_data = db.users.find_one({"user_name": user}) - if user_data: - LOG.info( - [dataset["dataset_name"] for dataset in user_data["datasets_list"]] - ) - return [ - dataset["dataset_name"] for dataset in user_data["datasets_list"] - ] - LOG.info([]) - return []
- - - -################### DATASET TO DATABASE ################### # noqa: E266 -
-[Doku] -@check_dataset_and_metadata_exist(False) -def add_dataset( # pylint: disable=too-many-arguments, too-many-locals - db: Database, - dataset_name: str, - database_type: str, - metadata_database_type: str, - dataset_path: Optional[str] = "", - metadata_path: Optional[str] = "", - s3_bucket: Optional[str] = "", - s3_key: Optional[str] = "", - endpoint_url: Optional[str] = "", - aws_access_key_id: Optional[str] = "", - aws_secret_access_key: Optional[str] = "", - metadata_s3_bucket: Optional[str] = "", - metadata_s3_key: Optional[str] = "", - metadata_endpoint_url: Optional[str] = "", - metadata_aws_access_key_id: Optional[str] = "", - metadata_aws_secret_access_key: Optional[str] = "", -) -> None: - """Set a database type to a dataset in dataset collection. - - Args: - db (Database): mongo database object - dataset_name (str): Dataset name - database_type (str): Type of the database - metadata_database_type (str): Metadata database type - - dataset_path (str): Path to the dataset (for local db type) - metadata_path (str): Path to metadata (for local db type) - - s3_bucket (str): S3 bucket name - s3_key (str): S3 key - endpoint_url (str): S3 endpoint URL - aws_access_key_id (str): AWS access key ID - aws_secret_access_key (str): AWS secret access key - metadata_s3_bucket (str): Metadata S3 bucket name - metadata_s3_key (str): Metadata S3 key - metadata_endpoint_url (str): Metadata S3 endpoint URL - metadata_aws_access_key_id (str): Metadata AWS access key ID - metadata_aws_secret_access_key (str): Metadata AWS secret access key - - Raises: - ValueError: If the dataset already exists - or if the database type is unknown. - - Returns: - None - """ - if db.datasets.count_documents({"dataset_name": dataset_name}) > 0: - raise ValueError("Cannot add database because already set. ") - - # Step 1: Build dataset - dataset: Dict = { - "dataset_name": dataset_name, - "database_type": database_type, - } - - if database_type == PrivateDatabaseType.PATH: - dataset["dataset_path"] = dataset_path - elif database_type == PrivateDatabaseType.S3: - dataset["s3_bucket"] = s3_bucket - dataset["s3_key"] = s3_key - dataset["endpoint_url"] = endpoint_url - dataset["aws_access_key_id"] = aws_access_key_id - dataset["aws_secret_access_key"] = aws_secret_access_key - else: - raise ValueError(f"Unknown database type {database_type}") - - # Step 2: Build metadata - dataset["metadata"] = {"database_type": metadata_database_type} - if metadata_database_type == PrivateDatabaseType.PATH: - # Store metadata from yaml to metadata collection - with open(metadata_path, encoding="utf-8") as f: # type: ignore - metadata_dict = yaml.safe_load(f) - - dataset["metadata"]["metadata_path"] = metadata_path - - elif metadata_database_type == PrivateDatabaseType.S3: - client = boto3.client( - "s3", - endpoint_url=metadata_endpoint_url, - aws_access_key_id=metadata_aws_access_key_id, - aws_secret_access_key=metadata_aws_secret_access_key, - ) - response = client.get_object( - Bucket=metadata_s3_bucket, Key=metadata_s3_key - ) - try: - metadata_dict = yaml.safe_load(response["Body"]) - except yaml.YAMLError as e: - raise e - - dataset["metadata"]["s3_bucket"] = metadata_s3_bucket - dataset["metadata"]["s3_key"] = metadata_s3_key - dataset["metadata"]["endpoint_url"] = metadata_endpoint_url - dataset["metadata"]["aws_access_key_id"] = metadata_aws_access_key_id - dataset["metadata"][ - "aws_secret_access_key" - ] = metadata_aws_secret_access_key - - else: - raise ValueError(f"Unknown database type {metadata_database_type}") - - # Step 3: Insert into db - res = db.datasets.insert_one(dataset) - check_result_acknowledged(res) - res = db.metadata.insert_one({dataset_name: metadata_dict}) - check_result_acknowledged(res) - - LOG.info( - f"Added dataset {dataset_name} with database " - f"{database_type} and associated metadata." - )
- - - -
-[Doku] -def add_datasets_via_yaml( # pylint: disable=R0912, R0914, R0915 - db: Database, - yaml_file: Union[str, Dict], - clean: bool, - overwrite_datasets: bool, - overwrite_metadata: bool, -) -> None: - """Set all database types to datasets in dataset collection based - on yaml file. - - Args: - db (Database): mongo database object - yaml_file (Union[str, Dict]): - if str: a path to the YAML file location - if Dict: a dictionnary containing the collection data - clean (bool): Whether to clean the collection before adding. - overwrite_datasets (bool): Whether to overwrite existing datasets. - overwrite_metadata (bool): Whether to overwrite existing metadata. - - Raises: - ValueError: If there are errors in the YAML file format. - - Returns: - None - """ - if clean: - # Collection created from scratch - db.datasets.drop() - db.metadata.drop() - LOG.info("Cleaning done. \n") - - if isinstance(yaml_file, str): - with open(yaml_file, encoding="utf-8") as f: - yaml_file = yaml.safe_load(f) - dataset_dict = DatasetsCollection(**yaml_file) - - # Step 1: add datasets - new_datasets = [] - existing_datasets = [] - for d in dataset_dict.datasets: - # Fill datasets_list - if not db.datasets.find_one({"dataset_name": d.dataset_name}): - new_datasets.append(d) - else: - existing_datasets.append(d) - - # Overwrite values for existing dataset with values from yaml - if existing_datasets: - if overwrite_datasets: - for d in existing_datasets: - dataset_filter = {"dataset_name": d.dataset_name} - update_operation = {"$set": d.dict()} - res: _WriteResult = db.datasets.update_many( - dataset_filter, update_operation - ) - check_result_acknowledged(res) - LOG.info("Existing datasets updated with new collection") - else: - warn( - "Some datasets already present in database." - "Overwrite is set to False." - ) - - # Add dataset collection - if new_datasets: - new_datasets_dicts = [d.dict() for d in new_datasets] - res = db.datasets.insert_many(new_datasets_dicts) - check_result_acknowledged(res) - LOG.info("Added datasets collection from yaml.") - - # Step 2: add metadata collections (one metadata per dataset) - for d in dataset_dict.datasets: - dataset_name = d.dataset_name - metadata_db_type = d.metadata.database_type - - match metadata_db_type: - case PrivateDatabaseType.PATH: - with open(d.metadata.metadata_path, encoding="utf-8") as f: - metadata_dict = yaml.safe_load(f) - - case PrivateDatabaseType.S3: - client = boto3.client( - "s3", - endpoint_url=d.metadata.endpoint_url, - aws_access_key_id=d.metadata.aws_access_key_id, - aws_secret_access_key=d.metadata.aws_secret_access_key, - ) - response = client.get_object( - Bucket=d.metadata.s3_bucket, - Key=d.metadata.s3_key, - ) - try: - metadata_dict = yaml.safe_load(response["Body"]) - except yaml.YAMLError as e: - return e - - case _: - raise InternalServerException( - "Unknown metadata_db_type PrivateDatabaseType:" - + f"{metadata_db_type}" - ) - - # Overwrite or not depending on config if metadata already exists - metadata_filter = {dataset_name: metadata_dict} - metadata = db.metadata.find_one(metadata_filter) - - if metadata and overwrite_metadata: - LOG.info(f"Metadata updated for dataset : {dataset_name}.") - res = db.metadata.update_one( - metadata_filter, {"$set": {dataset_name: metadata_dict}} - ) - check_result_acknowledged(res) - elif metadata: - LOG.info( - "Metadata already exist. " - "Use the command -om to overwrite with new values." - ) - else: - res = db.metadata.insert_one({dataset_name: metadata_dict}) - check_result_acknowledged(res) - LOG.info(f"Added metadata of {dataset_name} dataset. ")
- - - -
-[Doku] -@check_dataset_and_metadata_exist(True) -def del_dataset(db: Database, dataset: str) -> None: - """Delete dataset from dataset collection. - - Args: - db (Database): mongo database object - dataset (str): Dataset name to be deleted - - Returns: - None - """ - res = db.datasets.delete_many({"dataset_name": dataset}) - check_result_acknowledged(res) - res = db.metadata.delete_many({dataset: {"$exists": True}}) - check_result_acknowledged(res) - LOG.info(f"Deleted dataset and metadata for {dataset}.")
- - - -
-[Doku] -def show_dataset(db: Database, dataset: str) -> dict: # TODO test - """Show a dataset from dataset collection. - - Args: - db (Database): mongo database object - dataset (str): name of the dataset to show - - Returns: - dataset_info (dict): informations about the dataset - """ - dataset_info = list(db.datasets.find({"dataset_name": dataset}))[0] - dataset_info.pop("_id", None) - LOG.info(dataset_info) - return dataset_info
- - - -
-[Doku] -def show_metadata_of_dataset(db: Database, dataset: str) -> dict: # TODO test - """Show a metadata from metadata collection. - - Args: - db (Database): mongo database object - dataset (str): name of the dataset of the metadata to show - - Returns: - metadata (dict): informations about the metadata - """ - # Retrieve the document containing metadata for the specified dataset - metadata_document = db.metadata.find_one({dataset: {"$exists": True}}) - - if metadata_document: - # Extract metadata for the specified dataset - metadata_info = metadata_document[dataset] - LOG.info(metadata_info) - return metadata_info - - raise ValueError(f"No metadata found for dataset: {dataset}")
- - - -
-[Doku] -def get_list_of_datasets(db: Database) -> list: # TODO test - """Get the list of all dataset is 'datasets' collection - - Args: - db (Database): mongo database object - - Returns: - dataset_names (list): list of names of all datasets - """ - dataset_names = [] - for elem in db.datasets.find(): - dataset_names.append(elem["dataset_name"]) - LOG.info(dataset_names) - return dataset_names
- - - -####################### COLLECTIONS ####################### # noqa: E266 -
-[Doku] -def drop_collection(db: Database, collection: str) -> None: - """Delete collection. - - Args: - db (Database): mongo database object - collection (str): Collection name to be deleted. - - Returns: - None - """ - db.drop_collection(collection) - LOG.info(f"Deleted collection {collection}.")
- - - -
-[Doku] -def show_collection(db: Database, collection: str) -> list: - """Show a collection - - Args: - db (Database): mongo database object - collection (str): Collection name to be shown. - - Returns: - None - """ - collection_query = db[collection].find({}) - if not collection_query: - return [] - - collections = [] - for document in collection_query: - document.pop("_id", None) - collections.append(document) - LOG.info(collections) - return collections
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/private_dataset/in_memory_dataset.html b/html/de/_modules/lomas_server/private_dataset/in_memory_dataset.html deleted file mode 100644 index 7a17a148..00000000 --- a/html/de/_modules/lomas_server/private_dataset/in_memory_dataset.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - lomas_server.private_dataset.in_memory_dataset — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Quellcode für lomas_server.private_dataset.in_memory_dataset

-from typing import Dict, Union
-
-import pandas as pd
-
-from private_dataset.private_dataset import PrivateDataset
-
-
-
-[Doku] -class InMemoryDataset(PrivateDataset): - """ - PrivateDataset for a dataset created from an in-memory pandas DataFrame. - """ - - def __init__( - self, - metadata: Dict[str, Union[int, bool, Dict[str, Union[str, int]]]], - dataset_df: pd.DataFrame, - ) -> None: - """Initializer. - - Args: - metadata (Dict[str, Union[int, bool, Dict[str, Union[str, int]]]]): - Metadata dictionary. - dataset_df (pd.DataFrame): Dataframe of the dataset - """ - super().__init__(metadata) - self.df = dataset_df.copy() - -
-[Doku] - def get_pandas_df(self) -> pd.DataFrame: - """Get the data in pandas dataframe format - - Returns: - pd.DataFrame: pandas dataframe of dataset (a copy) - """ - # We use a copy here for safety. - return self.df.copy()
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/private_dataset/path_dataset.html b/html/de/_modules/lomas_server/private_dataset/path_dataset.html deleted file mode 100644 index 7fd93879..00000000 --- a/html/de/_modules/lomas_server/private_dataset/path_dataset.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - lomas_server.private_dataset.path_dataset — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.private_dataset.path_dataset

-from typing import Dict, Optional, Union
-
-import pandas as pd
-
-from private_dataset.private_dataset import PrivateDataset
-from utils.error_handler import InternalServerException, InvalidQueryException
-
-
-
-[Doku] -class PathDataset(PrivateDataset): - """ - PrivateDataset for dataset located at constant path. - - Path can be local or remote (http). - """ - - def __init__( - self, - metadata: Dict[str, Union[int, bool, Dict[str, Union[str, int]]]], - dataset_path: str, - ) -> None: - """Initializer. Does not load the dataset in memory yet. - - Args: - metadata (Dict[str, Union[int, bool, Dict[str, Union[str, int]]]]): - The metadata dictionary. - dataset_path (str): path of the dataset (local or remote). - """ - super().__init__(metadata) - self.ds_path: str = dataset_path - self.df: Optional[pd.DataFrame] = None - -
-[Doku] - def get_pandas_df(self) -> pd.DataFrame: - """Get the data in pandas dataframe format - - Raises: - InternalServerException: If the file format is not supported. - - Returns: - pd.DataFrame: pandas dataframe of dataset - """ - if self.df is None: - if self.ds_path.endswith(".csv"): - try: - self.df = pd.read_csv(self.ds_path, dtype=self.dtypes) - except Exception as err: - raise InternalServerException( - "Error reading csv at http path:" - f"{self.ds_path}: {err}", - ) from err - else: - return InvalidQueryException( - "File type other than .csv not supported for" - "loading into pandas DataFrame." - ) - - # Notify observer since memory usage has changed - for observer in self.dataset_observers: - observer.update_memory_usage() - - return self.df
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/private_dataset/private_dataset.html b/html/de/_modules/lomas_server/private_dataset/private_dataset.html deleted file mode 100644 index c433655c..00000000 --- a/html/de/_modules/lomas_server/private_dataset/private_dataset.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - - - lomas_server.private_dataset.private_dataset — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.private_dataset.private_dataset

-from abc import ABC, abstractmethod
-from typing import List, Optional
-
-import pandas as pd
-
-from dataset_store.private_dataset_observer import PrivateDatasetObserver
-
-
-
-[Doku] -class PrivateDataset(ABC): - """ - Overall access to sensitive data - """ - - df: Optional[pd.DataFrame] = None - - def __init__(self, metadata: dict) -> None: - """Initializer. - - Args: - metadata (dict): The metadata for this dataset - """ - self.metadata: dict = metadata - self.dataset_observers: List[PrivateDatasetObserver] = [] - self.dtypes: dict = _get_dtypes(metadata) - -
-[Doku] - @abstractmethod - def get_pandas_df(self, dataset_name: str) -> pd.DataFrame: - """Get the data in pandas dataframe format - - Args: - dataset_name (str): name of the private dataset - - Returns: - pd.DataFrame: The pandas dataframe for this dataset. - """
- - -
-[Doku] - def get_metadata(self) -> dict: - """Get the metadata for this dataset - - Returns: - dict: The metadata dictionary. - """ - return self.metadata
- - -
-[Doku] - def get_memory_usage(self) -> int: - """Returns the memory usage of this dataset, in MiB. - - The number returned only takes into account the memory usage - of the pandas DataFrame "cached" in the instance. - - Returns: - int: The memory usage, in MiB. - """ - if self.df is None: - return 0 - return self.df.memory_usage().sum() / (1024**2)
- - -
-[Doku] - def subscribe_for_memory_usage_updates( - self, dataset_observer: PrivateDatasetObserver - ) -> None: - """Add the PrivateDatasetObserver to the list of dataset_observers. - - Args: - dataset_observer (PrivateDatasetObserver): - The observer of this dataset. - """ - self.dataset_observers.append(dataset_observer)
-
- - - -def _get_dtypes(metadata: dict) -> dict: - """Extract and return the column types from the metadata. - - Args: - metadata (dict): The metadata dictionary. - - Returns: - dict: The dictionary of the column type. - """ - dtypes = {} - for col_name, data in metadata["columns"].items(): - dtypes[col_name] = data["type"] - return dtypes -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/private_dataset/s3_dataset.html b/html/de/_modules/lomas_server/private_dataset/s3_dataset.html deleted file mode 100644 index 6a3033f3..00000000 --- a/html/de/_modules/lomas_server/private_dataset/s3_dataset.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - lomas_server.private_dataset.s3_dataset — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.private_dataset.s3_dataset

-from typing import Optional
-
-import boto3
-import pandas as pd
-
-from private_dataset.private_dataset import PrivateDataset
-from utils.error_handler import InternalServerException
-
-
-
-[Doku] -class S3Dataset(PrivateDataset): - """ - PrivateDataset for dataset in S3 storage. - """ - - def __init__( - self, - metadata: dict, - s3_parameters: dict, - ) -> None: - """Initializer. Does not load the dataset yet. - - Args: - metadata (dict): The metadata dictionary. - s3_parameters (dict): informations to access metadata - """ - super().__init__(metadata) - - self.client = boto3.client( - "s3", - endpoint_url=s3_parameters["s3_endpoint"], - aws_access_key_id=s3_parameters["s3_aws_access_key_id"], - aws_secret_access_key=s3_parameters["aws_secret_access_key"], - ) - self.s3_bucket: str = s3_parameters["s3_bucket"] - self.s3_key: str = s3_parameters["s3_key"] - self.df: Optional[pd.DataFrame] = None - -
-[Doku] - def get_pandas_df(self) -> pd.DataFrame: - """Get the data in pandas dataframe format - - Raises: - InternalServerException: If the dataset cannot be read. - - Returns: - pd.DataFrame: pandas dataframe of dataset - """ - if self.df is None: - obj = self.client.get_object( - Bucket=self.s3_bucket, Key=self.s3_key - ) - try: - self.df = pd.read_csv(obj["Body"], dtype=self.dtypes) - except Exception as err: - raise InternalServerException( - "Error reading csv at s3 path:" - + f"{self.s3_bucket}/{self.s3_key}: {err}" - ) from err - - # Notify observer since memory usage has changed - for observer in self.dataset_observers: - observer.update_memory_usage() - - return self.df
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/private_dataset/utils.html b/html/de/_modules/lomas_server/private_dataset/utils.html deleted file mode 100644 index b5191b52..00000000 --- a/html/de/_modules/lomas_server/private_dataset/utils.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - lomas_server.private_dataset.utils — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.private_dataset.utils

-from admin_database.admin_database import AdminDatabase
-from constants import PrivateDatabaseType
-from private_dataset.path_dataset import PathDataset
-from private_dataset.private_dataset import PrivateDataset
-from private_dataset.s3_dataset import S3Dataset
-from utils.error_handler import InternalServerException
-
-
-
-[Doku] -def private_dataset_factory( - dataset_name: str, admin_database: AdminDatabase -) -> PrivateDataset: - """ - Returns the appropriate dataset class based on dataset storage location - - Args: - dataset_name (str): The dataset name. - admin_database (AdminDatabase): An initialized instance - of AdminDatabase. - - Raises: - InternalServerException: If the dataset type does not exist. - - Returns: - PrivateDataset: The PrivateDataset instance for this dataset. - """ - database_type = admin_database.get_dataset_field( - dataset_name, "database_type" - ) - - ds_metadata = admin_database.get_dataset_metadata(dataset_name) - - match database_type: - case PrivateDatabaseType.PATH: - dataset_path = admin_database.get_dataset_field( - dataset_name, "dataset_path" - ) - private_db = PathDataset(ds_metadata, dataset_path) - case PrivateDatabaseType.S3: - s3_parameters = {} - s3_parameters["s3_bucket"] = admin_database.get_dataset_field( - dataset_name, "s3_bucket" - ) - s3_parameters["s3_key"] = admin_database.get_dataset_field( - dataset_name, "s3_key" - ) - s3_parameters["s3_endpoint"] = admin_database.get_dataset_field( - dataset_name, "endpoint_url" - ) - s3_parameters["s3_aws_access_key_id"] = ( - admin_database.get_dataset_field( - dataset_name, "aws_access_key_id" - ) - ) - s3_parameters["aws_secret_access_key"] = ( - admin_database.get_dataset_field( - dataset_name, "aws_secret_access_key" - ) - ) - private_db = S3Dataset(ds_metadata, s3_parameters) - case _: - raise InternalServerException( - f"Unknown database type: {database_type}" - ) - - return private_db
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/tests/test_api.html b/html/de/_modules/lomas_server/tests/test_api.html deleted file mode 100644 index a2c3f39b..00000000 --- a/html/de/_modules/lomas_server/tests/test_api.html +++ /dev/null @@ -1,795 +0,0 @@ - - - - - - lomas_server.tests.test_api — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.tests.test_api

-import json
-import os
-import unittest
-from io import StringIO
-
-import pandas as pd
-from fastapi import status
-from fastapi.testclient import TestClient
-from pymongo.database import Database
-
-from admin_database.utils import get_mongodb
-from mongodb_admin import (
-    add_datasets_via_yaml,
-    add_users_via_yaml,
-    drop_collection,
-)
-from app import app
-from constants import EPSILON_LIMIT, DPLibraries
-from tests.constants import ENV_MONGO_INTEGRATION
-from utils.config import CONFIG_LOADER
-from utils.example_inputs import (
-    DUMMY_NB_ROWS,
-    PENGUIN_DATASET,
-    SMARTNOISE_QUERY_DELTA,
-    SMARTNOISE_QUERY_EPSILON,
-    example_dummy_opendp,
-    example_dummy_smartnoise_sql,
-    example_get_admin_db_data,
-    example_get_dummy_dataset,
-    example_opendp,
-    example_smartnoise_sql,
-    example_smartnoise_sql_cost,
-)
-
-INITAL_EPSILON = 10
-INITIAL_DELTA = 0.005
-
-
-
-[Doku] -class TestRootAPIEndpoint(unittest.TestCase): - """ - End-to-end tests of the api endpoints. - - This test can be both executed as an integration test - (enabled by setting LOMAS_TEST_MONGO_INTEGRATION to True), - or a standard test. The first requires a mongodb to be started - before running while the latter will use a local YamlDatabase. - """ - -
-[Doku] - @classmethod - def setUpClass(cls) -> None: - # Read correct config depending on the database we test against - if os.getenv(ENV_MONGO_INTEGRATION, "0").lower() in ("true", "1", "t"): - CONFIG_LOADER.load_config( - config_path="tests/test_configs/test_config_mongo.yaml", - secrets_path="tests/test_configs/test_secrets.yaml", - ) - else: - CONFIG_LOADER.load_config( - config_path="tests/test_configs/test_config.yaml", - secrets_path="tests/test_configs/test_secrets.yaml", - )
- - -
-[Doku] - @classmethod - def tearDownClass(cls) -> None: - pass
- - -
-[Doku] - def setUp(self) -> None: - """_summary_""" - self.user_name = "Dr. Antartica" - self.dataset = PENGUIN_DATASET - self.headers = { - "Content-type": "application/json", - "Accept": "*/*", - } - self.headers["user-name"] = self.user_name - - # Fill up database if needed - if os.getenv(ENV_MONGO_INTEGRATION, "0").lower() in ("true", "1", "t"): - self.db: Database = get_mongodb() - - add_users_via_yaml( - self.db, - yaml_file="tests/test_data/test_user_collection.yaml", - clean=True, - overwrite=True, - ) - add_datasets_via_yaml( - self.db, - yaml_file="tests/test_data/test_datasets.yaml", - clean=True, - overwrite_datasets=True, - overwrite_metadata=True, - )
- - -
-[Doku] - def tearDown(self) -> None: - # Clean up database if needed - if os.getenv(ENV_MONGO_INTEGRATION, "0").lower() in ("true", "1", "t"): - drop_collection(self.db, "metadata") - drop_collection(self.db, "datasets") - drop_collection(self.db, "users") - drop_collection(self.db, "queries_archives")
- - -
-[Doku] - def test_state(self) -> None: - """_summary_""" - with TestClient(app, headers=self.headers) as client: - response = client.get("/state", headers=self.headers) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["requested_by"] == self.user_name - assert response_dict["state"]["LIVE"]
- - -
-[Doku] - def test_get_dataset_metadata(self) -> None: - """_summary_""" - with TestClient(app) as client: - # Expect to work - response = client.post( - "/get_dataset_metadata", - json=example_get_admin_db_data, - headers=self.headers, - ) - assert response.status_code == status.HTTP_200_OK - - metadata = json.loads(response.content.decode("utf8")) - assert isinstance(metadata, dict), "metadata should be a dict" - assert "max_ids" in metadata, "max_ids should be in metadata" - assert "row_privacy" in metadata, "max_ids should be in metadata" - assert "columns" in metadata, "columns should be in metadata" - - # Expect to fail: dataset does not exist - fake_dataset = "I_do_not_exist" - response = client.post( - "/get_dataset_metadata", - json={"dataset_name": fake_dataset}, - headers=self.headers, - ) - assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json() == { - "InvalidQueryException": f"Dataset {fake_dataset} does not " - + "exists. Please, verify the client object initialisation." - }
- - -
-[Doku] - def test_get_dummy_dataset(self) -> None: - """_summary_""" - with TestClient(app) as client: - # Expect to work - response = client.post( - "/get_dummy_dataset", json=example_get_dummy_dataset - ) - assert response.status_code == status.HTTP_200_OK - - data = response.content.decode("utf8") - df = pd.read_csv(StringIO(data)) - assert isinstance( - df, pd.DataFrame - ), "Response should be a pd.DataFrame" - assert ( - df.shape[0] == DUMMY_NB_ROWS - ), "Dummy pd.DataFrame does not have expected number of rows" - - # Expect to fail: dataset does not exist - fake_dataset = "I_do_not_exist" - response = client.post( - "/get_dummy_dataset", - json={ - "dataset_name": fake_dataset, - "dummy_nb_rows": DUMMY_NB_ROWS, - "dummy_seed": 0, - }, - headers=self.headers, - ) - assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json() == { - "InvalidQueryException": f"Dataset {fake_dataset} does not " - + "exists. Please, verify the client object initialisation." - } - - # Expect to fail: missing argument dummy_nb_rows - response = client.post( - "/get_dummy_dataset", - json={ - "dataset_name": PENGUIN_DATASET, - }, - headers=self.headers, - ) - assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
- - -
-[Doku] - def test_smartnoise_query(self) -> None: - """_summary_""" - with TestClient(app, headers=self.headers) as client: - # Expect to work - response = client.post( - "/smartnoise_query", - json=example_smartnoise_sql, - headers=self.headers, - ) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["requested_by"] == self.user_name - assert response_dict["query_response"]["columns"] == ["NB_ROW"] - assert response_dict["query_response"]["data"][0][0] > 0 - assert response_dict["spent_epsilon"] == SMARTNOISE_QUERY_EPSILON - assert response_dict["spent_delta"] >= SMARTNOISE_QUERY_DELTA - - # Expect to fail: missing parameters: delta and mechanisms - response = client.post( - "/smartnoise_query", - json={ - "query_str": "SELECT COUNT(*) AS NB_ROW FROM df", - "dataset_name": PENGUIN_DATASET, - "epsilon": SMARTNOISE_QUERY_EPSILON, - "postprocess": True, - }, - headers=self.headers, - ) - assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - - response_dict = json.loads(response.content.decode("utf8"))[ - "detail" - ] - assert response_dict[0]["type"] == "missing" - assert response_dict[0]["loc"] == ["body", "delta"] - assert response_dict[1]["type"] == "missing" - assert response_dict[1]["loc"] == ["body", "mechanisms"] - - # Expect to fail: query does not make sense - input_smartnoise = dict(example_smartnoise_sql) - input_smartnoise["query_str"] = ( - "SELECT AVG(bill) FROM df" # no 'bill' column - ) - response = client.post( - "/smartnoise_query", - json=input_smartnoise, - headers=self.headers, - ) - assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert response.json() == { - "ExternalLibraryException": "Error obtaining cost: " - + "Column cannot be found bill", - "library": "smartnoise_sql", - } - - # Expect to fail: dataset without access - input_smartnoise = dict(example_smartnoise_sql) - input_smartnoise["dataset_name"] = "IRIS" - response = client.post( - "/smartnoise_query", - json=input_smartnoise, - headers=self.headers, - ) - assert response.status_code == status.HTTP_403_FORBIDDEN - assert response.json() == { - "UnauthorizedAccessException": "" - + "Dr. Antartica does not have access to IRIS." - } - - # Expect to fail: dataset does not exist - input_smartnoise = dict(example_smartnoise_sql) - input_smartnoise["dataset_name"] = "I_do_not_exist" - response = client.post( - "/smartnoise_query", - json=input_smartnoise, - headers=self.headers, - ) - assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json() == { - "InvalidQueryException": "" - + "Dataset I_do_not_exist does not exists. " - + "Please, verify the client object initialisation." - }
- - -
-[Doku] - def test_dummy_smartnoise_query(self) -> None: - """_summary_""" - with TestClient(app) as client: - # Expect to work - response = client.post( - "/dummy_smartnoise_query", json=example_dummy_smartnoise_sql - ) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["query_response"]["columns"] == ["res_0"] - assert response_dict["query_response"]["data"][0][0] > 0 - assert response_dict["query_response"]["data"][0][0] < 200
- - -
-[Doku] - def test_smartnoise_cost(self) -> None: - """_summary_""" - with TestClient(app) as client: - # Expect to work - response = client.post( - "/estimate_smartnoise_cost", json=example_smartnoise_sql_cost - ) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["epsilon_cost"] == SMARTNOISE_QUERY_EPSILON - assert response_dict["delta_cost"] > SMARTNOISE_QUERY_DELTA
- - -
-[Doku] - def test_opendp_query(self) -> None: - """_summary_""" - with TestClient(app, headers=self.headers) as client: - # Expect to work - response = client.post( - "/opendp_query", - json=example_opendp, - ) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["requested_by"] == self.user_name - assert response_dict["query_response"] > 0 - assert response_dict["spent_epsilon"] > 0.1 - assert response_dict["spent_delta"] == 0 - - # Expect to fail: transormation instead of measurement - trans = '{"version": "0.8.0", "ast": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "constructor", "func": "make_chain_tt", "module": "combinators", "args": [{"_type": "constructor", "func": "make_select_column", "module": "transformations", "kwargs": {"key": "bill_length_mm", "TOA": "String"}}, {"_type": "constructor", "func": "make_split_dataframe", "module": "transformations", "kwargs": {"separator": ",", "col_names": {"_type": "list", "_items": ["species", "island", "bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g", "sex"]}}}]}, "rhs": {"_type": "constructor", "func": "then_cast_default", "module": "transformations", "kwargs": {"TOA": "f64"}}}, "rhs": {"_type": "constructor", "func": "then_clamp", "module": "transformations", "kwargs": {"bounds": [30.0, 65.0]}}}, "rhs": {"_type": "constructor", "func": "then_resize", "module": "transformations", "kwargs": {"size": 346, "constant": 43.61}}}, "rhs": {"_type": "constructor", "func": "then_variance", "module": "transformations"}}}' # noqa: E501 # pylint: disable=C0301 - response = client.post( - "/opendp_query", - json={ - "dataset_name": PENGUIN_DATASET, - "opendp_json": trans, - }, - ) - assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json() == { - "InvalidQueryException": "The pipeline provided is not a " - + "measurement. It cannot be processed in this server." - }
- - -
-[Doku] - def test_dummy_opendp_query(self) -> None: - """_summary_""" - with TestClient(app) as client: - # Expect to work - response = client.post( - "/dummy_opendp_query", json=example_dummy_opendp - ) - assert response.status_code == status.HTTP_200_OK - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["query_response"] > 0
- - -
-[Doku] - def test_opendp_cost(self) -> None: - """_summary_""" - with TestClient(app) as client: - # Expect to work - response = client.post( - "/estimate_opendp_cost", json=example_opendp - ) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["epsilon_cost"] > 0.1 - assert response_dict["delta_cost"] == 0
- - -
-[Doku] - def test_get_initial_budget(self) -> None: - """_summary_""" - with TestClient(app, headers=self.headers) as client: - # Expect to work - response = client.post( - "/get_initial_budget", json=example_get_admin_db_data - ) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["initial_epsilon"] == INITAL_EPSILON - assert response_dict["initial_delta"] == INITIAL_DELTA - - # Query to spend budget - _ = client.post( - "/smartnoise_query", - json=example_smartnoise_sql, - headers=self.headers, - ) - - # Response should stay the same - response_2 = client.post( - "/get_initial_budget", json=example_get_admin_db_data - ) - assert response_2.status_code == status.HTTP_200_OK - response_dict_2 = json.loads(response_2.content.decode("utf8")) - assert response_dict_2 == response_dict
- - -
-[Doku] - def test_get_total_spent_budget(self) -> None: - """_summary_""" - with TestClient(app, headers=self.headers) as client: - # Expect to work - response = client.post( - "/get_total_spent_budget", json=example_get_admin_db_data - ) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["total_spent_epsilon"] == 0 - assert response_dict["total_spent_delta"] == 0 - - # Query to spend budget - _ = client.post( - "/smartnoise_query", - json=example_smartnoise_sql, - headers=self.headers, - ) - - # Response should have updated spent budget - response_2 = client.post( - "/get_total_spent_budget", json=example_get_admin_db_data - ) - assert response_2.status_code == status.HTTP_200_OK - - response_dict_2 = json.loads(response_2.content.decode("utf8")) - assert response_dict_2 != response_dict - assert ( - response_dict_2["total_spent_epsilon"] - == SMARTNOISE_QUERY_EPSILON - ) - assert ( - response_dict_2["total_spent_delta"] >= SMARTNOISE_QUERY_DELTA - )
- - -
-[Doku] - def test_get_remaining_budget(self) -> None: - """_summary_""" - with TestClient(app, headers=self.headers) as client: - # Expect to work - response = client.post( - "/get_remaining_budget", json=example_get_admin_db_data - ) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["remaining_epsilon"] == INITAL_EPSILON - assert response_dict["remaining_delta"] == INITIAL_DELTA - - # Query to spend budget - _ = client.post( - "/smartnoise_query", - json=example_smartnoise_sql, - headers=self.headers, - ) - - # Response should have removed spent budget - response_2 = client.post( - "/get_remaining_budget", json=example_get_admin_db_data - ) - assert response_2.status_code == status.HTTP_200_OK - - response_dict_2 = json.loads(response_2.content.decode("utf8")) - assert response_dict_2 != response_dict - assert ( - response_dict_2["remaining_epsilon"] - == INITAL_EPSILON - SMARTNOISE_QUERY_EPSILON - ) - assert ( - response_dict_2["remaining_delta"] - <= INITIAL_DELTA - SMARTNOISE_QUERY_DELTA - )
- - -
-[Doku] - def test_get_previous_queries(self) -> None: - """_summary_""" - with TestClient(app, headers=self.headers) as client: - # Expect to work - response = client.post( - "/get_previous_queries", json=example_get_admin_db_data - ) - assert response.status_code == status.HTTP_200_OK - - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["previous_queries"] == [] - - # Query to archive 1 (smartnoise) - query_res = client.post( - "/smartnoise_query", - json=example_smartnoise_sql, - headers=self.headers, - ) - query_res = json.loads(query_res.content.decode("utf8")) - - # Response should have one element in list - response_2 = client.post( - "/get_previous_queries", json=example_get_admin_db_data - ) - assert response_2.status_code == status.HTTP_200_OK - - response_dict_2 = json.loads(response_2.content.decode("utf8")) - assert len(response_dict_2["previous_queries"]) == 1 - assert ( - response_dict_2["previous_queries"][0]["dp_librairy"] - == DPLibraries.SMARTNOISE_SQL - ) - assert ( - response_dict_2["previous_queries"][0]["client_input"] - == example_smartnoise_sql - ) - assert ( - response_dict_2["previous_queries"][0]["response"] == query_res - ) - - # Query to archive 2 (opendp) - query_res = client.post( - "/opendp_query", - json=example_opendp, - ) - query_res = json.loads(query_res.content.decode("utf8")) - - # Response should have two elements in list - response_3 = client.post( - "/get_previous_queries", json=example_get_admin_db_data - ) - assert response_3.status_code == status.HTTP_200_OK - - response_dict_3 = json.loads(response_3.content.decode("utf8")) - assert len(response_dict_3["previous_queries"]) == 2 - assert ( - response_dict_3["previous_queries"][0] - == response_dict_2["previous_queries"][0] - ) - assert ( - response_dict_3["previous_queries"][1]["dp_librairy"] - == DPLibraries.OPENDP - ) - assert ( - response_dict_3["previous_queries"][1]["client_input"] - == example_opendp - ) - assert ( - response_dict_3["previous_queries"][1]["response"] == query_res - )
- - -
-[Doku] - def test_budget_over_limit(self) -> None: - """_summary_""" - with TestClient(app, headers=self.headers) as client: - # Should fail: too much budget on one go - smartnoise_body = dict(example_smartnoise_sql) - smartnoise_body["epsilon"] = EPSILON_LIMIT * 2 - - response = client.post( - "/smartnoise_query", - json=smartnoise_body, - headers=self.headers, - ) - - assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - error = response.json()["detail"][0] - assert error["type"] == "less_than_equal" - assert error["loc"] == ["body", "epsilon"] - assert error["msg"] == "Input should be less than or equal to 5"
- - -
-[Doku] - def test_subsequent_budget_limit_logic(self) -> None: - """_summary_""" - with TestClient(app, headers=self.headers) as client: - # Should fail: too much budget after three queries - smartnoise_body = dict(example_smartnoise_sql) - smartnoise_body["epsilon"] = 4.0 - - # spend 4.0 (total_spent = 4.0 <= INTIAL_BUDGET = 10.0) - response = client.post( - "/smartnoise_query", - json=smartnoise_body, - headers=self.headers, - ) - assert response.status_code == status.HTTP_200_OK - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["requested_by"] == self.user_name - - # spend 2*4.0 (total_spent = 8.0 <= INTIAL_BUDGET = 10.0) - response = client.post( - "/smartnoise_query", - json=smartnoise_body, - headers=self.headers, - ) - assert response.status_code == status.HTTP_200_OK - response_dict = json.loads(response.content.decode("utf8")) - assert response_dict["requested_by"] == self.user_name - - # spend 3*4.0 (total_spent = 12.0 > INTIAL_BUDGET = 10.0) - response = client.post( - "/smartnoise_query", - json=smartnoise_body, - headers=self.headers, - ) - assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json() == { - "InvalidQueryException": "Not enough budget for this query " - + "epsilon remaining 2.0, " - + "delta remaining 0.004970000100000034." - }
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/tests/test_dummy_generation.html b/html/de/_modules/lomas_server/tests/test_dummy_generation.html deleted file mode 100644 index 2b857b12..00000000 --- a/html/de/_modules/lomas_server/tests/test_dummy_generation.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - lomas_server.tests.test_dummy_generation — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.tests.test_dummy_generation

-import unittest
-
-from dp_queries.dummy_dataset import make_dummy_dataset
-from utils.example_inputs import DUMMY_NB_ROWS, DUMMY_SEED
-
-
-
-[Doku] -class TestMakeDummyDataset(unittest.TestCase): - """ - Tests for the generation of dummy datasets. - """ - -
-[Doku] - def test_cardinality_column(self) -> None: - """_summary_""" - metadata = { - "columns": { - "col_card": { - "type": "string", - "cardinality": 3, - "categories": ["a", "b", "c"], - } - } - } - df = make_dummy_dataset(metadata) - - # Test length - self.assertEqual(len(df), DUMMY_NB_ROWS) - - # Test cardinality type and categories - self.assertIn("col_card", df.columns) - self.assertEqual(df["col_card"].nunique(), 3) - self.assertEqual(set(df["col_card"].values), {"a", "b", "c"}) - assert isinstance(df["col_card"], object)
- - -
-[Doku] - def test_boolean_column(self) -> None: - """_summary_""" - - # Test a boolean column - metadata = { - "columns": {"col_bool": {"type": "boolean", "nullable": True}} - } - df = make_dummy_dataset(metadata) - - # Test length - self.assertEqual(len(df), DUMMY_NB_ROWS) - - # Test col generated is boolean - self.assertIn("col_bool", df.columns) - self.assertEqual(df.col_bool.dtypes.name, "boolean")
- - -
-[Doku] - def test_float_column(self) -> None: - """_summary_""" - lower_bound = 10.0 - upper_bound = 20.0 - metadata = { - "columns": { - "col_float": { - "type": "float", - "upper": upper_bound, - "lower": lower_bound, - } - } - } - df = make_dummy_dataset(metadata) - - # Test col generated is of type float - self.assertEqual(df.col_float.dtypes.name, "float64") - - # Test within bounds - self.assertTrue((df["col_float"] >= lower_bound).all()) - self.assertTrue((df["col_float"] <= upper_bound).all())
- - -
-[Doku] - def test_int_column(self) -> None: - """_summary_""" - lower_bound = 100 - upper_bound = 120 - metadata = { - "columns": { - "col_int": { - "type": "int", - "upper": upper_bound, - "lower": lower_bound, - } - } - } - df = make_dummy_dataset(metadata) - - # Test col generated is of type int - self.assertIn(df.col_int.dtypes.name, ["int32", "int64"]) - - # Test within bounds - self.assertTrue((df["col_int"] >= lower_bound).all()) - self.assertTrue((df["col_int"] <= upper_bound).all())
- - -
-[Doku] - def test_datetime_column(self) -> None: - """_summary_""" - metadata = {"columns": {"col_datetime": {"type": "datetime"}}} - df = make_dummy_dataset(metadata) - - # Test col generated is of type datetime - self.assertEqual(df.col_datetime.dtypes.name, "datetime64[ns]") - - # Should not have any null values - self.assertFalse(df.col_datetime.isnull().values.any())
- - -
-[Doku] - def test_nullable_column(self) -> None: - """_summary_""" - metadata = { - "columns": {"col_nullable": {"type": "datetime", "nullable": True}} - } - df = make_dummy_dataset(metadata) - - # Should have null values - self.assertTrue(df.col_nullable.isnull().values.any())
- - -
-[Doku] - def test_seed(self) -> None: - """_summary_""" - # Test the behavior with different seeds - metadata = {"columns": {"col_int": {"type": "int", "nullable": True}}} - seed1 = DUMMY_SEED - seed2 = DUMMY_SEED + 1 - - df1 = make_dummy_dataset(metadata, seed=seed1) - df2 = make_dummy_dataset(metadata, seed=seed2) - - # Check if datasets generated with different seeds are different - self.assertFalse(df1.equals(df2)) - - # Check if datasets generated with the same seed are identical - df1_copy = make_dummy_dataset(metadata, seed=seed1) - self.assertTrue(df1.equals(df1_copy))
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/tests/test_mongodb_admin.html b/html/de/_modules/lomas_server/tests/test_mongodb_admin.html deleted file mode 100644 index 6df4b4bb..00000000 --- a/html/de/_modules/lomas_server/tests/test_mongodb_admin.html +++ /dev/null @@ -1,748 +0,0 @@ - - - - - - lomas_server.tests.test_mongodb_admin — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.tests.test_mongodb_admin

-import os
-import unittest
-from types import SimpleNamespace
-
-import yaml
-from pymongo import MongoClient
-
-from admin_database.utils import get_mongodb_url
-from mongodb_admin import (
-    add_dataset,
-    add_dataset_to_user,
-    add_datasets_via_yaml,
-    add_user,
-    add_user_with_budget,
-    add_users_via_yaml,
-    del_dataset,
-    del_dataset_to_user,
-    del_user,
-    drop_collection,
-    set_budget_field,
-    set_may_query,
-)
-from constants import PrivateDatabaseType
-from tests.constants import ENV_MONGO_INTEGRATION
-from utils.config import CONFIG_LOADER, get_config
-
-
-
-[Doku] -@unittest.skipIf( - ENV_MONGO_INTEGRATION not in os.environ - and os.getenv(ENV_MONGO_INTEGRATION, "0").lower() in ("false", "0", "f"), - f"""Not an MongoDB integration test: {ENV_MONGO_INTEGRATION} - environment variable not set to True.""", -) -class TestMongoDBAdmin(unittest.TestCase): - """ - Tests for the functions in mongodb_admin.py. - - This is an integration test and requires a mongodb database - to be started before being executed. - - The test is only executed if the LOMAS_TEST_MONGO_INTEGRATION - environment variable is set to True. - """ - -
-[Doku] - @classmethod - def setUpClass(cls) -> None: - """Connection to database""" - CONFIG_LOADER.load_config( - config_path="tests/test_configs/test_config_mongo.yaml", - secrets_path="tests/test_configs/test_secrets.yaml", - ) - - db_args = SimpleNamespace(**vars(get_config().admin_database)) - db_url = get_mongodb_url(db_args) - cls.db = MongoClient(db_url)[db_args.db_name]
- - -
-[Doku] - def tearDown(self) -> None: - """Drop all data from database""" - drop_collection(self.db, "metadata") - drop_collection(self.db, "datasets") - drop_collection(self.db, "users") - drop_collection(self.db, "queries_archives")
- - -
-[Doku] - def test_add_user(self) -> None: - """Test adding a user""" - user = "Tintin" - - # Add user - add_user(self.db, user) - - expected_user = { - "user_name": user, - "may_query": True, - "datasets_list": [], - } - - user_found = self.db.users.find_one({"user_name": "Tintin"}) - del user_found["_id"] - - self.assertEqual(user_found, expected_user) - - # Adding existing user raises error - with self.assertRaises(ValueError): - add_user(self.db, user)
- - -
-[Doku] - def test_add_user_wb(self) -> None: - """Test adding a user with a dataset""" - user = "Tintin" - dataset = "Bijoux de la Castafiore" - epsilon = 10 - delta = 0.02 - - add_user_with_budget(self.db, user, dataset, epsilon, delta) - expected_user = { # pylint: disable=duplicate-code - "user_name": user, - "may_query": True, - "datasets_list": [ - { - "dataset_name": dataset, - "initial_epsilon": epsilon, - "initial_delta": delta, - "total_spent_epsilon": 0.0, - "total_spent_delta": 0.0, - } - ], - } - - user_found = self.db.users.find_one({"user_name": user}) - del user_found["_id"] - - self.assertEqual(user_found, expected_user) - - # Adding budget to existing user should raise error - with self.assertRaises(ValueError): - add_user_with_budget(self.db, user, dataset, epsilon, delta)
- - -
-[Doku] - def test_del_user(self) -> None: - """Test deleting a user""" - # Setup: add a user - user = "Tintin" - add_user(self.db, user) - - # Deleting user - del_user(self.db, user) - - expected_user = None - user_found = self.db.users.find_one({"user_name": user}) - self.assertEqual(user_found, expected_user) - - # Removing non-existing should raise error - with self.assertRaises(ValueError): - del_user(self.db, user)
- - -
-[Doku] - def test_add_dataset_to_user(self) -> None: - """Test add dataset to a user""" - user = "Tintin" - dataset = "Bijoux de la Castafiore" - epsilon = 10 - delta = 0.02 - - add_user(self.db, user) - add_dataset_to_user(self.db, user, dataset, epsilon, delta) - expected_user = { - "user_name": user, - "may_query": True, - "datasets_list": [ - { - "dataset_name": dataset, - "initial_epsilon": epsilon, - "initial_delta": delta, - "total_spent_epsilon": 0.0, - "total_spent_delta": 0.0, - } - ], - } - - user_found = self.db.users.find_one({"user_name": user}) - del user_found["_id"] - - assert user_found == expected_user - - # Adding dataset to existing user with existing dataset should - # raise and error - epsilon = 20 - with self.assertRaises(ValueError): - add_dataset_to_user(self.db, user, dataset, epsilon, delta) - - # Adding dataset to non-existing user should raise an error - user = "Milou" - with self.assertRaises(ValueError): - add_dataset_to_user(self.db, user, dataset, epsilon, delta)
- - -
-[Doku] - def test_del_dataset_to_user(self) -> None: - """Test delete dataset from user""" - # Setup: add user with dataset - user = "Tintin" - dataset = "Bijoux de la Castafiore" - epsilon = 10 - delta = 0.02 - - add_user_with_budget(self.db, user, dataset, epsilon, delta) - - # Test dataset deletion - del_dataset_to_user(self.db, user, dataset) - expected_user = { - "user_name": user, - "may_query": True, - "datasets_list": [], - } - user_found = self.db.users.find_one({"user_name": user}) - del user_found["_id"] - - self.assertEqual(user_found, expected_user) - - # Remove dataset from non-existant user should raise error - user = "Milou" - with self.assertRaises(ValueError): - del_dataset_to_user(self.db, user, dataset) - - # Remove dataset not present in user should raise error - user = "Tintin" - dataset = "Bijoux de la Castafiore" - with self.assertRaises(Exception): - del_dataset_to_user(self.db, user, dataset)
- - -
-[Doku] - def test_set_budget_field(self) -> None: - """Test setting a budget field""" - # Setup: add user with budget - user = "Tintin" - dataset = "Bijoux de la Castafiore" - epsilon = 10 - delta = 0.02 - - add_user_with_budget(self.db, user, dataset, epsilon, delta) - - # Updating budget should work - field = "initial_epsilon" - value = 15 - set_budget_field(self.db, user, dataset, field, value) - - expected_user = { - "user_name": user, - "may_query": True, - "datasets_list": [ - { - "dataset_name": dataset, - "initial_epsilon": value, - "initial_delta": delta, - "total_spent_epsilon": 0.0, - "total_spent_delta": 0.0, - } - ], - } - - user_found = self.db.users.find_one({"user_name": user}) - del user_found["_id"] - - self.assertEqual(user_found, expected_user) - - # Setting budget for non-existing user should fail - user = "Milou" - with self.assertRaises(ValueError): - set_budget_field(self.db, user, dataset, field, value) - - # Setting budget for non-existing dataset should fail - user = "Tintin" - dataset = "os de Milou" - with self.assertRaises(ValueError): - set_budget_field(self.db, user, dataset, field, value)
- - -
-[Doku] - def test_set_may_query(self) -> None: - """Test set may query""" - # Setup: add user with budget - user = "Tintin" - dataset = "PENGUIN" - epsilon = 10 - delta = 0.02 - - add_user_with_budget(self.db, user, dataset, epsilon, delta) - - # Set may query - value = False - set_may_query(self.db, user, value) - - expected_user = { - "user_name": user, - "may_query": value, - "datasets_list": [ - { - "dataset_name": dataset, - "initial_epsilon": epsilon, - "initial_delta": delta, - "total_spent_epsilon": 0.0, - "total_spent_delta": 0.0, - } - ], - } - - user_found = self.db.users.find_one({"user_name": user}) - del user_found["_id"] - - self.assertEqual(user_found, expected_user) - - # Raises error when user does not exist - user = "Milou" - with self.assertRaises(ValueError): - set_may_query(self.db, user, value)
- - -
-[Doku] - def test_add_users_via_yaml(self) -> None: - """Test create user collection via YAML file""" - # Adding two users - path = "./tests/test_data/test_user_collection.yaml" - clean = False - overwrite = False - add_users_via_yaml(self.db, path, clean, overwrite) - - tintin = { - "user_name": "Tintin", - "may_query": True, - "datasets_list": [ - { - "dataset_name": "Bijoux de la Castafiore", - "initial_epsilon": 10, - "initial_delta": 0.005, - "total_spent_epsilon": 0.0, - "total_spent_delta": 0.0, - } - ], - } - - user_found = self.db.users.find_one({"user_name": "Tintin"}) - del user_found["_id"] - - self.assertEqual(user_found, tintin) - - milou = { - "user_name": "Milou", - "may_query": True, - "datasets_list": [ - { - "dataset_name": "os", - "initial_epsilon": 20, - "initial_delta": 0.005, - "total_spent_epsilon": 0.0, - "total_spent_delta": 0.0, - } - ], - } - - user_found = self.db.users.find_one({"user_name": "Milou"}) - del user_found["_id"] - - self.assertEqual(user_found, milou) - - # Check cleaning - user = "Tintin" - field = "initial_epsilon" - value = 25.0 - dataset = "Bijoux de la Castafiore" - set_budget_field(self.db, user, dataset, field, value) - - clean = True - add_users_via_yaml(self.db, path, clean, overwrite) - - user_found = self.db.users.find_one({"user_name": "Tintin"}) - del user_found["_id"] - self.assertEqual(user_found, tintin) - - user_found = self.db.users.find_one({"user_name": "Milou"}) - del user_found["_id"] - self.assertEqual(user_found, milou) - - # Check overwriting (with new user) - user = "Tintin" - field = "initial_epsilon" - value = False - dataset = "Bijoux de la Castafiore" - set_budget_field(self.db, user, dataset, field, value) - - user = "Milou" - del_user(self.db, user) - add_users_via_yaml(self.db, path, clean=False, overwrite=True) - - user_found = self.db.users.find_one({"user_name": "Tintin"}) - del user_found["_id"] - self.assertEqual(user_found, tintin) - - user_found = self.db.users.find_one({"user_name": "Milou"}) - del user_found["_id"] - self.assertEqual(user_found, milou) - - # Overwrite to false and existing users should warn - with self.assertWarns(UserWarning): - add_users_via_yaml(self.db, path, clean=False, overwrite=False)
- - -
-[Doku] - def test_add_local_dataset(self) -> None: - """Test adding a local dataset""" - dataset = "PENGUIN" - database_type = PrivateDatabaseType.PATH - dataset_path = "some_path" - metadata_database_type = PrivateDatabaseType.PATH - metadata_path = "./tests/test_data/metadata/penguin_metadata.yaml" - - add_dataset( - self.db, - dataset, - database_type, - metadata_database_type, - dataset_path=dataset_path, - metadata_path=metadata_path, - ) - - expected_dataset = { - "dataset_name": dataset, - "database_type": database_type, - "dataset_path": dataset_path, - "metadata": { - "database_type": metadata_database_type, - "metadata_path": metadata_path, - }, - } - with open( - "./tests/test_data/metadata/penguin_metadata.yaml", - encoding="utf-8", - ) as f: - expected_metadata = yaml.safe_load(f) - - dataset_found = self.db.datasets.find_one({"dataset_name": "PENGUIN"}) - del dataset_found["_id"] - self.assertEqual(dataset_found, expected_dataset) - - metadata_found = self.db.metadata.find_one( - {dataset: {"$exists": True}} - )[dataset] - self.assertEqual(metadata_found, expected_metadata)
- - -
-[Doku] - def test_add_datasets_via_yaml(self) -> None: - """Test add datasets via a YAML file""" - # Load reference data - with open( - "./tests/test_data/test_datasets.yaml", - encoding="utf-8", - ) as f: - datasets = yaml.safe_load(f) - penguin = datasets["datasets"][0] - iris = datasets["datasets"][1] - - with open( - "./tests/test_data/metadata/penguin_metadata.yaml", - encoding="utf-8", - ) as f: - penguin_metadata = yaml.safe_load(f) - - def verify_datasets(): - # Check penguin and iris are in db - penguin_found = self.db.datasets.find_one( - {"dataset_name": "PENGUIN"} - ) - del penguin_found["_id"] - self.assertEqual(penguin_found, penguin) - - metadata_found = self.db.metadata.find_one( - {"PENGUIN": {"$exists": True}} - )["PENGUIN"] - self.assertEqual(metadata_found, penguin_metadata) - - iris_found = self.db.datasets.find_one({"dataset_name": "IRIS"}) - del iris_found["_id"] - self.assertEqual(iris_found, iris) - - metadata_found = self.db.metadata.find_one( - {"IRIS": {"$exists": True}} - )["IRIS"] - self.assertEqual(metadata_found, penguin_metadata) - - path = "./tests/test_data/test_datasets.yaml" - clean = False - overwrite_datasets = False - overwrite_metadata = False - - add_datasets_via_yaml( - self.db, path, clean, overwrite_datasets, overwrite_metadata - ) - - verify_datasets() - - # Check clean works - - # Add new dataset and then add_datasets_via_yaml with clean option - self.db.datasets.insert_one( - {"dataset_name": "Les aventures de Tintin"} - ) - - clean = True - add_datasets_via_yaml( - self.db, path, clean, overwrite_datasets, overwrite_metadata - ) - verify_datasets() - - # Check no overwrite triggers warning - clean = False - with self.assertWarns(UserWarning): - add_datasets_via_yaml( - self.db, path, clean, overwrite_datasets, overwrite_metadata - ) - - # Check overwrite works - self.db.datasets.update_one( - {"dataset_name": "IRIS"}, {"$set": {"dataset_name": "IRIS"}} - ) - - overwrite_datasets = True - add_datasets_via_yaml( - self.db, path, clean, overwrite_datasets, overwrite_metadata - ) - verify_datasets()
- - -
-[Doku] - def test_del_dataset(self) -> None: - """Test dataset deletion""" - # Setup: add one dataset - dataset = "PENGUIN" - database_type = PrivateDatabaseType.PATH - dataset_path = "some_path" - metadata_database_type = PrivateDatabaseType.PATH - metadata_path = "./tests/test_data/metadata/penguin_metadata.yaml" - - add_dataset( - self.db, - dataset, - database_type, - metadata_database_type, - dataset_path=dataset_path, - metadata_path=metadata_path, - ) - - # Verify delete works - del_dataset(self.db, dataset) - - dataset_found = self.db.datasets.find_one({"dataset_name": "PENGUIN"}) - self.assertEqual(dataset_found, None) - - nb_metadata = self.db.metadata.count_documents({}) - self.assertEqual(nb_metadata, 0) - - # Delete non-existing dataset should trigger error - with self.assertRaises(ValueError): - del_dataset(self.db, dataset)
- - -
-[Doku] - def test_drop_collection(self) -> None: - """Test drop collection from db""" - # Setup: add one dataset - dataset = "PENGUIN" - database_type = PrivateDatabaseType.PATH - dataset_path = "some_path" - metadata_database_type = PrivateDatabaseType.PATH - metadata_path = "./tests/test_data/metadata/penguin_metadata.yaml" - - add_dataset( - self.db, - dataset, - database_type, - metadata_database_type, - dataset_path=dataset_path, - metadata_path=metadata_path, - ) - - # Test - collection = "datasets" - drop_collection(self.db, collection) - - nb_datasets = self.db.datasets.count_documents({}) - self.assertEqual(nb_datasets, 0)
-
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/utils/anti_timing_att.html b/html/de/_modules/lomas_server/utils/anti_timing_att.html deleted file mode 100644 index f1bb22d9..00000000 --- a/html/de/_modules/lomas_server/utils/anti_timing_att.html +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - lomas_server.utils.anti_timing_att — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.utils.anti_timing_att

-import random
-import time
-from typing import Callable
-
-from fastapi import Request, Response
-
-from utils.config import Config
-from utils.error_handler import InternalServerException
-
-
-
-[Doku] -async def anti_timing_att( - request: Request, call_next: Callable, config: Config -) -> Response: - """ - Anti-timing attack mechanism. - - Changes the response time to either a minimum or by adding - random noïse in order to avoid timing attacks. - - Args: - request (Request): The FastApi request. - call_next (Callable): The FastApi endpoint to call. - config (Config): The server config. - - Returns: - Response: The reponse from call_next. - """ - start_time = time.time() - response = await call_next(request) - process_time = time.time() - start_time - - if config.server.time_attack: - match config.server.time_attack.method: - case "stall": - # if stall is used slow fast callbacks - # to a minimum response time defined by magnitude - if process_time < config.server.time_attack.magnitude: - time.sleep( - config.server.time_attack.magnitude - process_time - ) - case "jitter": - # if jitter is used it just adds some time - # between 0 and magnitude secs - time.sleep( - config.server.time_attack.magnitude * random.uniform(0, 1) - ) - case _: - raise InternalServerException( - "Time attack method not supported." - ) - return response
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/utils/collections_models.html b/html/de/_modules/lomas_server/utils/collections_models.html deleted file mode 100644 index e277d186..00000000 --- a/html/de/_modules/lomas_server/utils/collections_models.html +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - lomas_server.utils.collections_models — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.utils.collections_models

-from typing import Dict, List, Union
-
-from pydantic import BaseModel
-
-from constants import PrivateDatabaseType
-
-
-
-[Doku] -class DatasetOfUser(BaseModel): - """BaseModel for informations of a user on a dataset""" - - dataset_name: str - initial_epsilon: float - initial_delta: float - total_spent_epsilon: float - total_spent_delta: float
- - - -
-[Doku] -class User(BaseModel): - """BaseModel for a user in a user collection""" - - user_name: str - may_query: bool - datasets_list: List[DatasetOfUser]
- - - -
-[Doku] -class UserCollection(BaseModel): - """BaseModel for users collection""" - - users: List[User]
- - - -
-[Doku] -class MetadataOfDataset(BaseModel): - """BaseModel for metadata of a dataset""" - - database_type: PrivateDatabaseType
- - - -
-[Doku] -class MetadataOfPathDB(MetadataOfDataset): - """BaseModel for metadata of a dataset with PATH_DB""" - - metadata_path: str
- - - -
-[Doku] -class MetadataOfS3DB(MetadataOfDataset): - """BaseModel for metadata of a dataset with S3_DB""" - - s3_bucket: str - s3_key: str - endpoint_url: str - aws_access_key_id: str - aws_secret_access_key: str
- - - -
-[Doku] -class Dataset(BaseModel): - """BaseModel for a dataset""" - - dataset_name: str - database_type: PrivateDatabaseType - metadata: Union[MetadataOfPathDB, MetadataOfS3DB]
- - - -
-[Doku] -class DatasetOfPathDB(Dataset): - """BaseModel for a local dataset""" - - dataset_path: str
- - - -
-[Doku] -class DatasetOfS3DB(Dataset): - """BaseModel for a dataset on S3""" - - s3_bucket: str - s3_key: str - endpoint_url: str - aws_access_key_id: str - aws_secret_access_key: str
- - - -
-[Doku] -class DatasetsCollection(BaseModel): - """BaseModel for datasets collection""" - - datasets: List[Union[DatasetOfPathDB, DatasetOfS3DB]]
- - - -
-[Doku] -class Metadata(BaseModel): - """BaseModel for a metadata format""" - - max_ids: int - row_privacy: bool - columns: Dict[str, Dict[str, Union[int, float, str, List[str]]]]
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/utils/config.html b/html/de/_modules/lomas_server/utils/config.html deleted file mode 100644 index 44a635d0..00000000 --- a/html/de/_modules/lomas_server/utils/config.html +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - lomas_server.utils.config — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.utils.config

-import yaml
-from pydantic import BaseModel
-
-from typing_extensions import Dict
-
-from constants import (
-    ConfigKeys,
-    CONFIG_PATH,
-    SECRETS_PATH,
-    AdminDBType,
-    DatasetStoreType,
-    TimeAttackMethod,
-)
-from utils.error_handler import InternalServerException
-
-
-
-[Doku] -class TimeAttack(BaseModel): - """BaseModel for configs to prevent timing attacks""" - - method: TimeAttackMethod - magnitude: float
- - - -
-[Doku] -class Server(BaseModel): - """BaseModel for uvicorn server configs""" - - time_attack: TimeAttack - host_ip: str - host_port: int - log_level: str - reload: bool - workers: int
- - - -
-[Doku] -class DatasetStoreConfig(BaseModel): - """BaseModel for dataset store configs""" - - ds_store_type: DatasetStoreType
- - - -
-[Doku] -class LRUDatasetStoreConfig(DatasetStoreConfig): - """BaseModel for dataset store configs in case of a LRU dataset store""" - - max_memory_usage: int
- - - -
-[Doku] -class DBConfig(BaseModel): - """BaseModel for database type config""" - - db_type: str = AdminDBType
- - - -
-[Doku] -class YamlDBConfig(DBConfig): - """BaseModel for dataset store configs in case of a Yaml database""" - - db_file: str
- - - -
-[Doku] -class MongoDBConfig(DBConfig): - """BaseModel for dataset store configs in case of a MongoDB database""" - - address: str - port: int - username: str - password: str - db_name: str
- - - -
-[Doku] -class Config(BaseModel): - """ - Server runtime config. - """ - - # Develop mode - develop_mode: bool - - # Server configs - server: Server - - # A limit on the rate which users can submit answers - submit_limit: float - - admin_database: DBConfig - - dataset_store: DatasetStoreConfig
- - - -
-[Doku] -class ConfigLoader: - """Singleton object that holds the config for the server. - - Initialises the config by calling load_config() with its - default arguments. - - The config can be reloaded by calling load_config with - other arguments. - """ - - _instance = None - _config = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - -
-[Doku] - def load_config( - self, config_path: str = CONFIG_PATH, secrets_path: str = SECRETS_PATH - ) -> None: - """ - Loads the config and the secret data from disk, - merges them and returns the config object. - - Args: - config_path (str, optional): - The config filepath. Defaults to CONFIG_PATH. - secrets_path (str, optional): - The secrets filepath. Defaults to SECRETS_PATH. - - Raises: - InternalServerException: If the config cannot be - correctly interpreted. - """ - try: - with open(config_path, "r", encoding="utf-8") as f: - config_data = yaml.safe_load(f)[ConfigKeys.RUNTIME_ARGS][ - ConfigKeys.SETTINGS - ] - - # Merge secret data into config data - with open(secrets_path, "r", encoding="utf-8") as f: - secret_data = yaml.safe_load(f) - config_data = self._merge_dicts(config_data, secret_data) - - # Server configuration - server_config: Server = Server.model_validate( - config_data[ConfigKeys.SERVER] - ) - - # Admin database - db_type = AdminDBType( - config_data[ConfigKeys.DB][ConfigKeys.DB_TYPE] - ) - admin_database_config = self._validate_admin_db_config( - db_type, config_data[ConfigKeys.DB] - ) - - # Dataset store - ds_store_type = DatasetStoreType( - config_data[ConfigKeys.DATASET_STORE][ - ConfigKeys.DATASET_STORE_TYPE - ] - ) - ds_store_config = self._validate_ds_store_config( - ds_store_type, config_data[ConfigKeys.DATASET_STORE] - ) - - self._config = Config( - develop_mode=config_data[ConfigKeys.DEVELOP_MODE], - server=server_config, - submit_limit=config_data[ConfigKeys.SUBMIT_LIMIT], - admin_database=admin_database_config, - dataset_store=ds_store_config, - ) - - except Exception as e: - raise InternalServerException( - f"Could not read config from disk at {config_path}" - + f" or missing fields: {e}" - ) from e
- - - def _merge_dicts(self, d: Dict, u: Dict) -> Dict: - """Recursively add dictionnary u to dictionnary v - - Args: - d (Dict): dictionnary to add data to - u (Dict): dictionnary to be added to d - - Returns: - d (Dict): dictionnary d and u merged recursively - """ - for k, v in u.items(): - if isinstance(v, dict): - d[k] = self._merge_dicts(d.get(k, {}), v) - else: - d[k] = v - return d - - def _validate_admin_db_config( - self, db_type: AdminDBType, config_data: dict - ) -> DBConfig: - """Validate admin database based on configuration parameters - - Args: - db_type (AdminDBType): type of admin database - config_data (dict): additionnal configuration data - - Raises: - InternalServerException: If the admin database type from the config - does not exist. - - Returns: - DBConfig validated admin database configuration - """ - if db_type == AdminDBType.MONGODB: - return MongoDBConfig.model_validate(config_data) - if db_type == AdminDBType.YAML: - return YamlDBConfig.model_validate(config_data) - - raise InternalServerException( - f"Admin database type {db_type} not supported." - ) - - def _validate_ds_store_config( - self, ds_store_type: DatasetStoreType, config_data: dict - ) -> DatasetStoreConfig: - """Validate dataset store configuration parameters - - Args: - ds_store_type (DatasetStoreType): type of admin database - config_data (dict): additionnal configuration data - - Raises: - InternalServerException: If the dataset store type from the config - does not exist. - - Returns: - DatasetStoreConfig validated dataset store configuration - """ - if ds_store_type == DatasetStoreType.BASIC: - return DatasetStoreConfig.model_validate(config_data) - if ds_store_type == DatasetStoreType.LRU: - return LRUDatasetStoreConfig.model_validate(config_data) - - raise InternalServerException( - f"Dataset store {ds_store_type} not supported." - ) - -
-[Doku] - def set_config(self, config: Config) -> None: - """ - Set the singleton's config to config. - - Args: - config (Config): The new config. - """ - self._config = config
- - -
-[Doku] - def get_config(self) -> Config: - """ - Get the config. - - Returns: - Config: The config. - """ - if self._config is None: - self.load_config() - return self._config # type: ignore
-
- - - -CONFIG_LOADER = ConfigLoader() - - -
-[Doku] -def get_config() -> Config: - """ - Get the config from the ConfigLoader Singleton instance. - - Returns: - Config: The config. - """ - return CONFIG_LOADER.get_config()
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/utils/error_handler.html b/html/de/_modules/lomas_server/utils/error_handler.html deleted file mode 100644 index 7dca6cbf..00000000 --- a/html/de/_modules/lomas_server/utils/error_handler.html +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - lomas_server.utils.error_handler — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.utils.error_handler

-from fastapi import FastAPI, Request, status
-from fastapi.responses import JSONResponse
-
-from constants import INTERNAL_SERVER_ERROR
-from utils.loggr import LOG
-
-
-
-[Doku] -class InvalidQueryException(Exception): - """ - Custom exception for invalid queries - - For example, this exception will occur when the query: - - is not an opendp measurement - - cannot be reconstructed properly (for opendp and diffprivlib) - """ - - def __init__(self, error_message: str) -> None: - """_summary_ - - Args: - error_message (str): _description_ - """ - self.error_message = error_message
- - - -
-[Doku] -class ExternalLibraryException(Exception): - """ - Custom exception for issues within external libraries - - This exception will occur when the processes fail within the - external libraries (smartnoise-sql, opendp, diffprivlib) - """ - - def __init__(self, library: str, error_message: str) -> None: - """_summary_ - - Args: - library (str): _description_ - error_message (str): _description_ - """ - self.library = library - self.error_message = error_message
- - - -
-[Doku] -class UnauthorizedAccessException(Exception): - """ - Custom exception for unauthorized access: - (unknown user, no access to dataset, etc) - """ - - def __init__(self, error_message: str) -> None: - self.error_message = error_message
- - - -
-[Doku] -class InternalServerException(Exception): - """ - Custom exception for issues within server internal functionalities - like unexpected match cases - """ - - def __init__(self, error_message: str) -> None: - self.error_message = error_message
- - - -CUSTOM_EXCEPTIONS: tuple[type, ...] = ( - ExternalLibraryException, - InternalServerException, - InvalidQueryException, - UnauthorizedAccessException, -) - - -# Custom exception handlers -
-[Doku] -def add_exception_handlers(app: FastAPI) -> None: - """ - Translates custom exceptions to JSONResponses. - Args: - app (FastAPI): A fastapi App. - """ - - @app.exception_handler(InvalidQueryException) - async def invalid_query_exception_handler( - _: Request, exc: InvalidQueryException - ) -> JSONResponse: - LOG.info(f"InvalidQueryException raised: {exc.error_message}") - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content={"InvalidQueryException": exc.error_message}, - ) - - @app.exception_handler(ExternalLibraryException) - async def external_library_exception_handler( - _: Request, exc: ExternalLibraryException - ) -> JSONResponse: - LOG.info(f"ExternalLibraryException raised: {exc.error_message}") - return JSONResponse( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - content={ - "ExternalLibraryException": exc.error_message, - "library": exc.library, - }, - ) - - @app.exception_handler(UnauthorizedAccessException) - async def unauthorized_access_exception_handler( - _: Request, exc: UnauthorizedAccessException - ) -> JSONResponse: - LOG.info(f"UnauthorizedAccessException raised: {exc.error_message}") - return JSONResponse( - status_code=status.HTTP_403_FORBIDDEN, - content={"UnauthorizedAccessException": exc.error_message}, - ) - - @app.exception_handler(InternalServerException) - async def internal_server_exception_handler( - _: Request, exc: InternalServerException - ) -> JSONResponse: - LOG.info(f"InternalServerException raised: {exc.error_message}") - return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content={"InternalServerException": INTERNAL_SERVER_ERROR}, - )
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/utils/input_models.html b/html/de/_modules/lomas_server/utils/input_models.html deleted file mode 100644 index 0e288632..00000000 --- a/html/de/_modules/lomas_server/utils/input_models.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - lomas_server.utils.input_models — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.utils.input_models

-from typing import Optional
-
-from pydantic import BaseModel, Field
-
-from constants import DELTA_LIMIT, EPSILON_LIMIT
-
-
-
-[Doku] -class GetDbData(BaseModel): - """Model input to get information about a dataset""" - - dataset_name: str
- - - -
-[Doku] -class GetDummyDataset(BaseModel): - """Model input to get a dummy dataset""" - - dataset_name: str - dummy_nb_rows: int - dummy_seed: int
- - - -
-[Doku] -class SNSQLInp(BaseModel): - """Model input for a smarnoise-sql query""" - - query_str: str - dataset_name: str - epsilon: float = Field( - ..., - gt=0, - le=EPSILON_LIMIT, - ) - delta: float = Field( - ..., - gt=0, - le=DELTA_LIMIT, - ) - mechanisms: dict - postprocess: bool
- - - -
-[Doku] -class DummySNSQLInp(BaseModel): - """Model input for a smarnoise-sql dummy query""" - - query_str: str - dataset_name: str - dummy_nb_rows: int - dummy_seed: int - epsilon: float - delta: float - mechanisms: dict - postprocess: bool
- - - -
-[Doku] -class SNSQLInpCost(BaseModel): - """Model input for a smarnoise-sql cost query""" - - query_str: str - dataset_name: str - epsilon: float - delta: float - mechanisms: dict
- - - -
-[Doku] -class OpenDPInp(BaseModel): - """Model input for an opendp query""" - - dataset_name: str - opendp_json: str - fixed_delta: Optional[float] = None
- - - -
-[Doku] -class DummyOpenDPInp(BaseModel): - """Model input for a dummy opendp query""" - - dataset_name: str - opendp_json: str - dummy_nb_rows: int - dummy_seed: int - fixed_delta: Optional[float] = None
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_modules/lomas_server/utils/utils.html b/html/de/_modules/lomas_server/utils/utils.html deleted file mode 100644 index c25671e7..00000000 --- a/html/de/_modules/lomas_server/utils/utils.html +++ /dev/null @@ -1,252 +0,0 @@ - - - - - - lomas_server.utils.utils — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Quellcode für lomas_server.utils.utils

-import io
-from collections.abc import AsyncGenerator
-
-import pandas as pd
-from fastapi import Request
-from fastapi.responses import StreamingResponse
-from pymongo.database import Database
-
-from admin_database.utils import get_mongodb
-from mongodb_admin import (
-    add_datasets_via_yaml,
-    add_users_via_yaml,
-    drop_collection,
-)
-from utils.error_handler import InternalServerException
-from utils.loggr import LOG
-
-
-
-[Doku] -async def server_live(request: Request) -> AsyncGenerator: - """ - Checks the server is live and throws an exception otherwise. - - Args: - request (Request): Raw request - - Raises: - InternalServerException: If the server is not live. - - Returns: - AsyncGenerator - """ - if not request.app.state.server_state["LIVE"]: - raise InternalServerException( - "Woops, the server did not start correctly." - + "Contact the administrator of this service.", - ) - yield
- - - -
-[Doku] -def stream_dataframe(df: pd.DataFrame) -> StreamingResponse: - """ - Creates a streaming response for a given pandas dataframe. - - Args: - df (pd.DataFrame): The dataframe to stream. - - Returns: - StreamingResponse: The resulting streaming response. - """ - stream = io.StringIO() - - # CSV creation - df.to_csv(stream, index=False) - - response = StreamingResponse( - iter([stream.getvalue()]), media_type="text/csv" - ) - response.headers["Content-Disposition"] = ( - "attachment; filename=synthetic_data.csv" - ) - return response
- - - -
-[Doku] -def add_demo_data_to_admindb() -> None: - """ - Adds the demo data to the mongodb admindb. - - Uses hardcoded paths to user and dataset collections. - Meant to be used in the develop mode of the service. - """ - LOG.info("Creating example user collection") - mongo_db: Database = get_mongodb() - - LOG.info("Creating user collection") - add_users_via_yaml( - mongo_db, - clean=True, - overwrite=True, - yaml_file="/data/collections/user_collection.yaml", - ) - - LOG.info("Creating datasets and metadata collection") - add_datasets_via_yaml( - mongo_db, - clean=True, - overwrite_datasets=True, - overwrite_metadata=True, - yaml_file="/data/collections/dataset_collection.yaml", - ) - - LOG.info("Empty archives") - drop_collection(mongo_db, collection="queries_archives")
- -
- -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/_sources/CONTRIBUTING.md.txt b/html/de/_sources/CONTRIBUTING.md.txt deleted file mode 100644 index f5b336db..00000000 --- a/html/de/_sources/CONTRIBUTING.md.txt +++ /dev/null @@ -1,64 +0,0 @@ -# Tips for developers - -It is recommended to run the project with python 3.11. - -## Start service - -The different notebooks in `./notebooks` provide more complete documentation regarding the set up and administration of the server. -### On local machine -To start the container on a local machine, first create a mondodb volume -with `docker volume create mongodata`, then go to `lomas/server/` and run `docker compose up`. -If you encounter any issue, you might want to run `docker compose down` first. - -#### Additional services -Running `docker compose up` will also start two additional services automatically: -- a jupyter notebook environment that will be available at the address http://127.0.0.1:8888/ to interact as a user with the server -- a streamlit application that will be available at the address http://localhost:8501/ to interact with the server and the administration database as an administrator. - -### On a kubernetes cluster -To start the server on a kubernetes cluster, first add the repo with: -`helm repo add lomas https://dscc-admin-ch.github.io/helm-charts` -and then install the chart with: -`helm install lomas-sever lomas/lomas-server` - -### On Onyxia -To start the server on Onyxia, select the `lomas `service, (optionnally adapt the administration and runtime parameters) and click on 'Lancer'. - -## Tests - -It is possible to test the server with standard tests or integration tests (for the mongodb). -The `run_integration_tests.sh` script runs the integration tests, make sure to have an activated python venv in a linux environment with the server requirements installed for it to work. - -Local tests (except for those using the mongodb_admin) can be run with a simple `python -m unittest discover -s . ` from the `lomas_server` directory. The tests will be based on the config in `lomas/server/lomas_server/tests/test_configs/test_config.yaml` and be executed with the AdminYamlDatabase. -For local tests on the mongodb_admin, first get in the container with `docker exec -it lomas_server_dev bash` and then run the tests. - - -A github workflow is configured to run the integration tests script for pull requests on the develop and master branches. - -## Linting and other checks - -Here is a list of the checks performed: - - - Use black to automatically format the code: `black .` - - Use flake to verify formating and performing a static code analysis: `flake8 .` - - Use mypy for static type checking: `mypy .` - - Use pylint for further static analysis: `pylint --disable=E0401 --disable=C0114 --disable=R0903 --disable=E0611 --disable=W0511 --disable=duplicate-code tests/ .` - - - `--disable=E0401` to ignore import-error - - `--disable=C0114` to ignore missing-module-docstring - - `--disable=R0903` to ignore too-few-public-methods - - `--disable=E0611` to ignore no-name-in-module - - `--disable=W0511` to ignore fixme (TODOs) - - `--disable=duplicate-code tests/` to ignore duplicate-code statements related to the tests - -We rely on a github workflow to automatically run the checks on pull requests. - -## Documentation - -Documentation is automatically generated from the code docstrings with sphinx, as well as the resulting html pages. -The process goes in two steps - - `sphinx-apidoc -o ./docs/source ./lomas_server` for generating the .rst files. - - `make html` from the `./docs` folder to generate the webpages. - -We do not check the documentation files into the repo, but rather rely on a github workflow to generate and publish it -on a dedicated repo's github pages for easy access from the web. diff --git a/html/de/_sources/api.rst.txt b/html/de/_sources/api.rst.txt deleted file mode 100644 index 142f59e6..00000000 --- a/html/de/_sources/api.rst.txt +++ /dev/null @@ -1,8 +0,0 @@ -API Documentation -================= - -.. toctree:: - :maxdepth: 2 - - server_api - client_api \ No newline at end of file diff --git a/html/de/_sources/client_api.rst.txt b/html/de/_sources/client_api.rst.txt deleted file mode 100644 index 4ea37be9..00000000 --- a/html/de/_sources/client_api.rst.txt +++ /dev/null @@ -1,8 +0,0 @@ -Client API -================== - -.. toctree:: - :maxdepth: 2 - :glob: - - client_modules diff --git a/html/de/_sources/client_errors.rst.txt b/html/de/_sources/client_errors.rst.txt deleted file mode 100644 index 28f6eb74..00000000 --- a/html/de/_sources/client_errors.rst.txt +++ /dev/null @@ -1,49 +0,0 @@ -Errors -============ - -They are 4 custom errors describing the reasons why the queries fail and what to do: - 1. InvalidQueryException - 2. ExternalLibraryException - 3. UnauthorizedAccessException - 4. InternalServerException - -======================= - -.. _InvalidQueryException: - -InvalidQueryException ---------------------- -- Happens when: error is due to an an invalid query from the user -- Example: an "opendp" pipeline which is not a measurement, not enough budget for query -- What to do: Based on the specific error message, the user should check and fix their relevant query parameters. - - -.. _ExternalLibraryException: - -ExternalLibraryException ------------------------- -- Happens when: error comes from an external differential privacy library ("smartnoise-sql" or "opendp"). -- Example: invalid values in Smartnoise-SQL `mechanisms` parameter. -- What to do: Based on the specific error message, the user should check and fix their "smartnoise-sql" `query` parameter or "opendp" `pipeline` parameter. - - -.. _UnauthorizedAccessException: - -UnauthorizedAccessException ---------------------------- -- Happens when: user tries to query a dataset without sufficient authorisation: - - when the user does not exist, - - when the user does not have access to the dataset, - - when the user may not query. -- Example: user tries to query a dataframe to which they do not have access -- What to do: The user should decrease the budget parameters and check their access rights. - -.. _InternalServerException: - -InternalServerException ---------------------------- -- Happens when: there are internal issues within the server. -- Example: failed startup of the server -- What to do: contact the administrative team for help. Users are not responsible for the failure of their request. - -This error should never occur while the server is deployed. diff --git a/html/de/_sources/client_examples.rst.txt b/html/de/_sources/client_examples.rst.txt deleted file mode 100644 index 0097b235..00000000 --- a/html/de/_sources/client_examples.rst.txt +++ /dev/null @@ -1,17 +0,0 @@ -Examples -======== - -In this section, you will find various examples demonstrating how to use Lomas-client in different -scenarios. These examples will help you understand the practical applications and features of lomas. - -You can also find additional notebooks and examples in our -`GitHub repository `_. - - -.. toctree:: - :maxdepth: 2 - - notebooks/Demo_Client_Notebook - notebooks/s3_example_notebook - -Here are some examples for using Lomas-client. diff --git a/html/de/_sources/client_modules.rst.txt b/html/de/_sources/client_modules.rst.txt deleted file mode 100644 index a79f8159..00000000 --- a/html/de/_sources/client_modules.rst.txt +++ /dev/null @@ -1,7 +0,0 @@ -lomas_client -============ - -.. toctree:: - :maxdepth: 4 - - lomas_client diff --git a/html/de/_sources/client_quickstart.rst.txt b/html/de/_sources/client_quickstart.rst.txt deleted file mode 100644 index 405f5df2..00000000 --- a/html/de/_sources/client_quickstart.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ -Quickstart -========== - -This is the quickstart guide for Lomas-client, providing initial setup and usage instructions. - -Client ------- - -Installation -~~~~~~~~~~~~ - -To install lomas-client, follow these steps: - -1. Open a terminal. -2. Run the following command: - -.. code-block:: bash - - pip install lomas-client - -3. You're all set! - -First steps -~~~~~~~~~~~~ - -To use FSO lomas client, you can do the following: - -1. Import the library in your Python code. -2. Initialise the client with required url, name and dataset. -3. You can now use any function as long as you have access to the dataset! - - .. code-block:: python - - # Step 1 - from lomas_client import Client - - # Step 2 - APP_URL = "your_deployement_url" - USER_NAME = "your_name" - DATASET_NAME = "name_of_dataset_you_want_to_query" - client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME) - - # Step 3 - res = client.any_query(parameters) - -with `any_query` being one of the function presented below and `parameters` being its associated parameters. \ No newline at end of file diff --git a/html/de/_sources/index.rst.txt b/html/de/_sources/index.rst.txt deleted file mode 100644 index 4b878592..00000000 --- a/html/de/_sources/index.rst.txt +++ /dev/null @@ -1,115 +0,0 @@ -.. Lomas documentation master file, created by - sphinx-quickstart on Wed May 28 09:18:59 2024. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to Lomas documentation -======================================== -The lomas platform follows a classic server/client model. On the client side, the -user prepares queries for statistical analyses which are sent to the -service's REST API via HTTP. The user never has direct access to the sensitive -data. On the server side, the service is implemented in a micro-service architecture and is -thus split into two parts: the administration database and the client-facing HTTP server -(which we call server for brevity) that implements the service logic. The server -is responsible for processing the client requests and updating its own state as -well as administrative data (users data, budgets, query archives, etc.) in -the administration database. - -The service is not responsible for storing and managing private datasets, -these are usually already stored on the provider's infrastructure. - -.. See our white paper (TODO link) for detailed explanation of the platform. - -You can find our `GitHub repository `_ -following this link. - -Client -======== -The ``lomas_client`` library is a client to interact with the Lomas server. It is available on -Pypi. Reasearcher and Data Scientists 'using' the service to query the sensitive data will -only interact with the client and never with the server. - -Utilizing this client library is strongly advised for querying and interacting with the -server, as it takes care of all the necessary tasks such as serialization, deserialization, -REST API calls, and ensures the correct installation of other required libraries. In short, -it enables a seamless interaction with the server. - -For additional informations about the client, please see the -`README.md `_ of -the client and for additional examples please see the -:doc:`examples ` section. - - -Server -======== -The server side, implemented in a micro-service architecture, is composed of two main services: - -- A client-facing HTTP server, that uses FastAPI for processing user requests and executing diverse queries. -Its primary function is to efficiently handle incoming requests from the client (user) and to execute the different -queries (SmartnoiseSQL, OpenDP, etc.). - - -- A MongoDB administration database to manage the server state. This database serves as a repository for user and metadata about the dataset. User-related data include access permissions to specific datasets, allocated budgets for each user, remaining budgets and queries executed so far by the user (that we also refer to as "archives"). Dataset-related data includes details such as dataset names, information and credentials for accessing the sensitive dataset (e.g., S3, local, HTTP), and references to associated metadata. - - -The server connects to external databases, typically deployed by a data owner, to download the -sensitive datasets for query execution. Currently, the server can manage adapters to S3, -http file download and local files. - -For extensive informations about how to administrate the MongoDB database, -please refer to :doc:`Administration ` section. - -We aim to facilitate the platform configuration, deployment and testing on commonly available -IT infrastructure for NSOs and other potential users. - -In this regard, we provide two Helm charts for deploying the server components (server and -MongoDB database) and a client development environment in a Kubernetes cluster. - -For extensive informations about how to deploy, please refer to :doc:`Deployment ` -documentation. - -History -======== -The starting point of our platform was the code shared to us by `Oblivious `_. - -They originally developed a client/server platform for the `UN PET Lab Hackathon 2022 `_. - -.. toctree:: - :maxdepth: 2 - :caption: Client - :hidden: - - client_quickstart - client_examples - client_errors - client_contributing - -.. toctree:: - :maxdepth: 2 - :caption: Server - :hidden: - - server_deployment - server_administration - server_contributing - -.. toctree:: - :maxdepth: 2 - :caption: Python API - :hidden: - - api - -.. toctree:: - :maxdepth: 2 - :caption: Contributing - :hidden: - - CONTRIBUTING.md - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/html/de/_sources/lomas_client.rst.txt b/html/de/_sources/lomas_client.rst.txt deleted file mode 100644 index 95dd1270..00000000 --- a/html/de/_sources/lomas_client.rst.txt +++ /dev/null @@ -1,21 +0,0 @@ -lomas\_client package -===================== - -Submodules ----------- - -lomas\_client.client module ---------------------------- - -.. automodule:: lomas_client.client - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: lomas_client - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/lomas_server.admin_database.rst.txt b/html/de/_sources/lomas_server.admin_database.rst.txt deleted file mode 100644 index f0c219ad..00000000 --- a/html/de/_sources/lomas_server.admin_database.rst.txt +++ /dev/null @@ -1,45 +0,0 @@ -lomas\_server.admin\_database package -===================================== - -Submodules ----------- - -lomas\_server.admin\_database.admin\_database module ----------------------------------------------------- - -.. automodule:: lomas_server.admin_database.admin_database - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.admin\_database.mongodb\_database module ------------------------------------------------------- - -.. automodule:: lomas_server.admin_database.mongodb_database - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.admin\_database.utils module ------------------------------------------- - -.. automodule:: lomas_server.admin_database.utils - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.admin\_database.yaml\_database module ---------------------------------------------------- - -.. automodule:: lomas_server.admin_database.yaml_database - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: lomas_server.admin_database - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/lomas_server.administration.rst.txt b/html/de/_sources/lomas_server.administration.rst.txt deleted file mode 100644 index a735c2ed..00000000 --- a/html/de/_sources/lomas_server.administration.rst.txt +++ /dev/null @@ -1,10 +0,0 @@ -lomas\_server.administration package -==================================== - -Module contents ---------------- - -.. automodule:: lomas_server.administration - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/lomas_server.dataset_store.rst.txt b/html/de/_sources/lomas_server.dataset_store.rst.txt deleted file mode 100644 index 6028ee45..00000000 --- a/html/de/_sources/lomas_server.dataset_store.rst.txt +++ /dev/null @@ -1,53 +0,0 @@ -lomas\_server.dataset\_store package -==================================== - -Submodules ----------- - -lomas\_server.dataset\_store.basic\_dataset\_store module ---------------------------------------------------------- - -.. automodule:: lomas_server.dataset_store.basic_dataset_store - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.dataset\_store.dataset\_store module --------------------------------------------------- - -.. automodule:: lomas_server.dataset_store.dataset_store - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.dataset\_store.lru\_dataset\_store module -------------------------------------------------------- - -.. automodule:: lomas_server.dataset_store.lru_dataset_store - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.dataset\_store.private\_dataset\_observer module --------------------------------------------------------------- - -.. automodule:: lomas_server.dataset_store.private_dataset_observer - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.dataset\_store.utils module ------------------------------------------ - -.. automodule:: lomas_server.dataset_store.utils - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: lomas_server.dataset_store - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/lomas_server.dp_queries.dp_libraries.rst.txt b/html/de/_sources/lomas_server.dp_queries.dp_libraries.rst.txt deleted file mode 100644 index 42dcbc7a..00000000 --- a/html/de/_sources/lomas_server.dp_queries.dp_libraries.rst.txt +++ /dev/null @@ -1,37 +0,0 @@ -lomas\_server.dp\_queries.dp\_libraries package -=============================================== - -Submodules ----------- - -lomas\_server.dp\_queries.dp\_libraries.opendp module ------------------------------------------------------ - -.. automodule:: lomas_server.dp_queries.dp_libraries.opendp - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.dp\_queries.dp\_libraries.smartnoise\_sql module --------------------------------------------------------------- - -.. automodule:: lomas_server.dp_queries.dp_libraries.smartnoise_sql - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.dp\_queries.dp\_libraries.utils module ----------------------------------------------------- - -.. automodule:: lomas_server.dp_queries.dp_libraries.utils - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: lomas_server.dp_queries.dp_libraries - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/lomas_server.dp_queries.rst.txt b/html/de/_sources/lomas_server.dp_queries.rst.txt deleted file mode 100644 index f874287d..00000000 --- a/html/de/_sources/lomas_server.dp_queries.rst.txt +++ /dev/null @@ -1,45 +0,0 @@ -lomas\_server.dp\_queries package -================================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - lomas_server.dp_queries.dp_libraries - -Submodules ----------- - -lomas\_server.dp\_queries.dp\_logic module ------------------------------------------- - -.. automodule:: lomas_server.dp_queries.dp_logic - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.dp\_queries.dp\_querier module --------------------------------------------- - -.. automodule:: lomas_server.dp_queries.dp_querier - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.dp\_queries.dummy\_dataset module ------------------------------------------------ - -.. automodule:: lomas_server.dp_queries.dummy_dataset - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: lomas_server.dp_queries - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/lomas_server.private_dataset.rst.txt b/html/de/_sources/lomas_server.private_dataset.rst.txt deleted file mode 100644 index 1b508053..00000000 --- a/html/de/_sources/lomas_server.private_dataset.rst.txt +++ /dev/null @@ -1,53 +0,0 @@ -lomas\_server.private\_dataset package -====================================== - -Submodules ----------- - -lomas\_server.private\_dataset.in\_memory\_dataset module ---------------------------------------------------------- - -.. automodule:: lomas_server.private_dataset.in_memory_dataset - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.private\_dataset.path\_dataset module ---------------------------------------------------- - -.. automodule:: lomas_server.private_dataset.path_dataset - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.private\_dataset.private\_dataset module ------------------------------------------------------- - -.. automodule:: lomas_server.private_dataset.private_dataset - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.private\_dataset.s3\_dataset module -------------------------------------------------- - -.. automodule:: lomas_server.private_dataset.s3_dataset - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.private\_dataset.utils module -------------------------------------------- - -.. automodule:: lomas_server.private_dataset.utils - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: lomas_server.private_dataset - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/lomas_server.rst.txt b/html/de/_sources/lomas_server.rst.txt deleted file mode 100644 index 135502c8..00000000 --- a/html/de/_sources/lomas_server.rst.txt +++ /dev/null @@ -1,67 +0,0 @@ -lomas\_server package -===================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - lomas_server.admin_database - lomas_server.administration - lomas_server.dataset_store - lomas_server.dp_queries - lomas_server.private_dataset - lomas_server.tests - lomas_server.utils - -Submodules ----------- - -lomas\_server.app module ------------------------- - -.. automodule:: lomas_server.app - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.constants module ------------------------------- - -.. automodule:: lomas_server.constants - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.mongodb\_admin module ------------------------------------ - -.. automodule:: lomas_server.mongodb_admin - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.mongodb\_admin\_cli module ----------------------------------------- - -.. automodule:: lomas_server.mongodb_admin_cli - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.uvicorn\_serve module ------------------------------------ - -.. automodule:: lomas_server.uvicorn_serve - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: lomas_server - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/lomas_server.tests.rst.txt b/html/de/_sources/lomas_server.tests.rst.txt deleted file mode 100644 index fb443c02..00000000 --- a/html/de/_sources/lomas_server.tests.rst.txt +++ /dev/null @@ -1,45 +0,0 @@ -lomas\_server.tests package -=========================== - -Submodules ----------- - -lomas\_server.tests.constants module ------------------------------------- - -.. automodule:: lomas_server.tests.constants - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.tests.test\_api module ------------------------------------- - -.. automodule:: lomas_server.tests.test_api - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.tests.test\_dummy\_generation module --------------------------------------------------- - -.. automodule:: lomas_server.tests.test_dummy_generation - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.tests.test\_mongodb\_admin module ------------------------------------------------ - -.. automodule:: lomas_server.tests.test_mongodb_admin - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: lomas_server.tests - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/lomas_server.utils.rst.txt b/html/de/_sources/lomas_server.utils.rst.txt deleted file mode 100644 index 3c91f5c3..00000000 --- a/html/de/_sources/lomas_server.utils.rst.txt +++ /dev/null @@ -1,77 +0,0 @@ -lomas\_server.utils package -=========================== - -Submodules ----------- - -lomas\_server.utils.anti\_timing\_att module --------------------------------------------- - -.. automodule:: lomas_server.utils.anti_timing_att - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.utils.collections\_models module ----------------------------------------------- - -.. automodule:: lomas_server.utils.collections_models - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.utils.config module ---------------------------------- - -.. automodule:: lomas_server.utils.config - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.utils.error\_handler module ------------------------------------------ - -.. automodule:: lomas_server.utils.error_handler - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.utils.example\_inputs module ------------------------------------------- - -.. automodule:: lomas_server.utils.example_inputs - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.utils.input\_models module ----------------------------------------- - -.. automodule:: lomas_server.utils.input_models - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.utils.loggr module --------------------------------- - -.. automodule:: lomas_server.utils.loggr - :members: - :undoc-members: - :show-inheritance: - -lomas\_server.utils.utils module --------------------------------- - -.. automodule:: lomas_server.utils.utils - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: lomas_server.utils - :members: - :undoc-members: - :show-inheritance: diff --git a/html/de/_sources/notebooks/Demo_Client_Notebook.ipynb.txt b/html/de/_sources/notebooks/Demo_Client_Notebook.ipynb.txt deleted file mode 100644 index 2ace8fd4..00000000 --- a/html/de/_sources/notebooks/Demo_Client_Notebook.ipynb.txt +++ /dev/null @@ -1,1412 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3f18d338", - "metadata": {}, - "source": [ - "# Lomas: Client demo" - ] - }, - { - "cell_type": "markdown", - "id": "1582a2ae", - "metadata": {}, - "source": [ - "This notebook showcases how researcher could use the Lomas platform. It explains the different functionnalities provided by the `lomas-client` library to interact with the secure server.\n", - "\n", - "The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.\n", - "\n", - "Each user has access to one or multiple projects and for each dataset has a limited budget with $\\epsilon$ and $\\delta$ values." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "23bb4f13-7800-41b2-b429-68c2d02243d0", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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P/i1WfzOMjsFhZBuOCe6juo8qnMqAABR0XeGX/z8D3YMQDEioEOr+fPfsEOJXnlloR1CsXhr4VLnJj5fIZ1+nYsHc012CoBC+MtfnkqozpoOBUAP/flJO4Z8Uxiabjjn9ecHn8j7DysAACSj7/b8YAQULwI6OJ5/boFzkxdEJTlU/bWcqAixqlO6f1FUb82WbuLvueePdgxAOXh70bt2KE5V2z/xiQ8lnK++dPvN5ripE+0z4nSephRdYSx6K3EfqcrQNde833z7O//Suo++9n8/by686Ez7jDhdY59/foEdAwAge/qe4P2O4P3TdWnOB893mlrwWvT2UjsEoNjQBl0BlEIbdKoyqVJZLt38ecd1o3HVVZfYsY4rlrbfikW620PPW7QouNRGPtosKNR+og264kIbdOEKdR73fyb05TqsDdB//ep3EkrbJTsX6DM9b97LZtmy1a0/xtTXD4md6yeZc889PWU7o25nCMuXrUp4T81j3PjR5uyzZwV2hJBuGyupzgXex3U++sAHzzN/e3xe63VKy3HBBbPN6WfMdMZFpdWeeur52HOWtC6zblLGjx9pZs8+qcPnTf86KUD1vq+XvypssmuptvGjj8519pFbelnhn5b34ovPCu1owr88Ombuv/9hszC2bbSv3XmoiQSXttfXv/FFO5ZIP9j98pdtVaD0feALX7jRjrXp6DHVkX2ZD7RBB5Q+2qBL7zu6/1qU7DUdvRaJ+9pknVulao8122u3e21qbNyUUBPIXYewtrz921XX62FDB5u//W1e63LoenjBhbMT2vDL5fej0aNHtFt3zWf27Fl522733/+IefyxZ+1YfDlWrFhj5se+M2j7aR5TY+t91VWXtl7Xw7axlrW+oa7g7aVnqtjboCOgK4BiD+j0gf7SF79px+InH32QvScpndS+852v2LHk9OV+wSsLW79suydEfbBXrVqT9KISdHLUF/1UJyv3pOSeTETzPiP2BT/oS346Fzf/TYQed09I3hsSd3t15ESUznJ4qYTMt+/8mR2LO2nW8YEdOLjbZMXy1QkXANH7BPXm5F+eIP5lDLsYuxeFZBfjoJtyraNu1txt7F4YZp95UtJGbTuyvl7F8KUiagR04Qp1Hle7j94vOzq/XHHFhVn1AvrjH/82IZzx0zF67TWXhQYiqgqj6pnuOS+MSpH5PyP5COh0/hfv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", - "text/plain": [ - "" - ] - }, - "execution_count": 1, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_client.png\", width=800)" - ] - }, - { - "cell_type": "markdown", - "id": "5b73135c", - "metadata": {}, - "source": [ - "🐧🐧🐧\n", - "In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.\n", - "\n", - "Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.\n", - "\n", - "This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library `lomas-client`. \n", - "🐧🐧🐧" - ] - }, - { - "cell_type": "markdown", - "id": "01ae30d2", - "metadata": {}, - "source": [ - "## Step 1: Install the library\n", - "To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library `lomas-client` on her local developping environment. \n", - "\n", - "It can be installed via the pip command:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "28fbdd79-8c15-49a9-bcf9-fcdeac09d2b5", - "metadata": {}, - "outputs": [], - "source": [ - "#!pip install lomas-client" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "6fb569fc", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client import Client\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "9c63718b", - "metadata": {}, - "source": [ - "## Step 2: Initialise the client\n", - "\n", - "Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server. \n", - "\n", - "To create the client, Dr. Antartica needs to give it a few parameters:\n", - "- a url: the root application endpoint to the remote secure server.\n", - "- user_name: her name as registered in the database (Dr. Alice Antartica)\n", - "- dataset_name: the name of the dataset that she wants to query (PENGUIN)\n", - "\n", - "She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit (as is done in the Admin Notebook for Users and Datasets management)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "941991f7", - "metadata": {}, - "outputs": [], - "source": [ - "APP_URL = \"http://localhost:80\"\n", - "USER_NAME = \"Dr. Antartica\"\n", - "DATASET_NAME = \"PENGUIN\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "markdown", - "id": "0ec400c8", - "metadata": {}, - "source": [ - "And that's it for the preparation. She is now ready to use the various functionnalities offered by `lomas_client`." - ] - }, - { - "cell_type": "markdown", - "id": "9b9a5f13", - "metadata": {}, - "source": [ - "## Step 3: Understand the functionnalities of the library" - ] - }, - { - "cell_type": "markdown", - "id": "c7cb5531", - "metadata": {}, - "source": [ - "### Getting dataset metadata\n", - "\n", - "Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the `get_dataset_metadata()` function of the client. As this is public information, this does not cost any budget.\n", - "\n", - "This function returns metadata information in a format based on [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). Any metadata is required for Smartnoise-SQL is also required here and additional information such that the different categories in a string type column column can be added." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "0fdebac9-57fc-4410-878b-5a77425af634", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "penguin_metadata = client.get_dataset_metadata()\n", - "penguin_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "d338ed96", - "metadata": {}, - "source": [ - "Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) with their associated categories (i.e. the species column has 3 possibilities: 'Adelie', 'Chinstrap', 'Gentoo') and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds (i.e. the body mass of penguin ranges from 2000 to 7000 gramms). She also knows based on the field `max_ids: 1` that each penguin can only be once in the dataset and on the field `row_privacy: True` that each row represents a single penguin. " - ] - }, - { - "cell_type": "markdown", - "id": "5a3c899d", - "metadata": {}, - "source": [ - "### Get a dummy dataset\n", - "\n", - "Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset. \n", - "\n", - "Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets.\n", - "Getting a dummy dataset does not affect the budget as there is no differential privacy here. It is not a synthetic dataset and all that could be learn here is already present in the public metadata (it is created randomly on the fly based on the metadata).\n", - "\n", - "Dr. Antartica first create a dummy dataset with 200 rows and chooses a seed of 0." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "01f4365a", - "metadata": {}, - "outputs": [], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "3f553b29", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0GentooBiscoe49.20847316.117959190.1259502873.291927FEMALE
1GentooTorgersen55.03162819.963435242.9291423639.940005FEMALE
2ChinstrapTorgersen51.09671816.777518159.9614935401.743330MALE
3AdelieBiscoe49.07091114.796037244.5301532316.038092MALE
4ChinstrapBiscoe44.82791813.246787236.9488535036.246870FEMALE
\n", - "
" - ], - "text/plain": [ - " species island bill_length_mm bill_depth_mm flipper_length_mm \n", - "0 Gentoo Biscoe 49.208473 16.117959 190.125950 \\\n", - "1 Gentoo Torgersen 55.031628 19.963435 242.929142 \n", - "2 Chinstrap Torgersen 51.096718 16.777518 159.961493 \n", - "3 Adelie Biscoe 49.070911 14.796037 244.530153 \n", - "4 Chinstrap Biscoe 44.827918 13.246787 236.948853 \n", - "\n", - " body_mass_g sex \n", - "0 2873.291927 FEMALE \n", - "1 3639.940005 FEMALE \n", - "2 5401.743330 MALE \n", - "3 2316.038092 MALE \n", - "4 5036.246870 FEMALE " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset(\n", - " nb_rows = NB_ROWS, \n", - " seed = SEED\n", - ")\n", - "\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "98e6fda2-dde7-4f8b-a787-c9a1e3571ebe", - "metadata": {}, - "source": [ - "### Query on dummy dataset\n", - "Now that she has an idea of what the data looks like, she wants to start querying the real dataset for her research. \n", - "\n", - "However, her budget is limited and it would be a waste to spend it by mistake on a coding error. Therefore the client/server pipeline has functionnal testing capabilities for the users. It is possible to test a query on a `dummy` dataset to ensure that everything is working properly. Dr. Antartica will not be able to use the results of a dummy query for her analysis (as the data is random) but if the query on the dummy dataset works, she can be confident that her query will also work on the real dataset.\n", - "This functionnal testing on the dummy does not have any impact on the budget as it is on random data only.\n", - "\n", - "To test on the dummy data instead of the real data, the function call is exactly the same with the only exception of the flag `dummy=True`. In the following cell, she will test with `smartnoise_query` but it is the same flag for `opendp_query`. She can optionnaly give two additional parameters to set the seed and the number of rows of the dummy dataset.\n", - "\n", - "Another more advanced possibility for functionnal tests with the dummy is to compare results of queries on a local dummy and the remote dummy with a very high budget: \n", - "- create a local dummy on the notebook with a specific seed and number of rows\n", - "- compute locally the wanted query on this local dummy with python functions like numpy\n", - "- query the server on the same remote dummy with (`dummy=True`, same seed and same number of row) and a very big buget to limit noise as much as possible (don't worry this won't cost any real budget)\n", - "- compare and verify that the local and remote dummy have similar results." - ] - }, - { - "cell_type": "markdown", - "id": "243c73e3-daec-45d6-a3c8-ae1d60439ec4", - "metadata": {}, - "source": [ - "#### Average and number of rows with smartnoise-sql library on remote dummy" - ] - }, - { - "cell_type": "markdown", - "id": "d1f8ea18-ccab-4f75-9490-b4d1144b39db", - "metadata": {}, - "source": [ - "Dr. Antartica will now try a query to get the number of penguin and their average bill length (in mm) on the dummy dataset. She does not forget to \n", - "- set the `dummy` flag to True\n", - "- set very high budget values to be able to compare results with a similar local dummy (with the same seed and number of rows) if she wants to verify that the function do what is expected. Here she will just check that the number of rows is close to what she sets as parameter." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3946425d", - "metadata": {}, - "outputs": [], - "source": [ - "# Number of penguin and average bill length in mm\n", - "QUERY = \"SELECT COUNT(*) AS nb_penguins, \\\n", - " AVG(bill_length_mm) AS avg_bill_length_mm \\\n", - " FROM df\"" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "90cf2a6d", - "metadata": {}, - "outputs": [], - "source": [ - "# On the remote server dummy dataframe\n", - "dummy_res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0,\n", - " delta = 0.99,\n", - " dummy = True, \n", - " nb_rows = NB_ROWS,\n", - " seed = SEED\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "a30f277e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average bill length in remote dummy: 47.52mm.\n", - "Number of rows in remote dummy: 199.\n" - ] - } - ], - "source": [ - "print(f\"Average bill length in remote dummy: {np.round(dummy_res['query_response']['avg_bill_length_mm'][0], 2)}mm.\")\n", - "print(f\"Number of rows in remote dummy: {np.round(dummy_res['query_response']['nb_penguins'][0], 2)}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "167e8c6d-6c93-4ab4-9ba7-bf7e783a6bc2", - "metadata": {}, - "source": [ - "No functionnal errors happened and the estimated number of rows is very close (if not equal) to the number of rows that she set for the dummy dataframe. She is now even more confident in using her query on the server." - ] - }, - { - "cell_type": "markdown", - "id": "324454ed", - "metadata": {}, - "source": [ - "### Get current budget\n", - "\n", - "It is the first time that Dr. Antartica connects to the server and she wants to know how much buget the queen assigned her.\n", - "Therefore, she calls the fonction `get_initial_budget`." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "61a467f3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'initial_epsilon': 10.0, 'initial_delta': 0.005}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_initial_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "bc8f7a74", - "metadata": {}, - "source": [ - "She sees that she has 10.0 epsilon and 0.005 epsilon at her disposal.\n", - "\n", - "Then she checks her total spent budget `get_total_spent_budget`. As she only did queries on metadata on dummy dataframes, this should still be 0." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "afd22f84", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "05daf5a4", - "metadata": {}, - "source": [ - "It will also be useful to know what the remaining budget is. Therefore, she calls the function `get_remaining_budget`. It just substarcts the total spent budget from the initial budget." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "6260cf54", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10.0, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "20298e00", - "metadata": {}, - "source": [ - "As expected, for now the remaining budget is equal to the inital budget." - ] - }, - { - "cell_type": "markdown", - "id": "b746374c", - "metadata": {}, - "source": [ - "### Estimate cost of a query\n", - "Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The `estimate cost` function returns the estimated real cost of any query.\n", - "\n", - "Again, of course, this will not impact the user's budget.\n", - "\n", - "Dr. Antartica checks the budget that computing the average bill length will really cost her if she asks the query with an `epsilon` and a `delta`." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "fd5ed08a", - "metadata": {}, - "outputs": [], - "source": [ - "EPSILON = 0.5\n", - "DELTA = 1e-4" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "133020c6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 1.5, 'delta_cost': 0.00014999500000001387}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "3c6a3a8c", - "metadata": {}, - "source": [ - "This query would actually cost her 1.5 epsilon and delta 1.4999e-4. She decides that it is good enough." - ] - }, - { - "cell_type": "markdown", - "id": "e5379edf", - "metadata": {}, - "source": [ - "### Query on real private dataset with smartnoise-sql\n", - "Now that all the safeguard functions were tested, Dr. Antartica is ready to query on the real dataset and get a differentially private response of the number of penguins and average bill length. By default, the flag `dummy` is False so setting it is optional. She uses the values of `epsilon` and `delta` that she selected just before.\n", - "\n", - "Careful: This command DOES spend the budget of the user and the remaining budget is updated for every query." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "19e60263", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10.0, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "69767fac", - "metadata": {}, - "outputs": [], - "source": [ - "response = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA,\n", - " dummy = False # Optionnal\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "6dbbdf93", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins in real data: 342.\n", - "Average bill length of penguins in real data: 42.93mm.\n" - ] - } - ], - "source": [ - "nb_penguins = response['query_response']['nb_penguins'].iloc[0]\n", - "print(f\"Number of penguins in real data: {nb_penguins}.\")\n", - "\n", - "avg_bill_length = np.round(response['query_response']['avg_bill_length_mm'].iloc[0], 2)\n", - "print(f\"Average bill length of penguins in real data: {avg_bill_length}mm.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b2767e65", - "metadata": {}, - "source": [ - "After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "4ced8a56-e2c3-4bd1-b94b-95cbbcd58a15", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'requested_by': 'Dr. Antartica',\n", - " 'query_response': nb_penguins avg_bill_length_mm\n", - " 0 342 42.930651,\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "response" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "39701fe5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 8.5, 'remaining_delta': 0.004850004999999986}" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "e37c587f", - "metadata": {}, - "source": [ - "As can be seen in `get_total_spent_budget()`, it is the budget estimated with `estimate_smartnoise_cost()` that was spent." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "487f835f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 1.5, 'total_spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "eef4afcd", - "metadata": {}, - "source": [ - "Dr. Antartica has now a differentially private estimation of the number of penguins in the dataset and is confident to use the library for the rest of her analyses." - ] - }, - { - "cell_type": "markdown", - "id": "04929993", - "metadata": {}, - "source": [ - "## Step 4: Penguin statistics with opendp" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "b9685226", - "metadata": {}, - "outputs": [], - "source": [ - "import opendp as dp\n", - "import opendp.transformations as trans\n", - "import opendp.measurements as meas" - ] - }, - { - "cell_type": "markdown", - "id": "bbbca191", - "metadata": {}, - "source": [ - "### Confidence intervals for bill length over the whole population" - ] - }, - { - "cell_type": "markdown", - "id": "9d41bd58", - "metadata": {}, - "source": [ - "She is first interested to have a better idea of the distribution of bill length of all species. She already has the count and average from the previous step, so she only needs the variance values.\n", - "\n", - "She first checks the metadata again to use the relevant values in the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "4331d86f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "penguin_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "f90e0425", - "metadata": {}, - "source": [ - "She can define the columns names and the bounds of the relevant column." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "ff8cb7b6", - "metadata": {}, - "outputs": [], - "source": [ - "columns = [\"species\", \"island\", \"bill_length_mm\", \"bill_depth_mm\", \"flipper_length_mm\", \"body_mass_g\", \"sex\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "70b2bdb1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(30.0, 65.0)" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bill_length_min = penguin_metadata['columns']['bill_length_mm']['lower']\n", - "bill_length_max = penguin_metadata['columns']['bill_length_mm']['upper']\n", - "bill_length_min, bill_length_max" - ] - }, - { - "cell_type": "markdown", - "id": "e93ae087", - "metadata": {}, - "source": [ - "She can now define the pipeline of the transformation to have the variance that she wants on the data:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "75e4933b", - "metadata": {}, - "outputs": [], - "source": [ - "bill_length_transformation_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"bill_length_mm\", TOA=str) >>\n", - " trans.then_cast_default(TOA=float) >>\n", - " trans.then_clamp(bounds=(bill_length_min, bill_length_max)) >>\n", - " trans.then_resize(size=nb_penguins.tolist(), constant=avg_bill_length) >>\n", - " trans.then_variance()\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "411d464c", - "metadata": {}, - "source": [ - "However, when she tries to execute it on the server, she has an error (see below). " - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "8041a647", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Server error status 400: {\"InvalidQueryException\":\"The pipeline provided is not a measurement. It cannot be processed in this server.\"}\n" - ] - } - ], - "source": [ - "# Expect to fail !!!\n", - "client.opendp_query(\n", - " opendp_pipeline = bill_length_transformation_pipeline,\n", - " dummy=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d06c59dc", - "metadata": {}, - "source": [ - "This is because the server will only allow measurement pipeline with differentially private results. She adds Laplacian noise to the pipeline and should be able to instantiate the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "b8162859", - "metadata": {}, - "outputs": [], - "source": [ - "var_bill_length_measurement_pipeline = (\n", - " bill_length_transformation_pipeline >>\n", - " meas.then_laplace(scale=5.0)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "fc7e0ecd", - "metadata": {}, - "source": [ - "Now that there is a measurement, she is able to apply the pipeline on the dummy dataset of the server." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "df61bce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for variance: 27.36\n" - ] - } - ], - "source": [ - "dummy_var_res = client.opendp_query(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for variance: {np.round(dummy_var_res['query_response'], 2)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "ded11ac4", - "metadata": {}, - "source": [ - "With opendp, the function `estimate_opendp_cost` is particularly useful to estimate the used `epsilon` and `delta` based on the `scale` value." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "7ae7f735", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 0.7163742690067888, 'delta_cost': 0}" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1c791d36", - "metadata": {}, - "source": [ - "She can now execute the query on the real dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "085555a5", - "metadata": {}, - "outputs": [], - "source": [ - "var_res = client.opendp_query(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "674332e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins: 342 (from previous smartnoise-sql query).\n", - "Average bill length: 42.93 (from previous smartnoise-sql query).\n", - "Variance of bill length: 27.686 (from opendp query).\n" - ] - } - ], - "source": [ - "print(f\"Number of penguins: {nb_penguins} (from previous smartnoise-sql query).\")\n", - "\n", - "print(f\"Average bill length: {np.round(avg_bill_length, 2)} (from previous smartnoise-sql query).\")\n", - "\n", - "var_bill_length = var_res['query_response']\n", - "print(f\"Variance of bill length: {np.round(var_bill_length, 3)} (from opendp query).\")" - ] - }, - { - "cell_type": "markdown", - "id": "367081be-1159-45d8-9129-88fba20fb697", - "metadata": {}, - "source": [ - "She can now do all the postprocessing that she wants with the returned data without adding any privacy risk. " - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "f72b19d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Standard error of bill length: 0.28.\n" - ] - } - ], - "source": [ - "# Get standard error\n", - "standard_error = np.sqrt(var_bill_length/nb_penguins)\n", - "print(f\"Standard error of bill length: {np.round(standard_error, 2)}.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "62630a03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The 95% confidence interval of the bill length of all penguins is [42.37, 43.49].\n" - ] - } - ], - "source": [ - " # Compute the 95% confidence interval\n", - "ZSCORE = 1.96\n", - "lower_bound = np.round(avg_bill_length - ZSCORE*standard_error, 2)\n", - "upper_bound = np.round(avg_bill_length + ZSCORE*standard_error, 2)\n", - "print(f\"The 95% confidence interval of the bill length of all penguins is [{lower_bound}, {upper_bound}].\")" - ] - }, - { - "cell_type": "markdown", - "id": "0d30d98e-26f4-44ec-a0f0-7a039f344860", - "metadata": {}, - "source": [ - "### Count per species" - ] - }, - { - "cell_type": "markdown", - "id": "b6a3cc00-8734-4479-81a3-781c91b8eb06", - "metadata": {}, - "source": [ - "She can also creates an histogram of the number of penguin per species.\n", - "\n", - "She first extract the categories from the metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "558916a6-78a9-4589-abe8-41472f0c66d7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Adelie', 'Chinstrap', 'Gentoo']" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "categories = penguin_metadata['columns']['species']['categories']\n", - "categories" - ] - }, - { - "cell_type": "markdown", - "id": "5f05aeb0-42d1-4444-a458-d2989494f544", - "metadata": {}, - "source": [ - "Then, writes the pipeline:" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "505a2793-6750-4f2a-9a9d-09f3a4ae4099", - "metadata": {}, - "outputs": [], - "source": [ - "species_count_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"species\", TOA=str) >>\n", - " trans.then_count_by_categories(categories=categories) >>\n", - " meas.then_laplace(scale=0.5)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "0f4109a9-c2f2-4365-bfa1-b5b2512919f9", - "metadata": {}, - "source": [ - "Verify it works on the dummy:" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "712b1c74-cac6-4b3f-9421-8d8ffa9debc7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for histogram: [38, 33, 28, 0]\n" - ] - } - ], - "source": [ - "dummy_res = client.opendp_query(\n", - " opendp_pipeline = species_count_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for histogram: {dummy_res['query_response']}\")" - ] - }, - { - "cell_type": "markdown", - "id": "66de7794-9aae-41d1-ba98-6b234a05d6f8", - "metadata": {}, - "source": [ - "Checks the required cost:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "3f942b1b-94bc-468e-8374-3034f2e6b8ce", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 2.0, 'delta_cost': 0}" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = species_count_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1adfe446-ff13-41f4-9f3c-9998e2ae0f00", - "metadata": {}, - "source": [ - "And finally apply the pipeline on the real dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "c6fe47b7-048e-404a-bedb-720e734b0c70", - "metadata": {}, - "outputs": [], - "source": [ - "species_counts_res = client.opendp_query(\n", - " opendp_pipeline = species_count_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "78d1aa05-2777-476b-9520-f9206399670d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'requested_by': 'Dr. Antartica',\n", - " 'query_response': [152, 68, 124, 0],\n", - " 'spent_epsilon': 2.0,\n", - " 'spent_delta': 0}" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "species_counts_res" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "b8d5fd82-5ded-47ec-8135-f6870865055d", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Species Adelie has 152 penguins.\n", - "Species Chinstrap has 68 penguins.\n", - "Species Gentoo has 124 penguins.\n", - "Species Unknown has 0 penguins.\n" - ] - } - ], - "source": [ - "for i, count in enumerate(species_counts_res['query_response']):\n", - " if i == len(categories):\n", - " print(f\"Species Unknown has {count} penguins.\")\n", - " else:\n", - " print(f\"Species {categories[i]} has {count} penguins.\")" - ] - }, - { - "cell_type": "markdown", - "id": "94eaf59b-c108-424c-8978-b1c86e141ccb", - "metadata": {}, - "source": [ - "## Step 5: See archives of queries" - ] - }, - { - "cell_type": "markdown", - "id": "64003c53-de56-4bdc-a3c2-0c3e40031919", - "metadata": {}, - "source": [ - "She now wants to verify all the queries that she did on the real data. It is possible because an archive of all queries is kept in a secure database. With a function call she can see her queries, budget and associated responses." - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "008fd230-cdfd-4e03-91ce-5a60b06c106d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'query_str': 'SELECT COUNT(*) AS nb_penguins, AVG(bill_length_mm) AS avg_bill_length_mm FROM df',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'epsilon': 0.5,\n", - " 'delta': 0.0001,\n", - " 'mechanisms': {},\n", - " 'postprocess': True},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': {'index': [0],\n", - " 'columns': ['nb_penguins', 'avg_bill_length_mm'],\n", - " 'data': [[342, 42.93065072561188]],\n", - " 'index_names': [None],\n", - " 'column_names': [None]},\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387},\n", - " 'timestamp': 1717666971.0667355,\n", - " 'dp_librairy': 'smartnoise_sql'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': 27.68648927291686,\n", - " 'spent_epsilon': 0.7163742690067888,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1717666974.674265,\n", - " 'dp_librairy': 'opendp'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': [152, 68, 124, 0],\n", - " 'spent_epsilon': 2.0,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1717666977.146055,\n", - " 'dp_librairy': 'opendp'}]" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "previous_queries = client.get_previous_queries()\n", - "previous_queries" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.0rc1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/notebooks/Demo_Client_Notebook_polars.ipynb.txt b/html/de/_sources/notebooks/Demo_Client_Notebook_polars.ipynb.txt deleted file mode 100644 index 7a2f05e4..00000000 --- a/html/de/_sources/notebooks/Demo_Client_Notebook_polars.ipynb.txt +++ /dev/null @@ -1,1883 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3f18d338", - "metadata": {}, - "source": [ - "# Lomas: Client demo with polar" - ] - }, - { - "cell_type": "markdown", - "id": "1582a2ae", - "metadata": {}, - "source": [ - "This notebook showcases how researcher could use the lomas platform. It explains the different functionnalities provided by the `lomas-client` library to interact with the secure server.\n", - "\n", - "The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.\n", - "\n", - "Each user has access to one or multiple projects and for each dataset has a limited budget with $\\epsilon$ and $\\delta$ values." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "23bb4f13-7800-41b2-b429-68c2d02243d0", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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P/i1WfzOMjsFhZBuOCe6juo8qnMqAABR0XeGX/z8D3YMQDEioEOr+fPfsEOJXnlloR1CsXhr4VLnJj5fIZ1+nYsHc012CoBC+MtfnkqozpoOBUAP/flJO4Z8Uxiabjjn9ecHn8j7DysAACSj7/b8YAQULwI6OJ5/boFzkxdEJTlU/bWcqAixqlO6f1FUb82WbuLvueePdgxAOXh70bt2KE5V2z/xiQ8lnK++dPvN5ripE+0z4nSephRdYSx6K3EfqcrQNde833z7O//Suo++9n8/by686Ez7jDhdY59/foEdAwAge/qe4P2O4P3TdWnOB893mlrwWvT2UjsEoNjQBl0BlEIbdKoyqVJZLt38ecd1o3HVVZfYsY4rlrbfikW620PPW7QouNRGPtosKNR+og264kIbdOEKdR73fyb05TqsDdB//ep3EkrbJTsX6DM9b97LZtmy1a0/xtTXD4md6yeZc889PWU7o25nCMuXrUp4T81j3PjR5uyzZwV2hJBuGyupzgXex3U++sAHzzN/e3xe63VKy3HBBbPN6WfMdMZFpdWeeur52HOWtC6zblLGjx9pZs8+qcPnTf86KUD1vq+XvypssmuptvGjj8519pFbelnhn5b34ovPCu1owr88Ombuv/9hszC2bbSv3XmoiQSXttfXv/FFO5ZIP9j98pdtVaD0feALX7jRjrXp6DHVkX2ZD7RBB5Q+2qBL7zu6/1qU7DUdvRaJ+9pknVulao8122u3e21qbNyUUBPIXYewtrz921XX62FDB5u//W1e63LoenjBhbMT2vDL5fej0aNHtFt3zWf27Fl522733/+IefyxZ+1YfDlWrFhj5se+M2j7aR5TY+t91VWXtl7Xw7axlrW+oa7g7aVnqtjboCOgK4BiD+j0gf7SF79px+InH32QvScpndS+852v2LHk9OV+wSsLW79suydEfbBXrVqT9KISdHLUF/1UJyv3pOSeTETzPiP2BT/oS346Fzf/TYQed09I3hsSd3t15ESUznJ4qYTMt+/8mR2LO2nW8YEdOLjbZMXy1QkXANH7BPXm5F+eIP5lDLsYuxeFZBfjoJtyraNu1txt7F4YZp95UtJGbTuyvl7F8KUiagR04Qp1Hle7j94vOzq/XHHFhVn1AvrjH/82IZzx0zF67TWXhQYiqgqj6pnuOS+MSpH5PyP5COh0/hfv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", - "text/plain": [ - "" - ] - }, - "execution_count": 1, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_client.png\", width=800)" - ] - }, - { - "cell_type": "markdown", - "id": "5b73135c", - "metadata": {}, - "source": [ - "🐧🐧🐧\n", - "In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.\n", - "\n", - "Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.\n", - "\n", - "This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library `lomas-client`. \n", - "🐧🐧🐧" - ] - }, - { - "cell_type": "markdown", - "id": "01ae30d2", - "metadata": {}, - "source": [ - "## Step 1: Install the library\n", - "To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library `lomas-client` on her local developping environment. \n", - "\n", - "It can be installed via the pip command:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "28fbdd79-8c15-49a9-bcf9-fcdeac09d2b5", - "metadata": {}, - "outputs": [], - "source": [ - "#!pip install lomas-cliententententent" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "6fb569fc", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client.client import Client\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "9c63718b", - "metadata": {}, - "source": [ - "## Step 2: Initialise the client\n", - "\n", - "Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server. \n", - "\n", - "To create the client, Dr. Antartica needs to give it a few parameters:\n", - "- a url: the root application endpoint to the remote secure server.\n", - "- user_name: her name as registered in the database (Dr. Alice Antartica)\n", - "- dataset_name: the name of the dataset that she wants to query (PENGUIN)\n", - "\n", - "She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit (as is done in the Admin Notebook for Users and Datasets management)." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "941991f7", - "metadata": {}, - "outputs": [], - "source": [ - "APP_URL = \"http://lomas_server_dev:80\"\n", - "#APP_URL = \"https://lomas-server.lab.sspcloud.fr\"\n", - "USER_NAME = \"Dr. Antartica\"\n", - "DATASET_NAME = \"PENGUIN\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "markdown", - "id": "0ec400c8", - "metadata": {}, - "source": [ - "And that's it for the preparation. She is now ready to use the various functionnalities offered by `lomas_client`." - ] - }, - { - "cell_type": "markdown", - "id": "9b9a5f13", - "metadata": {}, - "source": [ - "## Step 3: Understand the functionnalities of the library" - ] - }, - { - "cell_type": "markdown", - "id": "c7cb5531", - "metadata": {}, - "source": [ - "### Getting dataset metadata\n", - "\n", - "Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the `get_dataset_metadata()` function of the client. As this is public information, this does not cost any budget.\n", - "\n", - "This function returns metadata information in a format based on [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). Any metadata is required for Smartnoise-SQL is also required here and additional information such that the different categories in a string type column column can be added." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "d15cbe39", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "penguin_metadata = client.get_dataset_metadata()\n", - "penguin_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "d338ed96", - "metadata": {}, - "source": [ - "Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) with their associated categories (i.e. the species column has 3 possibilities: 'Adelie', 'Chinstrap', 'Gentoo') and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds (i.e. the body mass of penguin ranges from 2000 to 7000 gramms). She also knows based on the field `max_ids: 1` that each penguin can only be once in the dataset and on the field `row_privacy: True` that each row represents a single penguin. " - ] - }, - { - "cell_type": "markdown", - "id": "5a3c899d", - "metadata": {}, - "source": [ - "### Get a dummy dataset\n", - "\n", - "Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset. \n", - "\n", - "Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets.\n", - "Getting a dummy dataset does not affect the budget as there is no differential privacy here. It is not a synthetic dataset and all that could be learn here is already present in the public metadata (it is created randomly on the fly based on the metadata).\n", - "\n", - "Dr. Antartica first create a dummy dataset with 200 rows and chooses a seed of 0." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "01f4365a", - "metadata": {}, - "outputs": [], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3f553b29", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0GentooBiscoe49.20847316.117959190.1259502873.291927FEMALE
1GentooTorgersen55.03162819.963435242.9291423639.940005FEMALE
2ChinstrapTorgersen51.09671816.777518159.9614935401.743330MALE
3AdelieBiscoe49.07091114.796037244.5301532316.038092MALE
4ChinstrapBiscoe44.82791813.246787236.9488535036.246870FEMALE
\n", - "
" - ], - "text/plain": [ - " species island bill_length_mm bill_depth_mm flipper_length_mm \n", - "0 Gentoo Biscoe 49.208473 16.117959 190.125950 \\\n", - "1 Gentoo Torgersen 55.031628 19.963435 242.929142 \n", - "2 Chinstrap Torgersen 51.096718 16.777518 159.961493 \n", - "3 Adelie Biscoe 49.070911 14.796037 244.530153 \n", - "4 Chinstrap Biscoe 44.827918 13.246787 236.948853 \n", - "\n", - " body_mass_g sex \n", - "0 2873.291927 FEMALE \n", - "1 3639.940005 FEMALE \n", - "2 5401.743330 MALE \n", - "3 2316.038092 MALE \n", - "4 5036.246870 FEMALE " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset(\n", - " nb_rows = NB_ROWS, \n", - " seed = SEED\n", - ")\n", - "\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "98e6fda2-dde7-4f8b-a787-c9a1e3571ebe", - "metadata": {}, - "source": [ - "### Query on dummy dataset\n", - "Now that she has an idea of what the data looks like, she wants to start querying the real dataset for her research. \n", - "\n", - "However, her budget is limited and it would be a waste to spend it by mistake on a coding error. Therefore the client/server pipeline has functionnal testing capabilities for the users. It is possible to test a query on a `dummy` dataset to ensure that everything is working properly. Dr. Antartica will not be able to use the results of a dummy query for her analysis (as the data is random) but if the query on the dummy dataset works, she can be confident that her query will also work on the real dataset.\n", - "This functionnal testing on the dummy does not have any impact on the budget as it is on random data only.\n", - "\n", - "To test on the dummy data instead of the real data, the function call is exactly the same with the only exception of the flag `dummy=True`. In the following cell, she will test with `smartnoise_query` but it is the same flag for `opendp_query`. She can optionnaly give two additional parameters to set the seed and the number of rows of the dummy dataset.\n", - "\n", - "Another more advanced possibility for functionnal tests with the dummy is to compare results of queries on a local dummy and the remote dummy with a very high budget: \n", - "- create a local dummy on the notebook with a specific seed and number of rows\n", - "- compute locally the wanted query on this local dummy with python functions like numpy\n", - "- query the server on the same remote dummy with (`dummy=True`, same seed and same number of row) and a very big buget to limit noise as much as possible (don't worry this won't cost any real budget)\n", - "- compare and verify that the local and remote dummy have similar results." - ] - }, - { - "cell_type": "markdown", - "id": "243c73e3-daec-45d6-a3c8-ae1d60439ec4", - "metadata": {}, - "source": [ - "#### Average and number of rows with smartnoise-sql library on remote dummy" - ] - }, - { - "cell_type": "markdown", - "id": "d1f8ea18-ccab-4f75-9490-b4d1144b39db", - "metadata": {}, - "source": [ - "Dr. Antartica will now try a query to get the number of penguin and their average bill length (in mm) on the dummy dataset. She does not forget to \n", - "- set the `dummy` flag to True\n", - "- set very high budget values to be able to compare results with a similar local dummy (with the same seed and number of rows) if she wants to verify that the function do what is expected. Here she will just check that the number of rows is close to what she sets as parameter." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "3946425d", - "metadata": {}, - "outputs": [], - "source": [ - "# Number of penguin and average bill length in mm\n", - "QUERY = \"SELECT COUNT(*) AS nb_penguins, \\\n", - " AVG(bill_length_mm) AS avg_bill_length_mm \\\n", - " FROM df\"" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "90cf2a6d", - "metadata": {}, - "outputs": [], - "source": [ - "# On the remote server dummy dataframe\n", - "dummy_res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0,\n", - " delta = 0.99,\n", - " dummy = True, \n", - " nb_rows = NB_ROWS,\n", - " seed = SEED\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a30f277e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average bill length in remote dummy: 47.51mm.\n", - "Number of rows in remote dummy: 200.\n" - ] - } - ], - "source": [ - "print(f\"Average bill length in remote dummy: {np.round(dummy_res['query_response']['avg_bill_length_mm'][0], 2)}mm.\")\n", - "print(f\"Number of rows in remote dummy: {np.round(dummy_res['query_response']['nb_penguins'][0], 2)}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "167e8c6d-6c93-4ab4-9ba7-bf7e783a6bc2", - "metadata": {}, - "source": [ - "No functionnal errors happened and the estimated number of rows is very close (if not equal) to the number of rows that she set for the dummy dataframe. She is now even more confident in using her query on the server." - ] - }, - { - "cell_type": "markdown", - "id": "324454ed", - "metadata": {}, - "source": [ - "### Get current budget\n", - "\n", - "It is the first time that Dr. Antartica connects to the server and she wants to know how much buget the queen assigned her.\n", - "Therefore, she calls the fonction `get_initial_budget`." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "61a467f3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'initial_epsilon': 10, 'initial_delta': 0.005}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_initial_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "bc8f7a74", - "metadata": {}, - "source": [ - "She sees that she has 10.0 epsilon and 0.005 epsilon at her disposal.\n", - "\n", - "Then she checks her total spent budget `get_total_spent_budget`. As she only did queries on metadata on dummy dataframes, this should still be 0." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "afd22f84", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0, 'total_spent_delta': 0}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "05daf5a4", - "metadata": {}, - "source": [ - "It will also be useful to know what the remaining budget is. Therefore, she calls the function `get_remaining_budget`. It just substarcts the total spent budget from the initial budget." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "6260cf54", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "20298e00", - "metadata": {}, - "source": [ - "As expected, for now the remaining budget is equal to the inital budget." - ] - }, - { - "cell_type": "markdown", - "id": "b746374c", - "metadata": {}, - "source": [ - "### Estimate cost of a query\n", - "Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The `estimate cost` function returns the estimated real cost of any query.\n", - "\n", - "Again, of course, this will not impact the user's budget.\n", - "\n", - "Dr. Antartica checks the budget that computing the average bill length will really cost her if she asks the query with an `epsilon` and a `delta`." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "fd5ed08a", - "metadata": {}, - "outputs": [], - "source": [ - "EPSILON = 0.5\n", - "DELTA = 1e-4" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "133020c6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 1.5, 'delta_cost': 0.00014999500000001387}" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "3c6a3a8c", - "metadata": {}, - "source": [ - "This query would actually cost her 1.5 epsilon and delta 1.4999e-4. She decides that it is good enough." - ] - }, - { - "cell_type": "markdown", - "id": "e5379edf", - "metadata": {}, - "source": [ - "### Query on real private dataset with smartnoise-sql\n", - "Now that all the safeguard functions were tested, Dr. Antartica is ready to query on the real dataset and get a differentially private response of the number of penguins and average bill length. By default, the flag `dummy` is False so setting it is optional. She uses the values of `epsilon` and `delta` that she selected just before.\n", - "\n", - "Careful: This command DOES spend the budget of the user and the remaining budget is updated for every query." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "19e60263", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "69767fac", - "metadata": {}, - "outputs": [], - "source": [ - "response = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA,\n", - " dummy = False # Optionnal\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "6dbbdf93", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins in real data: 344.\n", - "Average bill length of penguins in real data: 43.84mm.\n" - ] - } - ], - "source": [ - "nb_penguins = response['query_response']['nb_penguins'].iloc[0]\n", - "print(f\"Number of penguins in real data: {nb_penguins}.\")\n", - "\n", - "avg_bill_length = np.round(response['query_response']['avg_bill_length_mm'].iloc[0], 2)\n", - "print(f\"Average bill length of penguins in real data: {avg_bill_length}mm.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b2767e65", - "metadata": {}, - "source": [ - "After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "4ced8a56-e2c3-4bd1-b94b-95cbbcd58a15", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'requested_by': 'Dr. Antartica',\n", - " 'query_response': nb_penguins avg_bill_length_mm\n", - " 0 344 43.836323,\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "response" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "39701fe5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 8.5, 'remaining_delta': 0.004850004999999986}" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "e37c587f", - "metadata": {}, - "source": [ - "As can be seen in `get_total_spent_budget()`, it is the budget estimated with `estimate_smartnoise_cost()` that was spent." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "487f835f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 1.5, 'total_spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "eef4afcd", - "metadata": {}, - "source": [ - "Dr. Antartica has now a differentially private estimation of the number of penguins in the dataset and is confident to use the library for the rest of her analyses." - ] - }, - { - "cell_type": "markdown", - "id": "04929993", - "metadata": {}, - "source": [ - "## Step 4: Penguin statistics with opendp" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "b9685226", - "metadata": {}, - "outputs": [], - "source": [ - "import opendp.prelude as dp\n", - "import opendp.transformations as trans\n", - "import opendp.measurements as meas\n", - "\n", - "dp.enable_features(\"contrib\")" - ] - }, - { - "cell_type": "markdown", - "id": "bbbca191", - "metadata": {}, - "source": [ - "### Confidence intervals for bill length over the whole population" - ] - }, - { - "cell_type": "markdown", - "id": "9d41bd58", - "metadata": {}, - "source": [ - "She is first interested to have a better idea of the distribution of bill length of all species. She already has the count and average from the previous step, so she only needs the variance values.\n", - "\n", - "She first checks the metadata again to use the relevant values in the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "4331d86f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "penguin_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "f90e0425", - "metadata": {}, - "source": [ - "She can define the columns names and the bounds of the relevant column." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "ff8cb7b6", - "metadata": {}, - "outputs": [], - "source": [ - "columns = [\"species\", \"island\", \"bill_length_mm\", \"bill_depth_mm\", \"flipper_length_mm\", \"body_mass_g\", \"sex\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "70b2bdb1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(30.0, 65.0)" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bill_length_min = penguin_metadata['columns']['bill_length_mm']['lower']\n", - "bill_length_max = penguin_metadata['columns']['bill_length_mm']['upper']\n", - "bill_length_min, bill_length_max" - ] - }, - { - "cell_type": "markdown", - "id": "e93ae087", - "metadata": {}, - "source": [ - "She can now define the pipeline of the transformation to have the variance that she wants on the data:" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "75e4933b", - "metadata": {}, - "outputs": [], - "source": [ - "bill_length_transformation_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"bill_length_mm\", TOA=str) >>\n", - " trans.then_cast_default(TOA=float) >>\n", - " trans.then_clamp(bounds=(bill_length_min, bill_length_max)) >>\n", - " trans.then_resize(size=nb_penguins.tolist(), constant=avg_bill_length) >>\n", - " trans.then_variance()\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "411d464c", - "metadata": {}, - "source": [ - "However, when she tries to execute it on the server, she has an error (see below). " - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "8041a647", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Server error status 400: {\"InvalidQueryException\":\"The pipeline provided is not a measurement. It cannot be processed in this server.\"}\n" - ] - } - ], - "source": [ - "# Expect to fail !!!\n", - "client.opendp_query(\n", - " opendp_pipeline = bill_length_transformation_pipeline,\n", - " dummy=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d06c59dc", - "metadata": {}, - "source": [ - "This is because the server will only allow measurement pipeline with differentially private results. She adds Laplacian noise to the pipeline and should be able to instantiate the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "b8162859", - "metadata": {}, - "outputs": [], - "source": [ - "var_bill_length_measurement_pipeline = (\n", - " bill_length_transformation_pipeline >>\n", - " meas.then_laplace(scale=5.0)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "fc7e0ecd", - "metadata": {}, - "source": [ - "Now that there is a measurement, she is able to apply the pipeline on the dummy dataset of the server." - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "df61bce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for variance: 32.97\n" - ] - } - ], - "source": [ - "dummy_var_res = client.opendp_query(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for variance: {np.round(dummy_var_res['query_response'], 2)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "ded11ac4", - "metadata": {}, - "source": [ - "With opendp, the function `estimate_opendp_cost` is particularly useful to estimate the used `epsilon` and `delta` based on the `scale` value." - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "7ae7f735", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 0.7122093023265229, 'delta_cost': 0}" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1c791d36", - "metadata": {}, - "source": [ - "She can now execute the query on the real dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "085555a5", - "metadata": {}, - "outputs": [], - "source": [ - "var_res = client.opendp_query(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "674332e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins: 344 (from previous smartnoise-sql query).\n", - "Average bill length: 43.84 (from previous smartnoise-sql query).\n", - "Variance of bill length: 28.052 (from opendp query).\n" - ] - } - ], - "source": [ - "print(f\"Number of penguins: {nb_penguins} (from previous smartnoise-sql query).\")\n", - "\n", - "print(f\"Average bill length: {np.round(avg_bill_length, 2)} (from previous smartnoise-sql query).\")\n", - "\n", - "var_bill_length = var_res['query_response']\n", - "print(f\"Variance of bill length: {np.round(var_bill_length, 3)} (from opendp query).\")" - ] - }, - { - "cell_type": "markdown", - "id": "367081be-1159-45d8-9129-88fba20fb697", - "metadata": {}, - "source": [ - "She can now do all the postprocessing that she wants with the returned data without adding any privacy risk. " - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "f72b19d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Standard error of bill length: 0.29.\n" - ] - } - ], - "source": [ - "# Get standard error\n", - "standard_error = np.sqrt(var_bill_length/nb_penguins)\n", - "print(f\"Standard error of bill length: {np.round(standard_error, 2)}.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "62630a03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The 95% confidence interval of the bill length of all penguins is [43.28, 44.4].\n" - ] - } - ], - "source": [ - " # Compute the 95% confidence interval\n", - "ZSCORE = 1.96\n", - "lower_bound = np.round(avg_bill_length - ZSCORE*standard_error, 2)\n", - "upper_bound = np.round(avg_bill_length + ZSCORE*standard_error, 2)\n", - "print(f\"The 95% confidence interval of the bill length of all penguins is [{lower_bound}, {upper_bound}].\")" - ] - }, - { - "cell_type": "markdown", - "id": "0d30d98e-26f4-44ec-a0f0-7a039f344860", - "metadata": {}, - "source": [ - "### Count per species" - ] - }, - { - "cell_type": "markdown", - "id": "b6a3cc00-8734-4479-81a3-781c91b8eb06", - "metadata": {}, - "source": [ - "She can also creates an histogram of the number of penguin per species.\n", - "\n", - "She first extract the categories from the metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "558916a6-78a9-4589-abe8-41472f0c66d7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Adelie', 'Chinstrap', 'Gentoo']" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "categories = penguin_metadata['columns']['species']['categories']\n", - "categories" - ] - }, - { - "cell_type": "markdown", - "id": "5f05aeb0-42d1-4444-a458-d2989494f544", - "metadata": {}, - "source": [ - "Then, writes the pipeline:" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "505a2793-6750-4f2a-9a9d-09f3a4ae4099", - "metadata": {}, - "outputs": [], - "source": [ - "species_count_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"species\", TOA=str) >>\n", - " trans.then_count_by_categories(categories=categories) >>\n", - " meas.then_laplace(scale=0.5)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "0f4109a9-c2f2-4365-bfa1-b5b2512919f9", - "metadata": {}, - "source": [ - "Verify it works on the dummy:" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "712b1c74-cac6-4b3f-9421-8d8ffa9debc7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for histogram: [38, 33, 29, 0]\n" - ] - } - ], - "source": [ - "dummy_res = client.opendp_query(\n", - " opendp_pipeline = species_count_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for histogram: {dummy_res['query_response']}\")" - ] - }, - { - "cell_type": "markdown", - "id": "66de7794-9aae-41d1-ba98-6b234a05d6f8", - "metadata": {}, - "source": [ - "Checks the required cost:" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "3f942b1b-94bc-468e-8374-3034f2e6b8ce", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 2.0, 'delta_cost': 0}" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = species_count_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1adfe446-ff13-41f4-9f3c-9998e2ae0f00", - "metadata": {}, - "source": [ - "And finally apply the pipeline on the real dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "c6fe47b7-048e-404a-bedb-720e734b0c70", - "metadata": {}, - "outputs": [], - "source": [ - "species_counts_res = client.opendp_query(\n", - " opendp_pipeline = species_count_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "b8d5fd82-5ded-47ec-8135-f6870865055d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Species Adelie has 152 penguins.\n", - "Species Chinstrap has 68 penguins.\n", - "Species Gentoo has 124 penguins.\n", - "Species Unknown has 0 penguins.\n" - ] - } - ], - "source": [ - "for i, count in enumerate(species_counts_res['query_response']):\n", - " if i == len(categories):\n", - " print(f\"Species Unknown has {count} penguins.\")\n", - " else:\n", - " print(f\"Species {categories[i]} has {count} penguins.\")" - ] - }, - { - "cell_type": "markdown", - "id": "94eaf59b-c108-424c-8978-b1c86e141ccb", - "metadata": {}, - "source": [ - "## Step 5: See archives of queries" - ] - }, - { - "cell_type": "markdown", - "id": "64003c53-de56-4bdc-a3c2-0c3e40031919", - "metadata": {}, - "source": [ - "She now wants to verify all the queries that she did on the real data. It is possible because an archive of all queries is kept in a secure database. With a function call she can see her queries, budget and associated responses." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "008fd230-cdfd-4e03-91ce-5a60b06c106d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'query_str': 'SELECT COUNT(*) AS nb_penguins, AVG(bill_length_mm) AS avg_bill_length_mm FROM df',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'epsilon': 0.5,\n", - " 'delta': 0.0001,\n", - " 'mechanisms': {},\n", - " 'postprocess': True},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': {'index': [0],\n", - " 'columns': ['nb_penguins', 'avg_bill_length_mm'],\n", - " 'data': [[342, 43.13189211774378]],\n", - " 'index_names': [None],\n", - " 'column_names': [None]},\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387},\n", - " 'timestamp': 1714988610.19844,\n", - " 'dp_librairy': 'smartnoise_sql'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': 10.184409589415381,\n", - " 'spent_epsilon': 0.7163742690067888,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1714988634.4750721,\n", - " 'dp_librairy': 'opendp'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': [152, 67, 123, 0],\n", - " 'spent_epsilon': 2.0,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1714988645.1652308,\n", - " 'dp_librairy': 'opendp'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'query_str': 'SELECT COUNT(*) AS nb_penguins, AVG(bill_length_mm) AS avg_bill_length_mm FROM df',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'epsilon': 0.5,\n", - " 'delta': 0.0001,\n", - " 'mechanisms': {},\n", - " 'postprocess': True},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': {'index': [0],\n", - " 'columns': ['nb_penguins', 'avg_bill_length_mm'],\n", - " 'data': [[344, 43.83632334140567]],\n", - " 'index_names': [None],\n", - " 'column_names': [None]},\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387},\n", - " 'timestamp': 1714990018.1717296,\n", - " 'dp_librairy': 'smartnoise_sql'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': 28.051717576669322,\n", - " 'spent_epsilon': 0.7122093023265229,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1714990066.3611896,\n", - " 'dp_librairy': 'opendp'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': [152, 68, 124, 0],\n", - " 'spent_epsilon': 2.0,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1714990074.9196112,\n", - " 'dp_librairy': 'opendp'}]" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "previous_queries = client.get_previous_queries()\n", - "previous_queries" - ] - }, - { - "cell_type": "markdown", - "id": "422536e7-2fd5-441f-a28d-14ffa9014084", - "metadata": {}, - "source": [ - "# FSO Example: (Synthetic) Income dataset\n", - "\n", - "## Boxplot of income per partitions of the population" - ] - }, - { - "cell_type": "markdown", - "id": "ee11c6c2-c474-4d08-ae03-3523643ba61f", - "metadata": {}, - "source": [ - "### Disclaimer: Temporary Version of OpenDP with Polars" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "71109933-5837-4c22-bebc-4c82142ef548", - "metadata": {}, - "outputs": [], - "source": [ - "# Import library\n", - "import numpy as np\n", - "import pandas as pd\n", - "import polars as pl\n", - "import opendp.prelude as dp\n", - "\n", - "dp.enable_features(\"contrib\")" - ] - }, - { - "cell_type": "markdown", - "id": "2663f674-bc04-4b79-a240-80ef84860f19", - "metadata": {}, - "source": [ - "### Switching user and exploring new dataset\n", - "\n", - "Let us now change user and select another dataset to play with. For this example, we will use an income distribution dataset (synthetic) from the Swiss Federal Statistical Office." - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "bb0e35e1-357d-4291-a211-15a815bd68af", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'columns': {'region': {'type': 'int'},\n", - " 'eco_branch': {'type': 'int'},\n", - " 'profession': {'type': 'int'},\n", - " 'education': {'type': 'int'},\n", - " 'age': {'type': 'int'},\n", - " 'sex': {'type': 'int'},\n", - " 'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "USER_NAME = \"Dr. FSO\"\n", - "DATASET_NAME = \"FSO_INCOME_SYNTHETIC\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)\n", - "\n", - "fso_income_metadata = client.get_dataset_metadata()\n", - "fso_income_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "729e0423-e7fd-4edb-ae44-d12cf0844de0", - "metadata": {}, - "source": [ - "Let us also check how much budget we have." - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "id": "7f6da2fd-0387-4b12-8630-07b6b3d11650", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 43.0, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 52, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "48af044f-5279-4e39-ba85-dd96fd5cac64", - "metadata": {}, - "source": [ - "### Data preparation" - ] - }, - { - "cell_type": "markdown", - "id": "1d43022a-0df2-4740-8c48-ef7776a8c0fc", - "metadata": {}, - "source": [ - "#### Column types and bounds" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "id": "7d0cc01a-76ca-4bf3-840b-f2ec6a2cd9b9", - "metadata": {}, - "outputs": [], - "source": [ - "# Income bounds\n", - "income_lower_bound, income_upper_bound = 1_000.0, 100_000.0" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "id": "32d6854b-510f-411b-8f73-127663686ac3", - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "module 'opendp.prelude' has no attribute 'lazyframe_domain'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[54], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Define dtype domain with bounds\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m lf_domain \u001b[38;5;241m=\u001b[39m \u001b[43mdp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlazyframe_domain\u001b[49m([\n\u001b[1;32m 3\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mregion\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 4\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124meco_branch\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 5\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mprofession\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 6\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124meducation\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 7\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mage\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 8\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msex\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 9\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mincome\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(\n\u001b[1;32m 10\u001b[0m T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mfloat\u001b[39m,\n\u001b[1;32m 11\u001b[0m bounds\u001b[38;5;241m=\u001b[39m(income_lower_bound, income_upper_bound)\n\u001b[1;32m 12\u001b[0m ))\n\u001b[1;32m 13\u001b[0m ])\n", - "\u001b[0;31mAttributeError\u001b[0m: module 'opendp.prelude' has no attribute 'lazyframe_domain'" - ] - } - ], - "source": [ - "# Define dtype domain with bounds\n", - "lf_domain = dp.lazyframe_domain([\n", - " dp.series_domain(\"region\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"eco_branch\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"profession\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"education\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"age\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"sex\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"income\", dp.atom_domain(\n", - " T=float,\n", - " bounds=(income_lower_bound, income_upper_bound)\n", - " ))\n", - "])" - ] - }, - { - "cell_type": "markdown", - "id": "5b539720-91b7-4c98-927d-58f641739f2f", - "metadata": {}, - "source": [ - "#### Counts per partition of the population" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "94c6c379-e5f5-41f7-a721-71f1b57f9915", - "metadata": {}, - "outputs": [], - "source": [ - "# Total\n", - "total_counts = pl.LazyFrame({\n", - " \"counts\": [2_032_543]\n", - "}, schema_overrides={\"counts\": pl.UInt32})\n", - "\n", - "# For sex\n", - "sex_counts = pl.LazyFrame({\n", - " \"sex\": [0, 1], \n", - " \"counts\": [634_720, 1_397_823]\n", - "}, schema_overrides={\"sex\": pl.Int32, \"counts\": pl.UInt32})\n", - "\n", - "# For region\n", - "region_counts = pl.LazyFrame({\n", - " \"region\": [1, 2, 3, 4, 5, 6, 7],\n", - " \"counts\": [352_001, 474_690, 267_304, 366_879, 284_638, 210_800, 76_231]\n", - "}, schema_overrides={\"region\": pl.Int32, \"counts\": pl.UInt32})\n", - "\n", - "# For region and sex\n", - "sex_region_counts = pl.LazyFrame({\n", - " \"sex\": [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], \n", - " \"region\": [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7], \n", - " \"counts\": [113_367, 148_265, 83_326, 113_715, 87_668, 64_357, 24_022, 238_634, 326_425, 183_978, 253_164, 196_970, 146_443, 52_209]\n", - "}, schema_overrides={\"sex\": pl.Int32, \"region\": pl.Int32, \"counts\": pl.UInt32})\n", - "\n", - "# Add counts to margin\n", - "lf_domain = lf_domain.with_counts(\n", - " total_counts\n", - ").with_counts(sex_counts).with_counts(region_counts).with_counts(sex_region_counts)" - ] - }, - { - "cell_type": "markdown", - "id": "4bff5ce4-2758-4454-ad58-497511c278be", - "metadata": {}, - "source": [ - "### Income distribution for partitions of the population:\n", - "#### Prepare the pipeline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8aada47e-cbc5-451b-9f1f-7b7311f29b63", - "metadata": {}, - "outputs": [], - "source": [ - "# Prepare a list of candidates\n", - "candidates = [x * 250.0 for x in range(8, 52)]\n", - "print(candidates)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ac3e2b5-92b6-4fdc-a8be-09a18f2f9c06", - "metadata": {}, - "outputs": [], - "source": [ - "# Partitions\n", - "PARTITIONS = ['sex', 'region']" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7099c026-e677-477d-9df0-60e406498303", - "metadata": {}, - "outputs": [], - "source": [ - "metric = dp.symmetric_distance() # Input metric\n", - "expr_domain = dp.expr_domain(lf_domain, grouping_columns=PARTITIONS) # Expr domain (Groupby)\n", - "temperature = 1000.0 # Noise parameter" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "382562c8-0a5a-4ece-a351-1381d10ff892", - "metadata": {}, - "outputs": [], - "source": [ - "def make_quantile_pipeline(quantile):\n", - " # Create expression\n", - " return (\n", - " (dp.csv_domain(lf_domain), metric)\n", - " >> dp.t.then_scan_csv()\n", - " >> dp.t.then_groupby_stable(PARTITIONS)\n", - " >> dp.m.then_private_agg(\n", - " dp.c.make_basic_composition(\n", - " [\n", - " (expr_domain, dp.l1(metric))\n", - " >> dp.t.then_col('income')\n", - " >> dp.m.then_private_quantile_expr(candidates, temperature, quantile)\n", - " ]\n", - " )\n", - " )\n", - " >> dp.t.make_collect(lf_domain, metric)\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "29f5ff59-b411-4714-b273-0a5e47a946a4", - "metadata": {}, - "outputs": [], - "source": [ - "q25 = make_quantile_pipeline(0.25)\n", - "q50 = make_quantile_pipeline(0.5)\n", - "q75 = make_quantile_pipeline(0.75)" - ] - }, - { - "cell_type": "markdown", - "id": "20f77fb2-390e-4c30-a34c-a0f235eb08d4", - "metadata": {}, - "source": [ - "#### Apply pipeline on data" - ] - }, - { - "cell_type": "markdown", - "id": "20e27602-0b4d-414c-a20a-562068d76965", - "metadata": {}, - "source": [ - "Let us first try out the pipeline with a dummy query and then estimate the cost of the different pipelines." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "577c8059-bda2-4f9f-a6bd-10cec7e8aa03", - "metadata": {}, - "outputs": [], - "source": [ - "dummy_r25 = client.opendp_query(\n", - " opendp_pipeline = q25, \n", - " dummy=True,\n", - " input_data_type=\"path\"\n", - ")\n", - "dummy_r25" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d56609e0-f28c-4d71-b7ca-80509acaa2af", - "metadata": {}, - "outputs": [], - "source": [ - "cost_q25 = client.estimate_opendp_cost(q25, input_data_type=\"path\")\n", - "cost_q50 = client.estimate_opendp_cost(q50, input_data_type=\"path\")\n", - "cost_q75 = client.estimate_opendp_cost(q75, input_data_type=\"path\")\n", - "\n", - "print(f\"The estimated costs are respectively {cost_q25}, {cost_q50} and {cost_q75} for q25, q50 and q75\")" - ] - }, - { - "cell_type": "markdown", - "id": "678d7741-e154-4c53-8834-5b76fa1298f5", - "metadata": {}, - "source": [ - "Since our budget is 45, we know that we can execute these three pipelines remotely." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fbf3b57e-3e66-4f98-b18c-39b183a80aa0", - "metadata": {}, - "outputs": [], - "source": [ - "r25 = client.opendp_query(q25, input_data_type=\"path\")\n", - "r50 = client.opendp_query(q50, input_data_type=\"path\")\n", - "r75 = client.opendp_query(q75, input_data_type=\"path\")" - ] - }, - { - "cell_type": "markdown", - "id": "34e2780e-2fc2-4b6f-86f0-d66522346149", - "metadata": {}, - "source": [ - "Let us put together the results and show them in a table. Notice that the output is a polars dataframe, we thus need to transform it to a pandas DataFrame if we want to work with pandas." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd36778a-ed94-4540-b803-4c8911a6da67", - "metadata": {}, - "outputs": [], - "source": [ - "r25 = r25[\"query_response\"].to_pandas()\n", - "r50 = r50[\"query_response\"].to_pandas()\n", - "r75 = r75[\"query_response\"].to_pandas()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "811e715f-888a-4946-937f-7f75d011843b", - "metadata": {}, - "outputs": [], - "source": [ - "results = pd.merge(r25, r50, on=PARTITIONS, suffixes=('_25', '_50'))\n", - "results = pd.merge(results, r75, on=PARTITIONS)\n", - "results.sort_values(by = ['region', 'sex']).head()" - ] - }, - { - "cell_type": "markdown", - "id": "16aa6910-25da-4c49-ab44-92cc4418f9d6", - "metadata": {}, - "source": [ - "#### Visualise results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "77f053b5-66dc-4252-b5ec-4a2ec3df3076", - "metadata": {}, - "outputs": [], - "source": [ - "import seaborn as sns\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "58de1e66-75f4-4be0-b4a8-19352250d897", - "metadata": {}, - "outputs": [], - "source": [ - "def quantile_data(q1, q2, q3):\n", - " return np.concatenate((np.random.uniform(q1[0], q2[0], size=50), np.random.uniform(q2[0], q3[0], size=50)))\n", - "\n", - "results['data'] = results.apply(\n", - " lambda row: quantile_data(row[\"income_25\"], row[\"income_50\"], row[\"income\"]),\n", - " axis=1,\n", - ")\n", - "results['sex'] = results['sex'].replace({0: 'woman', 1: 'man'})\n", - "results['region'] = results['region'].replace({1: 'Lemanique', 2: 'Mittleland', 3: 'North-West', 4: 'Zürich', 5: 'Oriental', 6: 'Central', 7: 'Ticino'})\n", - "results = results.explode('data', ignore_index=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "65b63c19-d710-4368-9dbd-88a737fd793f", - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure(figsize=(10, 6))\n", - "sns.boxplot(x=\"region\", y=\"data\", hue=\"sex\", data=results, palette=\"Set1\", width=0.5);\n", - "plt.xticks(fontsize=12)\n", - "plt.yticks(fontsize=12)\n", - "plt.xlabel('Regions', fontsize=15)\n", - "plt.ylabel('Income per month (in CHF)', fontsize=15)\n", - "plt.title('Income per partition of the population', fontsize=16)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "acd4e078-f92d-4edb-835a-a891a15e08a5", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/notebooks/demo_kubernetes_admin_notebook.ipynb.txt b/html/de/_sources/notebooks/demo_kubernetes_admin_notebook.ipynb.txt deleted file mode 100644 index b621eb2a..00000000 --- a/html/de/_sources/notebooks/demo_kubernetes_admin_notebook.ipynb.txt +++ /dev/null @@ -1,465 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Secure Data Disclosure on Kubernetes: Server Administration" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "#### This notebook showcases how a data owner could add and make their data available to certain user. We will do this in a step by step fashion." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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5tS9qF/LUn2msHAUAAAgFwSEAACg5KjbTIWCb7/9Nq0GI1IXVNpe7777rtm+fbtdCgBwqaruazp1Lp0WVECcBbVm3LZtm50KFvQ8jTeYqa1bt9qpaFQG86NuRtWNqsuAAQPsVHNr1661UwAAAIVBQAgAAErKypUrvUoWdSu6b98+uxRR6G73JUuWmNmzZ3vdXdHtKAC4de7S1VT27G3ngPgppV4ZggJClWdylU0rws2bN9upaNSKMShU9BsLMah14XvvvWenAAAACoOAEAAAlATd+f3OO+944VamlTJItWXLFm87KmhlWwKAmwLCTp3drXqAQti7d6+dKjy/bisTNI5xObj66qsjPWpra+1fNJfNeIt+XZYG0ZiFflxBoJb5rZvGXlToCAAAUEgEhAAAoEVpDL2lS5eat956yyxbtsy8//779l+Qq+TWmPv377dLAQDSuUs3WhGiqPbs2WOn3DQeXb6oW0tkTzdbZUo3Z/mFwK5uRocPH26nmosy9iIAAECuCAgBAECL0d3RCrDmzJnDuHkFwniOAOCvqppWhCieXbt22Sm33r3zF1h369bNTrkFtXaDMevXr7dTmQkKFtO7GfXrXlQho8JGAACAQiMgBAAARbd7924zf/58r9Xg6tWr7VIU0tq1a72QcN68eaEVlAAQFwoHq6r9xwAD8mnDhg12yq1nz552KnedO3e2U80pgIpT95VhYWk+6aY3P/369bNTwd2LZtN6EQAAIBttDjWy0yUlm/7e46Ji7c2N/y3J3QYAQKCGfj8wa9asMStWrAitJHNp3769mTp1qp3DM88844WtmerVq5cZNGiQGTBggDe/aj3lLgDxtWf3LrNs0btm9y7G+0LhXX755aZdu3Z2LtWBAwfMgw8+aOfcFCydcsopXjnqjTfecAZ96qr0pJNOsnPNbd261Tz++ON2LpXG8/Nz33332algmb5GPt4zU6NGjTITJkywc83l8r4XXXSRqaiosHOpVHZT68QPfehDZsiQIXZpqldeecUsX77czgHRjJ98mp0CAOSito/7Gp5vfmWFYqMFIQAAKIqtu7uYd99912vFlk04iPxJdO36zjvveJWEABBnnTp3MVXVfe0cUFhB110FhxMnTrRzbuPGjTMdO3Y0tbW15uKLLzZnnHFGs7HtFH4Fybb7zChjJIa9t4vfuH2S/tnKgXpt8JMY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KH2t+LnooouKx+I5A/Wa0CyeffaZ9PiGtWnnjqfzGgBGKnMQDnPmDAJguDIHYfPwfQKg/8xBOHj6MwdhNREwzZo1Ky/1FMORRsVhmf7MQVimt/e4evXqdOONN+al/qk2J11/5partt1Q63mKYPDUU0+tea6effv2FccnhmytV5zHGP60t3kgu4vXXL9+fc3npNqxqTYPYGd93UY9v9fbOaymP32H5jBrzoK0aNkRadSoUXkNwMhnDkIAAACAFtNbKLNw4cLcYrCcdNJJ6ayzzqrrJlqEe8uWLSvCrVrnjoznXXjhhUXIW284GOJ34nfreU1odjEXofkIAUY2ASEAAADQ8qI6MCrBKpk2bVpuUYsxY8bkVk+9zbEYIhxcvnx5nwK7EKHiOeecU1NgF88biHkR4zVj2FEYKWI+wm1Pbc1LAIw0AkIAAACAdjt37sytniZOnJhbjTd37tzcKrdjx47cak5Lly6tGrhVO84h5vWLcLC/Yh8i/KsmhhUdiHCwQ4SEp512Wl6C4W33rh1p88a1ad++vXkNACOJgBAAAACg3ZNPPplbPY0fPz63Gisq3mIevGo2bdqUW80n9v+EE07IS+U2btyYWz3F7x955JF5qaeoPrz//vvTd7/73WIew+uvv76Yc7CSCP8iBKyk0rHumMsw5uWL1+n4ideL169WBVlpPkoYjp7YvDE9vsFQowAjkYAQAAAAoAaNnl8uth8Vb9WG1YzgKoZDbSZRMbhixYqicu68886rWpEX+/+b3/wmL/V04oknVnz/UTn5gx/8IN12221p+/btxboIG2+88cYitKskQsCycxf7XOm17rzzzmK73eemjNeL14/9qBQSxjZjiNTh7uqrr34+GI2gtJJt27Z1CVHjJ36XkePxjWvSk088npcAGCkEhAAAAAA1aERlWEe4FnPXnX/++b0Od/n444N3kz6Gy3zTm97U689LX/rSIthbtmxZr5WWUZXXEe51FyFeterJm2++ueLvRmhXqZIwArujjjoqLx0wc+bM3OqpWlViiP144IEHisAzArL169c/X3H4s5/9rNgfGCn27tmTNm1Yk3bvqj48MADDi4AQAAAAoEF6C9k6wrUIxqpVDoYIo2655Za8NPxs3bq1CPkqiRCv0jGIYLTa0KShWpXb/Pnzc+uAasf7zDPP7LVi9K677krf/va3i2q5H/3o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", - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_admin_side.png\", width=800)" - ] - }, - { - "cell_type": "markdown", - "id": "0034a717", - "metadata": {}, - "source": [ - "# Start of DEMO" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Since the service has been deployed in the demo 1, the URL should be accessible." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "e7578d3a", - "metadata": {}, - "outputs": [], - "source": [ - "URL = 'https://lomas-server.lab.sspcloud.fr/'" - ] - }, - { - "cell_type": "markdown", - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "source": [ - "## Administering the service by accessing the mongoDB" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e249d717", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../lomas_server/')" - ] - }, - { - "cell_type": "markdown", - "id": "1a10543e", - "metadata": {}, - "source": [ - "Let's add a formatting function to have more readable outputs." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a0145cfe", - "metadata": {}, - "outputs": [], - "source": [ - "from ast import literal_eval\n", - "import subprocess\n", - "\n", - "def run(command, to_dict=False):\n", - " command = f\"python mongodb_admin.py {command}\"\n", - " completed_process = subprocess.run(command, shell=True, text=True, capture_output=True)\n", - " output = completed_process.stdout\n", - " if to_dict:\n", - " return literal_eval(output)\n", - " else:\n", - " output = output.rstrip('\\n').replace(r'\\n', '\\n')\n", - " return print(output)" - ] - }, - { - "cell_type": "markdown", - "id": "d368d6a6-f1fe-4f65-9ce1-38c0b39584d1", - "metadata": {}, - "source": [ - "## **Preparing the database**" - ] - }, - { - "cell_type": "markdown", - "id": "b37c19b8-303d-4fe8-b515-33ed1099c581", - "metadata": {}, - "source": [ - "#### Some existing options" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "8a749f4b-93cb-460c-bb40-4880df6e51d9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "usage: MongoDB administration script for the user database [-h]\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}\n", - " ...\n", - "\n", - "options:\n", - " -h, --help show this help message and exit\n", - "\n", - "subcommands:\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}\n", - " user database administration operations\n", - " add_user add user to users collection\n", - " add_user_with_budget\n", - " add user with budget to users collection\n", - " del_user delete user from users collection\n", - " add_dataset_to_user\n", - " add dataset with initialized budget values for a user\n", - " del_dataset_to_user\n", - " delete dataset for user in users collection\n", - " set_budget_field set budget field to given value for given user and\n", - " dataset\n", - " set_may_query set may query field to given value for given user\n", - " show_user show all metadata of user\n", - " create_users_collection\n", - " create users collection from yaml file\n", - " add_dataset set in which database the dataset is stored\n", - " add_datasets create dataset to database type collection\n", - " drop_collection delete collection from database\n", - " show_collection print the users collection\n" - ] - } - ], - "source": [ - "run(\"--help\") # !python mongodb_admin.py --help" - ] - }, - { - "cell_type": "markdown", - "id": "9579cbc3", - "metadata": {}, - "source": [ - "#### Cleaning the database" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "da0863e4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection datasets.\n", - "Deleted collection metadata.\n", - "Deleted collection users.\n" - ] - } - ], - "source": [ - "run(\"drop_collection --collection datasets\") \n", - "run(\"drop_collection --collection metadata\")\n", - "run(\"drop_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "d7edd7d3-20f9-4546-afc8-25661f948d44", - "metadata": {}, - "source": [ - "## **Datasets (add and drop)**" - ] - }, - { - "cell_type": "markdown", - "id": "ed1597b3-767f-470c-a7d7-8fe41dd82da5", - "metadata": {}, - "source": [ - "#### For each dataset, 2 informations are required:\n", - "\n", - "#### - the type of database in which the dataset is stored\n", - "#### - a path to the metadata of the dataset (stored as a yaml file).\n", - "\n", - "#### Metadata are expected to be in the same format as [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details)." - ] - }, - { - "cell_type": "markdown", - "id": "9b0e730b", - "metadata": {}, - "source": [ - "## Add one dataset" - ] - }, - { - "cell_type": "markdown", - "id": "d1d331ea", - "metadata": {}, - "source": [ - "#### We can add **one dataset** with its name, database type and path to medata file:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "53f5787d-e721-43d9-85ce-da842f173381", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added dataset PENGUIN with database REMOTE_HTTP_DB and metadata from ../data/collections/metadata/penguin_metadata.yaml.\n" - ] - } - ], - "source": [ - "run(\"add_dataset -d PENGUIN -db REMOTE_HTTP_DB -mp ../data/collections/metadata/penguin_metadata.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "4e57ddf9", - "metadata": {}, - "source": [ - "### Add multiple datasets" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "0e42f9cb-3a02-45f5-baee-2e06edda739f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "Added datasets collection from yaml at ../data/collections/dataset_collection.yaml. \n", - "Added metadata of IRIS dataset. \n", - "Added metadata of PENGUIN dataset. \n", - "Added metadata of TITANIC dataset. \n", - "Added metadata of FSO_INCOME_SYNTHETIC dataset. \n" - ] - } - ], - "source": [ - "run(\"add_datasets --path ../data/collections/dataset_collection.yaml -c\")" - ] - }, - { - "cell_type": "markdown", - "id": "0e0b85d5", - "metadata": {}, - "source": [ - "## **Users**" - ] - }, - { - "cell_type": "markdown", - "id": "14ab18db-4b6d-4663-bde0-b5d9d3d3d2ee", - "metadata": {}, - "source": [ - "#### Adding users" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "0f6aa33c-6bd1-4d62-ba06-3533b064340d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Dr. Antartica with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"add_user_with_budget --user 'Dr. Antartica' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "markdown", - "id": "0bed2714", - "metadata": {}, - "source": [ - "### And we can also modify existing the total budget of a user:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "87eecb9c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Dr. Antartica for dataset PENGUIN of initial_epsilon to 20.0.\n" - ] - } - ], - "source": [ - "run(\"set_budget_field --user 'Dr. Antartica' --dataset 'PENGUIN' --field initial_epsilon --value 20.0\")" - ] - }, - { - "cell_type": "markdown", - "id": "ba7cfa86", - "metadata": {}, - "source": [ - "### Finally, many users can actually be loaded directly from a single file" - ] - }, - { - "cell_type": "markdown", - "id": "20b3cd2c", - "metadata": {}, - "source": [ - "#### We add the data based on a yaml file:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "87b776f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "Added user data from yaml at ../data/collections/user_collection.yaml.\n" - ] - } - ], - "source": [ - "run(\"create_users_collection --path ../data/collections/user_collection.yaml -c\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Archives of queries" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"show_collection --collection queries_archives\")" - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "## **Stopping the service: Let's not do it right now!**\n", - "\n", - "To tear down the service, we simply execute the command `helm uninstall lomas-service`" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "2fdbfafb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "release \"sdd-service\" uninstalled\n" - ] - } - ], - "source": [ - "!helm uninstall lomas-service" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/notebooks/demo_kubernetes_deployment_notebook.ipynb.txt b/html/de/_sources/notebooks/demo_kubernetes_deployment_notebook.ipynb.txt deleted file mode 100644 index 9e14a3b7..00000000 --- a/html/de/_sources/notebooks/demo_kubernetes_deployment_notebook.ipynb.txt +++ /dev/null @@ -1,336 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Demo - Kubernetes Service Deployment" - ] - }, - { - "cell_type": "markdown", - "id": "91ba5946", - "metadata": {}, - "source": [ - "## Building the container images\n", - "\n", - "#### Use `docker login` to setup your credentials" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "execution_count": 1, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_deployment_containers.png\", width=800)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0650340f", - "metadata": {}, - "outputs": [], - "source": [ - "!cd .. && docker build --target lomas_server -t /lomas_server:latest .\n", - "!cd .. && docker push /lomas_server:latest" - ] - }, - { - "cell_type": "markdown", - "id": "ee01e006", - "metadata": {}, - "source": [ - "### Client" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "36d2d05c", - "metadata": {}, - "outputs": [], - "source": [ - "!cd ../../client/ && docker build --target lomas_client -t /lomas_client:latest .\n", - "!cd ../../client/ && docker push /lomas_client:latest" - ] - }, - { - "cell_type": "markdown", - "id": "0034a717", - "metadata": {}, - "source": [ - "## Starting the service" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "43dccc5e", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_deployment_service.png\", width=800)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e249d717", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../deploy/helm/charts/lomas_server')" - ] - }, - { - "cell_type": "markdown", - "id": "11075ea0", - "metadata": {}, - "source": [ - "\n", - "#### Update `values.yaml` file\n", - "\n", - "#### Download Helm dependency for the MongoDB chart" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "fe550e12", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Saving 1 charts\n", - "Downloading mongodb from repo oci://registry-1.docker.io/bitnamicharts\n", - "Pulled: registry-1.docker.io/bitnamicharts/mongodb:13.18.1\n", - "Digest: sha256:f3b2a691537260044746bc4a8898e9ae68e8c29864639737b6da920f99aebe97\n", - "Deleting outdated charts\n" - ] - } - ], - "source": [ - "!helm dependency update" - ] - }, - { - "cell_type": "markdown", - "id": "2913bce4", - "metadata": {}, - "source": [ - "#### Install server chart" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5ed0e2a9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "W1212 08:38:59.187407 178308 warnings.go:70] annotation \"kubernetes.io/ingress.class\" is deprecated, please use 'spec.ingressClassName' instead\n", - "NAME: lomas-service\n", - "LAST DEPLOYED: Tue Dec 12 08:38:56 2023\n", - "NAMESPACE: user-aymond\n", - "STATUS: deployed\n", - "REVISION: 1\n", - "TEST SUITE: None\n", - "NOTES:\n", - "1. Get the application URL by running these commands:\n", - " https://lomas-server.lab.sspcloud.fr/\n" - ] - } - ], - "source": [ - "!helm install -f values.yaml lomas-service ." - ] - }, - { - "cell_type": "markdown", - "id": "527d4837", - "metadata": {}, - "source": [ - "#### Check deployment with `kubectl get all` and by querying `/state`" - ] - }, - { - "cell_type": "markdown", - "id": "0d564566", - "metadata": {}, - "source": [ - "## Starting the client session\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "81ce5384", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "os.chdir(\"../../../../notebooks/\")\n", - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_deployment_client.png\", width=800)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "79ccb52f", - "metadata": {}, - "outputs": [], - "source": [ - "os.chdir(\"../../client/deploy/helm/charts/lomas_client\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Update `values.yaml` file\n", - "\n", - "#### Install the client chart" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "W1212 10:06:30.269176 189525 warnings.go:70] annotation \"kubernetes.io/ingress.class\" is deprecated, please use 'spec.ingressClassName' instead\n", - "NAME: lomas-client\n", - "LAST DEPLOYED: Tue Dec 12 10:06:28 2023\n", - "NAMESPACE: user-aymond\n", - "STATUS: deployed\n", - "REVISION: 1\n", - "TEST SUITE: None\n", - "NOTES:\n", - "1. Get the application URL by running these commands:\n", - " https://lomas-client.lab.sspcloud.fr/\n" - ] - } - ], - "source": [ - "!helm install -f values.yaml lomas-client ." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Access the client environment through the url and use the password defined in the values file." - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "## Stopping the service" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!helm uninstall lomas-service\n", - "!helm uninstall lomas-client" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/notebooks/income_minimal.ipynb.txt b/html/de/_sources/notebooks/income_minimal.ipynb.txt deleted file mode 100644 index b1905c5e..00000000 --- a/html/de/_sources/notebooks/income_minimal.ipynb.txt +++ /dev/null @@ -1,286 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "13180a2e-3676-4b55-8de4-c114a50aba35", - "metadata": {}, - "source": [ - "## Minimal OpenDP example on the income dataset\n", - "\n", - "(Just to check if it's working as intended)" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "20ebbbe0-71f2-4daf-93f6-185d7574c8fc", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client.client import Client\n", - "\n", - "APP_URL = \"http://lomas_server_dev:80\" # Onyxia: \"https://lomas-server-demo.lab.sspcloud.fr\"\n", - "USER_NAME = \"Dr. FSO\"\n", - "DATASET_NAME = \"FSO_INCOME_SYNTHETIC\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "f1b5cc29-9581-4fd8-9efe-ad818f861fdf", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'columns': {'region': {'type': 'int'},\n", - " 'eco_branch': {'type': 'int'},\n", - " 'profession': {'type': 'int'},\n", - " 'education': {'type': 'int'},\n", - " 'age': {'type': 'int'},\n", - " 'sex': {'type': 'int'},\n", - " 'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "income_metadata = client.get_dataset_metadata()\n", - "income_metadata" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "fc70134d-264b-4088-ac6c-9c56361dbc32", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
regioneco_branchprofessioneducationagesexincome
0-7268-87594470-70972279447099981.049234
1799903-4654-6748-2700134520528.687956
2-155-5597-5224-35529480803953955.911640
39648758237294274-178-757829734.556213
431239381-5878-25749842816531113.182182
\n", - "
" - ], - "text/plain": [ - " region eco_branch profession education age sex income\n", - "0 -7268 -8759 4470 -7097 2279 4470 99981.049234\n", - "1 799 903 -4654 -6748 -2700 1345 20528.687956\n", - "2 -155 -5597 -5224 -3552 9480 8039 53955.911640\n", - "3 9648 7582 3729 4274 -178 -7578 29734.556213\n", - "4 3123 9381 -5878 -2574 9842 8165 31113.182182" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0\n", - "\n", - "df_dummy = client.get_dummy_dataset(\n", - " nb_rows = NB_ROWS, \n", - " seed = SEED\n", - ")\n", - "\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "ce85a354-8a3a-42be-92bf-b06c7ceaf0e7", - "metadata": {}, - "outputs": [], - "source": [ - "import opendp.prelude as dp\n", - "import opendp.transformations as trans\n", - "import opendp.measurements as meas\n", - "\n", - "dp.enable_features(\"contrib\")\n", - "\n", - "columns = [\"region\", \"eco_branch\", \"profession\", \"education\", \"age\", \"sex\", \"income\"]\n", - "\n", - "income_min = float(income_metadata['columns'][\"income\"][\"lower\"])\n", - "income_max = float(income_metadata['columns'][\"income\"][\"upper\"])\n", - "\n", - "num_rows_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"income\", TOA=str) >>\n", - " trans.then_count() >>\n", - " meas.then_laplace(scale=0.5) # scale arbitrary\n", - ")\n", - "\n", - "num_rows = client.opendp_query(\n", - " opendp_pipeline = num_rows_pipeline,\n", - ")[\"query_response\"]\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "1c713e61-4c80-4514-88cd-b15d8e151c5a", - "metadata": {}, - "outputs": [], - "source": [ - "income_average_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"income\", TOA=str) >>\n", - " trans.then_cast_default(TOA=float) >>\n", - " trans.then_clamp(bounds=(income_min, income_max)) >>\n", - " trans.then_resize(size=num_rows, constant=1000.0) >> # Arbitrary constant\n", - " trans.then_mean() >>\n", - " meas.then_laplace(scale=0.5)\n", - ")\n", - "\n", - "income_average = client.opendp_query(\n", - " opendp_pipeline = income_average_pipeline,\n", - " dummy=True\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "834105bb-e8dc-4243-88cf-27b687711b8a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'query_response': 1001.9747347375568}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "income_average" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3b86525b-36cc-4533-b7a5-a298c8bb132f", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/notebooks/kubernetes_admin_notebook.ipynb.txt b/html/de/_sources/notebooks/kubernetes_admin_notebook.ipynb.txt deleted file mode 100644 index 903af62d..00000000 --- a/html/de/_sources/notebooks/kubernetes_admin_notebook.ipynb.txt +++ /dev/null @@ -1,1437 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Secure Data Disclosure on Kubernetes: Deployment and Server Administration" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "This notebook showcases how a data owner could set up the service on a kubernetes cluster, add and make their data available to certain user. We will do this in a step by step fashion." - ] - }, - { - "cell_type": "markdown", - "id": "de384c88-559e-4384-a49b-1664ffdd6692", - "metadata": {}, - "source": [ - "## Deploying the service" - ] - }, - { - "cell_type": "markdown", - "id": "91ba5946", - "metadata": {}, - "source": [ - "### Building the server image\n", - "The Lomas service is comprised of a fastapi server and a MongoDB database for keeping state and administration. While the database image is public, the server image must first be built and pushed to a registry.\n", - "\n", - "NOTE: For now, the server configuration file is copied and put into the server container. This is of course not practical (and not safe, since the configuration file contains passwords and secrets) and will be updated in future versions. The `config/example_config.yaml` is the one that is copied into the container. One has to change it and rebuild+push the server container in order to change the server configuration." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8f688134", - "metadata": {}, - "outputs": [], - "source": [ - "# !docker login (=> use personal token from dockerhub, has to be done only once)\n", - "\n", - "!cd .. && docker build --target lomas_server -t /lomas_serverer:latest .\n", - "!cd .. && docker push /lomas-server:latest" - ] - }, - { - "cell_type": "markdown", - "id": "92f3237b-6f13-4c52-a9f2-82d94f0b7e66", - "metadata": {}, - "source": [ - "### Deploying the service Helm chart\n", - "We use a Helm chart to deploy the service on a Kubernetes cluster. The lomas-server chart is located at `deploy/helm/charts/lomas_server`, let us change our working directory to this location." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e249d717", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../deploy/helm/charts/lomas_server')" - ] - }, - { - "cell_type": "markdown", - "id": "11075ea0", - "metadata": {}, - "source": [ - "The `values.yaml` file contains all the configuration values for the service. We must now update the `image.repository` field to the one we pushed the server container image to. One can also change the url to which the service will be published with `ingress.hosts[0].host` (or disable this feature by setting `ingress.enabled` to `False`).\n", - "\n", - " => Update `values.yaml` file\n", - "\n", - "As previously stated, the service is made up of a server and a MongoDB database. Before installing the chart, we must thus first download that dependency." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "fe550e12", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Saving 1 charts\n", - "Downloading mongodb from repo oci://registry-1.docker.io/bitnamicharts\n", - "Save error occurred: could not download oci://registry-1.docker.io/bitnamicharts/mongodb: failed to copy: httpReadSeeker: failed open: failed to do request: Get \"https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/95/953b9cef6799e942255a1d5edcb7cb7508230fb57e4d68d02e27aed4b1694eaf/data?verify=1701701776-w82MF1fkjhlDOYT4WuSJHicDe5c%3D\": dial tcp: lookup production.cloudflare.docker.com on 169.254.25.10:53: server misbehaving\n", - "Error: could not download oci://registry-1.docker.io/bitnamicharts/mongodb: failed to copy: httpReadSeeker: failed open: failed to do request: Get \"https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/95/953b9cef6799e942255a1d5edcb7cb7508230fb57e4d68d02e27aed4b1694eaf/data?verify=1701701776-w82MF1fkjhlDOYT4WuSJHicDe5c%3D\": dial tcp: lookup production.cloudflare.docker.com on 169.254.25.10:53: server misbehaving\n" - ] - } - ], - "source": [ - "!helm dependency update" - ] - }, - { - "cell_type": "markdown", - "id": "2913bce4", - "metadata": {}, - "source": [ - "Now the chart is ready to be installed, so let the magic happen!" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5ed0e2a9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error: INSTALLATION FAILED: cannot re-use a name that is still in use\n" - ] - } - ], - "source": [ - "!helm install -f values.yaml lomas-service ." - ] - }, - { - "cell_type": "markdown", - "id": "527d4837", - "metadata": {}, - "source": [ - "The installation notes show the url at which the server is exposed. One can have a look at the api docummentation by visiting `/docs`\n", - "\n", - "One can also check the whether the service started error free by using the `kubectl get all` command as well as inspecting the server logs with `kubectl logs `" - ] - }, - { - "cell_type": "markdown", - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "source": [ - "## Administering the service by accessing the mongoDB" - ] - }, - { - "cell_type": "markdown", - "id": "6d2ec36f", - "metadata": {}, - "source": [ - "Let's switch directory again and move to the administration script." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "d4ede728", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../../../../lomas_server/')" - ] - }, - { - "cell_type": "markdown", - "id": "4a8c8115", - "metadata": {}, - "source": [ - "To interact with the mongoDB, we will need to install a few libraries. Let's do so by creating a python virtual environment and installing the dependencies listed in `admin_requirements.txt`" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "f6863a6d", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: annotated-types==0.5.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 1)) (0.5.0)\n", - "Requirement already satisfied: anyio==3.7.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 2)) (3.7.1)\n", - "Requirement already satisfied: asttokens==2.4.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 3)) (2.4.0)\n", - "Requirement already satisfied: backcall==0.2.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 4)) (0.2.0)\n", - "Collecting boto3 (from -r ../admin_requirements.txt (line 5))\n", - " Downloading boto3-1.34.40-py3-none-any.whl.metadata (6.6 kB)\n", - "Requirement already satisfied: comm==0.1.4 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 6)) (0.1.4)\n", - "Requirement already satisfied: debugpy==1.7.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 7)) (1.7.0)\n", - "Requirement already satisfied: decorator==5.1.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 8)) (5.1.1)\n", - "Requirement already satisfied: dnspython==2.4.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 9)) (2.4.2)\n", - "Requirement already satisfied: executing==1.2.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 10)) (1.2.0)\n", - "Requirement already satisfied: fastapi==0.103.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 11)) (0.103.1)\n", - "Requirement already satisfied: idna==3.4 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 12)) (3.4)\n", - "Requirement already satisfied: ipykernel==6.25.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 13)) (6.25.2)\n", - "Requirement already satisfied: ipython==8.15.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 14)) (8.15.0)\n", - "Requirement already satisfied: jedi==0.19.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 15)) (0.19.0)\n", - "Requirement already satisfied: jupyter_client==8.3.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 16)) (8.3.1)\n", - "Requirement already satisfied: jupyter_core==5.3.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 17)) (5.3.1)\n", - "Requirement already satisfied: matplotlib-inline==0.1.6 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 18)) (0.1.6)\n", - "Requirement already satisfied: nest-asyncio==1.5.7 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 19)) (1.5.7)\n", - "Requirement already satisfied: packaging==23.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 20)) (23.1)\n", - "Requirement already satisfied: parso==0.8.3 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 21)) (0.8.3)\n", - "Requirement already satisfied: pexpect==4.8.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 22)) (4.8.0)\n", - "Requirement already satisfied: pickleshare==0.7.5 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 23)) (0.7.5)\n", - "Requirement already satisfied: platformdirs==3.10.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 24)) (3.10.0)\n", - "Requirement already satisfied: prompt-toolkit==3.0.39 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 25)) (3.0.39)\n", - "Requirement already satisfied: psutil==5.9.5 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 26)) (5.9.5)\n", - "Requirement already satisfied: ptyprocess==0.7.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 27)) (0.7.0)\n", - "Requirement already satisfied: pure-eval==0.2.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 28)) (0.2.2)\n", - "Requirement already satisfied: pyaml==23.9.5 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 29)) (23.9.5)\n", - "Requirement already satisfied: pydantic==2.3.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 30)) (2.3.0)\n", - "Requirement already satisfied: pydantic_core==2.6.3 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 31)) (2.6.3)\n", - "Requirement already satisfied: Pygments==2.16.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 32)) (2.16.1)\n", - "Requirement already satisfied: pymongo==4.5.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 33)) (4.5.0)\n", - "Requirement already satisfied: python-dateutil==2.8.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 34)) (2.8.2)\n", - "Requirement already satisfied: PyYAML==6.0.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 35)) (6.0.1)\n", - "Requirement already satisfied: pyzmq==25.1.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 36)) (25.1.1)\n", - "Requirement already satisfied: six==1.16.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 37)) (1.16.0)\n", - "Requirement already satisfied: sniffio==1.3.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 38)) (1.3.0)\n", - "Requirement already satisfied: stack-data==0.6.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 39)) (0.6.2)\n", - "Requirement already satisfied: starlette==0.27.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 40)) (0.27.0)\n", - "Requirement already satisfied: tornado==6.3.3 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 41)) (6.3.3)\n", - "Requirement already satisfied: traitlets==5.9.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 42)) (5.9.0)\n", - "Requirement already satisfied: typing_extensions==4.7.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 43)) (4.7.1)\n", - "Requirement already satisfied: wcwidth==0.2.6 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 44)) (0.2.6)\n", - "Requirement already satisfied: exceptiongroup in /home/onyxia/work/.venv/lib/python3.10/site-packages (from anyio==3.7.1->-r ../admin_requirements.txt (line 2)) (1.2.0)\n", - "Collecting botocore<1.35.0,>=1.34.40 (from boto3->-r ../admin_requirements.txt (line 5))\n", - " Downloading botocore-1.34.40-py3-none-any.whl.metadata (5.7 kB)\n", - "Collecting jmespath<2.0.0,>=0.7.1 (from boto3->-r ../admin_requirements.txt (line 5))\n", - " Downloading jmespath-1.0.1-py3-none-any.whl (20 kB)\n", - "Collecting s3transfer<0.11.0,>=0.10.0 (from boto3->-r ../admin_requirements.txt (line 5))\n", - " Downloading s3transfer-0.10.0-py3-none-any.whl.metadata (1.7 kB)\n", - "Collecting urllib3<2.1,>=1.25.4 (from botocore<1.35.0,>=1.34.40->boto3->-r ../admin_requirements.txt (line 5))\n", - " Downloading urllib3-2.0.7-py3-none-any.whl.metadata (6.6 kB)\n", - "Downloading boto3-1.34.40-py3-none-any.whl (139 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m139.3/139.3 kB\u001b[0m \u001b[31m8.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading botocore-1.34.40-py3-none-any.whl (12.0 MB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m12.0/12.0 MB\u001b[0m \u001b[31m58.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n", - "\u001b[?25hDownloading s3transfer-0.10.0-py3-none-any.whl (82 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m82.1/82.1 kB\u001b[0m \u001b[31m3.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading urllib3-2.0.7-py3-none-any.whl (124 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m124.2/124.2 kB\u001b[0m \u001b[31m25.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hInstalling collected packages: urllib3, jmespath, botocore, s3transfer, boto3\n", - "Successfully installed boto3-1.34.40 botocore-1.34.40 jmespath-1.0.1 s3transfer-0.10.0 urllib3-2.0.7\n" - ] - } - ], - "source": [ - "!pip install -r ../admin_requirements.txt\n" - ] - }, - { - "cell_type": "markdown", - "id": "9f35fd20-715c-483b-88e4-449c287ba61d", - "metadata": {}, - "source": [ - "We should now have the required environment to interact with the admin database." - ] - }, - { - "cell_type": "markdown", - "id": "d368d6a6-f1fe-4f65-9ce1-38c0b39584d1", - "metadata": {}, - "source": [ - "### Preparing the database" - ] - }, - { - "cell_type": "markdown", - "id": "b37c19b8-303d-4fe8-b515-33ed1099c581", - "metadata": {}, - "source": [ - "You can visualise all the options offered by the database by running the command `python mongodb_admin.py --help`. We will go through through each of them in the rest of the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "8a749f4b-93cb-460c-bb40-4880df6e51d9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "usage: MongoDB administration script for the user database [-h]\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}\n", - " ...\n", - "\n", - "options:\n", - " -h, --help show this help message and exit\n", - "\n", - "subcommands:\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}\n", - " user database administration operations\n", - " add_user add user to users collection\n", - " add_user_with_budget\n", - " add user with budget to users collection\n", - " del_user delete user from users collection\n", - " add_dataset_to_user\n", - " add dataset with initialized budget values for a user\n", - " del_dataset_to_user\n", - " delete dataset for user in users collection\n", - " set_budget_field set budget field to given value for given user and\n", - " dataset\n", - " set_may_query set may query field to given value for given user\n", - " show_user show all metadata of user\n", - " create_users_collection\n", - " create users collection from yaml file\n", - " add_dataset set in which database the dataset is stored\n", - " add_datasets create dataset to database type collection\n", - " drop_collection delete collection from database\n", - " show_collection print the users collection\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py --help" - ] - }, - { - "cell_type": "markdown", - "id": "9579cbc3", - "metadata": {}, - "source": [ - "Let's first make sure the database is empty and in a clean state." - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "18a3681c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection datasets.\n", - "Deleted collection metadata.\n", - "Deleted collection users.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py drop_collection --collection datasets\n", - "!python mongodb_admin.py drop_collection --collection metadata\n", - "!python mongodb_admin.py drop_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "d7edd7d3-20f9-4546-afc8-25661f948d44", - "metadata": {}, - "source": [ - "### Datasets (add and drop)" - ] - }, - { - "cell_type": "markdown", - "id": "ed1597b3-767f-470c-a7d7-8fe41dd82da5", - "metadata": {}, - "source": [ - "We first need to set the dataset meta-information. For each dataset, 2 informations are required:\n", - "- the type of database in which the dataset is stored\n", - "- a path to the metadata of the dataset (stored as a yaml file).\n", - "\n", - "To later perform query on the dataset, metadata are required. In this secure server the metadata information is expected to be in the same format as [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). It is also expected to be in a `yaml` file.\n", - "\n", - "These information (dataset name, dataset type and metadata path) are stored in the `datasets` collection. Then for each dataset, its metadata is fetched from its `yaml` file and stored in a collection named `metadata`." - ] - }, - { - "cell_type": "markdown", - "id": "2678fb3f", - "metadata": {}, - "source": [ - "We then check that there is indeed no data in the dataset and metadata collections yet:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "9b7a7fae", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection datasets" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "d36e03ff", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection metadata" - ] - }, - { - "cell_type": "markdown", - "id": "d1d331ea", - "metadata": {}, - "source": [ - "We can add **one dataset** with its name, and informations on where to load the dataset and metadata file." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "53f5787d-e721-43d9-85ce-da842f173381", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added dataset PENGUIN with database REMOTE_HTTP_DB and associated metadata.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset -d \"PENGUIN\" -db \"REMOTE_HTTP_DB\" -d_url \"https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv\" -m_db \"LOCAL_DB\" -mp \"../data/collections/metadata/penguin_metadata.yaml\"" - ] - }, - { - "cell_type": "markdown", - "id": "783c7b53", - "metadata": {}, - "source": [ - "We can also show an example of how to add a dataset and its metadata stored on an S3 server. The access key id and secret access id should be updated depending on the location of the file." - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "5604d9bc", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added dataset TITANIC with database S3_DB and associated metadata.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset -d \"TITANIC\" -db \"S3_DB\" -s3b \"example\" -s3k \"data/titanic.csv\" -s3_url \"https://api-lomas-minio.lab.sspcloud.fr\" -s3_ak \"admin\" -s3_sak \"admin123\" -m_db \"S3_DB\" -m_s3b \"example\" -m_s3k \"metadata/titanic_metadata.yaml\" -m_s3_url \"https://api-lomas-minio.lab.sspcloud.fr\" -m_s3_ak \"admin\" -m_s3_sak \"admin123\"" - ] - }, - { - "cell_type": "markdown", - "id": "398f8990", - "metadata": {}, - "source": [ - "We can now see the dataset and metadata collection with the Iris dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "3005eda2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'dataset_name': 'TITANIC', 'database_type': 'S3_DB', 's3_bucket': 'example', 's3_key': 'data/titanic.csv', 'endpoint_url': 'https://api-sdd-minio.lab.sspcloud.fr', 'aws_access_key_id': 'admin', 'aws_secret_access_key': 'admin123'}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection datasets" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "7527f3f4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'TITANIC': {'': {'Schema': {'Table': {'max_ids': 1, 'PassengerId': {'type': 'int', 'lower': 1}, 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3}, 'Name': {'type': 'string'}, 'Sex': {'type': 'string', 'cardinality': 2, 'categories': ['male', 'female']}, 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0}, 'SibSp': {'type': 'int', 'lower': 0}, 'Parch': {'type': 'int', 'lower': 0}, 'Ticket': {'type': 'string'}, 'Fare': {'type': 'float', 'lower': 0.0}, 'Cabin': {'type': 'string'}, 'Embarked': {'type': 'string', 'cardinality': 3, 'categories': ['C', 'Q', 'S']}, 'Survived': {'type': 'boolean'}, 'row_privacy': True}}}, 'engine': 'csv'}}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection metadata" - ] - }, - { - "cell_type": "markdown", - "id": "a0a2076e", - "metadata": {}, - "source": [ - "Or a path to a yaml file which contains all these informations to do **multiple datasets** in one command:" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "0e42f9cb-3a02-45f5-baee-2e06edda739f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "Added datasets collection from yaml at ../data/collections/dataset_collection.yaml. \n", - "Added metadata of IRIS dataset. \n", - "Added metadata of PENGUIN dataset. \n", - "Added metadata of TITANIC dataset. \n", - "Added metadata of FSO_INCOME_SYNTHETIC dataset. \n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -c" - ] - }, - { - "cell_type": "markdown", - "id": "d1a69223", - "metadata": {}, - "source": [ - "The argument *-c* or *--clean* allows you to clear the current dataset collection before adding your collection.\n", - "\n", - "By default, *add_datasets* will only add new dataset found from the collection provided." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "7047e416", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml" - ] - }, - { - "cell_type": "markdown", - "id": "46b06b96", - "metadata": {}, - "source": [ - "Arguments :\n", - "\n", - "*-od* / *--overwrite_datasets* : Overwrite the values for **exisiting datasets** with the values provided in the yaml.\n", - "\n", - "*-om* / *--overwrite_metadata* : Overwrite the values for **exisiting metadata** with the values provided in the yaml." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "692adbde", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Datasets updated with values from yaml at ../data/collections/dataset_collection.yaml.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing datasets\n", - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -od" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "2547d3c7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Metadata updated for dataset : IRIS.\n", - "Metadata updated for dataset : PENGUIN.\n", - "Metadata updated for dataset : TITANIC.\n", - "Metadata updated for dataset : FSO_INCOME_SYNTHETIC.\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing metadata\n", - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -om" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "9bc8ea43", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Datasets updated with values from yaml at ../data/collections/dataset_collection.yaml.\n", - "Metadata updated for dataset : IRIS.\n", - "Metadata updated for dataset : PENGUIN.\n", - "Metadata updated for dataset : TITANIC.\n", - "Metadata updated for dataset : FSO_INCOME_SYNTHETIC.\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing datasets & metadata\n", - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -od -om" - ] - }, - { - "cell_type": "markdown", - "id": "87d686ae", - "metadata": {}, - "source": [ - "Let's see all the dataset collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "536b5b35", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'dataset_name': 'IRIS', 'database_type': 'REMOTE_HTTP_DB', 'dataset_url': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/iris_metadata.yaml'}}, {'dataset_name': 'PENGUIN', 'database_type': 'REMOTE_HTTP_DB', 'dataset_url': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/penguin_metadata.yaml'}}, {'dataset_name': 'TITANIC', 'database_type': 'S3_DB', 's3_bucket': 'example', 's3_key': 'data/titanic.csv', 'endpoint_url': 'https://api-sdd-minio.lab.sspcloud.fr', 'aws_access_key_id': 'admin', 'aws_secret_access_key': 'admin123', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/titanic_metadata.yaml'}}, {'dataset_name': 'FSO_INCOME_SYNTHETIC', 'database_type': 'LOCAL_DB', 'dataset_path': '../data/datasets/income_synthetic_data.csv', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/fso_income_synthetic_metadata.yaml'}}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection datasets" - ] - }, - { - "cell_type": "markdown", - "id": "0746b382-8692-445f-9ca9-0d2407a25259", - "metadata": {}, - "source": [ - "Finally let's have a look at the stored metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "c667dda0-5d0f-48c8-956c-8d8a756b7ff7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'IRIS': {'': {'Schema': {'Table': {'max_ids': 1, 'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0}, 'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0}, 'row_privacy': True, 'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0}, 'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0}, 'species': {'type': 'string', 'cardinality': 3, 'categories': ['setosa', 'versicolor', 'virginica']}}}}, 'engine': 'csv'}}, {'PENGUIN': {'': {'Schema': {'Table': {'max_ids': 1, 'row_privacy': True, 'censor_dims': False, 'species': {'type': 'string', 'cardinality': 3, 'categories': ['Adelie', 'Chinstrap', 'Gentoo']}, 'island': {'type': 'string', 'cardinality': 3, 'categories': ['Torgersen', 'Biscoe', 'Dream']}, 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0}, 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0}, 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0}, 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0}, 'sex': {'type': 'string', 'cardinality': 2, 'categories': ['MALE', 'FEMALE']}}}}, 'engine': 'csv'}}, {'TITANIC': {'': {'Schema': {'Table': {'max_ids': 1, 'PassengerId': {'type': 'int', 'lower': 1}, 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3}, 'Name': {'type': 'string'}, 'Sex': {'type': 'string', 'cardinality': 2, 'categories': ['male', 'female']}, 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0}, 'SibSp': {'type': 'int', 'lower': 0}, 'Parch': {'type': 'int', 'lower': 0}, 'Ticket': {'type': 'string'}, 'Fare': {'type': 'float', 'lower': 0.0}, 'Cabin': {'type': 'string'}, 'Embarked': {'type': 'string', 'cardinality': 3, 'categories': ['C', 'Q', 'S']}, 'Survived': {'type': 'boolean'}, 'row_privacy': True}}}, 'engine': 'csv'}}, {'FSO_INCOME_SYNTHETIC': {'': {'Schema': {'Table': {'max_ids': 1, 'region': {'type': 'int'}, 'eco_branch': {'type': 'int'}, 'profession': {'type': 'int'}, 'education': {'type': 'int'}, 'age': {'type': 'int'}, 'sex': {'type': 'int'}, 'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}}, 'engine': 'csv'}}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection metadata" - ] - }, - { - "cell_type": "markdown", - "id": "0e0b85d5", - "metadata": {}, - "source": [ - "### Users" - ] - }, - { - "cell_type": "markdown", - "id": "14ab18db-4b6d-4663-bde0-b5d9d3d3d2ee", - "metadata": {}, - "source": [ - "#### Adding users\n", - "Let's see which users are alreay loaded:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "7f450145", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "2d2ae627", - "metadata": {}, - "source": [ - "And now let's add few users." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "0f6aa33c-6bd1-4d62-ba06-3533b064340d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Mrs. Daisy with dataset IRIS, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Mrs. Daisy' --dataset 'IRIS' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "7858f019-8783-4fed-acd8-ff0107d33465", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Mr. Coldheart with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Mr. Coldheart' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "231e7d93-05ba-424a-8329-d96b0bfb4fb9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Lord McFreeze with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Lord McFreeze' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "markdown", - "id": "51b0c274-880c-44f9-9182-6cb162a54c55", - "metadata": {}, - "source": [ - "Users must all have different names, otherwise you will have an error and nothing will be done:" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "6276730e-39c2-47f1-962f-342c1acb7944", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Traceback (most recent call last):\n", - " File \"/home/onyxia/work/sdd-poc-server/server/src/mongodb_admin.py\", line 549, in \n", - " args.func(args)\n", - " File \"/home/onyxia/work/sdd-poc-server/server/src/mongodb_admin.py\", line 47, in add_user_with_budget\n", - " raise ValueError(\"Cannot add user because already exists. \")\n", - "ValueError: Cannot add user because already exists. \n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "markdown", - "id": "49f81f7e-e086-412f-8467-89b665e5559a", - "metadata": {}, - "source": [ - "If you want to add another dataset access to an existing user, just use the function `add_dataset_to_user` command." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "82a5f498-aed1-4779-9d73-b2b71dde4ce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset IRIS to user Lord McFreeze with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset_to_user --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 5.0 --delta 0.005" - ] - }, - { - "cell_type": "markdown", - "id": "06170073-49ed-4329-8101-2debdd77eb98", - "metadata": {}, - "source": [ - "Alternatively, you can create a user without assigned dataset and then add dataset in another command." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "06839270-36cf-4de7-b93c-d143c4866bc8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added user Madame Frostina.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user --user 'Madame Frostina'" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "e83378fe", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset IRIS to user Madame Frostina with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset_to_user --user 'Madame Frostina' --dataset 'IRIS' --epsilon 5.0 --delta 0.005" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "919b2652", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset PENGUIN to user Madame Frostina with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset_to_user --user 'Madame Frostina' --dataset 'PENGUIN' --epsilon 5.0 --delta 0.005" - ] - }, - { - "cell_type": "markdown", - "id": "0bed2714", - "metadata": {}, - "source": [ - "And we can also modify existing the total budget of a user:" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "ec2cce5e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Dr. Antartica with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Dr. Antartica' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "87eecb9c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Dr. Antartica for dataset PENGUIN of initial_epsilon to 20.0.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py set_budget_field --user 'Dr. Antartica' --dataset 'PENGUIN' --field initial_epsilon --value 20.0" - ] - }, - { - "cell_type": "markdown", - "id": "bbeb5dc2-e91e-4440-8df5-3e9506bf4ee1", - "metadata": {}, - "source": [ - "Let's see the current state of the database:" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "3b3f61c6-65dc-4b1e-a32e-47cdd2729ab6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'user_name': 'Mrs. Daisy', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Mr. Coldheart', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Lord McFreeze', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 20.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "4e0ae62f-ff80-4234-8102-4dccec0b284f", - "metadata": {}, - "source": [ - "Do not hesitate to re-run this command after every other command to ensure that everything runs as expected." - ] - }, - { - "cell_type": "markdown", - "id": "9ab1f5ba-68bd-4c96-bacd-b81dfa5d6302", - "metadata": {}, - "source": [ - "#### Removing users\n", - "You have just heard that the penguin named Coldheart might have malicious intentions and decide to remove his access until an investigation has been carried out. To ensure that he is not allowed to do any more queries, run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "7f341b3d-5a88-4fd9-8c97-cc70145834f1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set user Mr. Coldheart may query.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py set_may_query --user 'Mr. Coldheart' --value False" - ] - }, - { - "cell_type": "markdown", - "id": "4cc56586-f9a9-4e88-abed-51ba36a6e4f1", - "metadata": {}, - "source": [ - "Now, he won't be able to do any query (unless you re-run the query with --value True).\n", - "\n", - "A few days have passed and the investigation reveals that he was aiming to do unethical research, you can remove his dataset by doing:" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "9153d9af-b4be-4496-9f80-d140870f60fe", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Remove access to dataset PENGUIN from user Mr. Coldheart.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py del_dataset_to_user --user 'Mr. Coldheart' --dataset 'PENGUIN'" - ] - }, - { - "cell_type": "markdown", - "id": "18d411ae-a211-4997-8984-81281c6275eb", - "metadata": {}, - "source": [ - "Or delete him completely from the codebase:" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "a54e89eb-1ee1-48ad-9e00-bace8516a3ef", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted user Mr. Coldheart.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py del_user --user 'Mr. Coldheart'" - ] - }, - { - "cell_type": "markdown", - "id": "06a7c17f-da34-472a-ad7f-3ae73a1beb7b", - "metadata": {}, - "source": [ - "Let's see the resulting users:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "79fa414a-f097-4207-a628-19fa434a1ad3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'user_name': 'Mrs. Daisy', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Lord McFreeze', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 20.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "90a46a59-70ed-4a26-88cd-6ca8f1d17318", - "metadata": {}, - "source": [ - "#### Changing the budget\n", - "You also change your mind about the budget allowed to Lord McFreeze and give him a bit more on the penguin dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "0909e6c4-141e-4d57-acd2-bdc0a2d92cea", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Lord McFreeze for dataset PENGUIN of initial_epsilon to 15.0.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_epsilon --value 15.0" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "c0e110fe-4297-4559-9a95-bc0ebdfa402c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Lord McFreeze for dataset PENGUIN of initial_delta to 0.005.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_delta --value 0.005" - ] - }, - { - "cell_type": "markdown", - "id": "952d7ed4-ce1d-4a87-9319-6b57968ef20e", - "metadata": {}, - "source": [ - "Let's check all our changes by looking at the state of the database:" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "2ab46c5d-1553-4925-bd25-61c9c205dc95", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'user_name': 'Mrs. Daisy', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Lord McFreeze', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 15.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 20.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "ba7cfa86", - "metadata": {}, - "source": [ - "### Finally, everything can actually be loaded directly from a single file" - ] - }, - { - "cell_type": "markdown", - "id": "43340fc9", - "metadata": {}, - "source": [ - "Let's delete the existing user collection first:" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "597cb0b3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection users.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py drop_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "81661298", - "metadata": {}, - "source": [ - "Is is now empty:" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "e1638145", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "20b3cd2c", - "metadata": {}, - "source": [ - "We add the data based on a yaml file:" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "87b776f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added user data from yaml at ../data/collections/user_collection.yaml.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml" - ] - }, - { - "cell_type": "markdown", - "id": "c86ccbe8", - "metadata": {}, - "source": [ - "By default, *create_users_collection* will only add new users to the database." - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "a84c9392", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "No new users added, they already exist in the server\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml" - ] - }, - { - "cell_type": "markdown", - "id": "c34ca747", - "metadata": {}, - "source": [ - "If you want to clean the current users collection and replace it, you can use the argument *--clean*. " - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "7bb9a70b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "Added user data from yaml at ../data/collections/user_collection.yaml.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml --clean" - ] - }, - { - "cell_type": "markdown", - "id": "371742a4", - "metadata": {}, - "source": [ - "If you want to add new users and update the existing ones in your collection, you can use the argument *--overwrite*. This will make sure to add new users if they do not exist and replace values from existing users with the collection provided." - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "ae4ce110", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Existing users updated. \n", - "No new users added, they already exist in the server\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml --overwrite" - ] - }, - { - "cell_type": "markdown", - "id": "63853e73", - "metadata": {}, - "source": [ - "And let's see the resulting collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "77866f52", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'user_name': 'Alice', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10, 'initial_delta': 0.0001, 'total_spent_epsilon': 1, 'total_spent_delta': 1e-06}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5, 'initial_delta': 0.0005, 'total_spent_epsilon': 0.2, 'total_spent_delta': 1e-07}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 45, 'initial_delta': 0.005, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}, {'user_name': 'Dr. FSO', 'may_query': True, 'datasets_list': [{'dataset_name': 'FSO_INCOME_SYNTHETIC', 'initial_epsilon': 45, 'initial_delta': 0.005, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}, {'user_name': 'Bob', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10, 'initial_delta': 0.0001, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}, {'user_name': 'Jack', 'may_query': True, 'datasets_list': [{'dataset_name': 'TITANIC', 'initial_epsilon': 45, 'initial_delta': 0.2, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "942b58d3", - "metadata": {}, - "source": [ - "## Archives of queries" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "0ed2df36", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection queries_archives" - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "## Stopping the service: Let's not do it right now!\n", - "\n", - "To tear down the service, we simply execute the command `helm uninstall lomas-service`" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/notebooks/kubernetes_deployment_notebook.ipynb.txt b/html/de/_sources/notebooks/kubernetes_deployment_notebook.ipynb.txt deleted file mode 100644 index cf2fd0c9..00000000 --- a/html/de/_sources/notebooks/kubernetes_deployment_notebook.ipynb.txt +++ /dev/null @@ -1,264 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Kubernetes Service Deployment" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "This notebook showcases how a data owner could set up the service on a kubernetes cluster, add and make their data available to certain user. In addition, it also shows how to set up user sessions.\n", - "\n", - "We use helm charts to deploy the service on a kubernetes cluster." - ] - }, - { - "cell_type": "markdown", - "id": "91ba5946", - "metadata": {}, - "source": [ - "### Building the container images\n", - "The Lomas service is comprised of a fastapi server and a MongoDB database for keeping state about users and datasets. While the database image is public, the server image must first be built and pushed to a registry. This also holds true for the client session image.\n", - "\n", - "As a preparation step, first make sure to have a docker registry at your disposal and log into that:\n", - "\n", - "`docker login` (=> use personal token from dockerhub, has to be done only once)\n", - "\n", - "This must be done only once, the local docker credential helper will store the token locally.\n", - "\n", - "Let's now build the server image and push it to the registry. For this, we need to move from the root of this repository to `server/` and run `docker build`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4ec58145", - "metadata": {}, - "outputs": [], - "source": [ - "!cd .. && docker build --target lomas_server -t /lomas_server:latest .\n", - "!cd .. && docker push /lomas_server:latest" - ] - }, - { - "cell_type": "markdown", - "id": "5197dd0c", - "metadata": {}, - "source": [ - "This will copy the server code as well as some dummy datasets into the server image.\n", - "\n", - "As a second step, let's do the same for the client image:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "74a06cfd", - "metadata": {}, - "outputs": [], - "source": [ - "!cd ../../client/ && docker build --target lomas_client -t /lomas_client:latest .\n", - "!cd ../../client/ && docker push /lomas_client:latest" - ] - }, - { - "cell_type": "markdown", - "id": "0034a717", - "metadata": {}, - "source": [ - "### Starting the service" - ] - }, - { - "cell_type": "markdown", - "id": "92f3237b-6f13-4c52-a9f2-82d94f0b7e66", - "metadata": {}, - "source": [ - "We use a Helm chart to deploy the service on a Kubernetes cluster. For the next part of this notebook to work correctly, one must have access to a Kubernetes cluster with sufficient rights and an environment with correctly configured helm and kubectl command line tools.\n", - "\n", - "The lomas-server chart is located at `server/deploy/helm/charts/lomas_server`, let us change our working directory to this location." - ] - }, - { - "cell_type": "code", - "execution_count": 90, - "id": "e249d717", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../deploy/helm/charts/lomas_server')" - ] - }, - { - "cell_type": "markdown", - "id": "11075ea0", - "metadata": {}, - "source": [ - "The `values.yaml` file contains all the configuration values for the service. We must now update the `image.repository` field to the one we pushed the server container image to. One can also change the url to which the service will be published with `ingress.hosts[0].host` and `ingress.tls.hosts[0]` (or disable this feature by setting `ingress.enabled` to `False`).\n", - "\n", - " => Update `values.yaml` file\n", - "\n", - "Password and secrets will be deployed in kubernetes secrets and other service parameters will be deployed in configMaps.\n", - "\n", - "As previously stated, the service is made up of a server and a MongoDB database. Before installing the chart, we must thus first download the MongoDB dependency." - ] - }, - { - "cell_type": "code", - "execution_count": 91, - "id": "fe550e12", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Saving 1 charts\n", - "Downloading mongodb from repo oci://registry-1.docker.io/bitnamicharts\n", - "Pulled: registry-1.docker.io/bitnamicharts/mongodb:13.18.1\n", - "Digest: sha256:f3b2a691537260044746bc4a8898e9ae68e8c29864639737b6da920f99aebe97\n", - "Deleting outdated charts\n" - ] - } - ], - "source": [ - "!helm dependency update" - ] - }, - { - "cell_type": "markdown", - "id": "2913bce4", - "metadata": {}, - "source": [ - "Now the chart is ready to be installed, so let the magic happen!" - ] - }, - { - "cell_type": "code", - "execution_count": 92, - "id": "5ed0e2a9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "W0918 07:49:26.628394 768420 warnings.go:70] annotation \"kubernetes.io/ingress.class\" is deprecated, please use 'spec.ingressClassName' instead\n", - "NAME: lomas-service\n", - "LAST DEPLOYED: Mon Sep 18 07:49:24 2023\n", - "NAMESPACE: user-paulineml\n", - "STATUS: deployed\n", - "REVISION: 1\n", - "TEST SUITE: None\n", - "NOTES:\n", - "1. Get the application URL by running these commands:\n", - " https://sdd-demo.lab.sspcloud.fr/\n" - ] - } - ], - "source": [ - "!helm install -f values.yaml lomas-service ." - ] - }, - { - "cell_type": "markdown", - "id": "527d4837", - "metadata": {}, - "source": [ - "The installation notes show the url at which the server is exposed. One can have a look at the server state by checking `/state` and the api docummentation by visiting `/docs` in their browser.\n", - "\n", - "One can also check the whether the service started without issues by using the `kubectl get all` command as well as inspecting the server logs with `kubectl logs `" - ] - }, - { - "cell_type": "markdown", - "id": "fda13679", - "metadata": {}, - "source": [ - "### Starting the client session\n", - "\n", - "The client deployment Helm chart is located in another directory, so let's again move our working directory" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ad2a7805", - "metadata": {}, - "outputs": [], - "source": [ - "os.chdir(\"../../../../../client/deploy/helm/charts/lomas_client\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Again, one needs to update the `values.yaml` file with the desired values. The important fields are `ingress.hosts[0].host` and `ingress.tls.hosts[0]` for the url, `password` for the user session and `image.repository` for specifying the previously built image. Make sure to change the `nameOverride`, `fullnameOverride` and url when deploying multiple client images.\n", - "\n", - "`=> update values.yaml`\n", - "\n", - "Similarly to the server deployment, let's install the client Helm chart:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "outputs": [], - "source": [ - "!helm install -f values.yaml lomas-client ." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The user session should now be available at the specified url, just type the password and you are in!\n", - "\n", - "If the service was started with developper mode to false, move to the `admin_notebook` to learn how to administer your freshly deployed service.\n", - "\n", - "Once users and datasets have been added to the service, one can start to experiment with it. The URL to use for the service is the one defined in the server `values.yaml` file. " - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "### Stopping the service\n", - "\n", - "To tear down the service, we simply execute the command `helm uninstall lomas-service`. The same goes for the client session with `helm uninstall lomas-client`." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/notebooks/local_admin_notebook.ipynb.txt b/html/de/_sources/notebooks/local_admin_notebook.ipynb.txt deleted file mode 100644 index b2da6767..00000000 --- a/html/de/_sources/notebooks/local_admin_notebook.ipynb.txt +++ /dev/null @@ -1,1840 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Lomas-server: CLI administration" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "This notebook showcases how data owner could set up the server, add make their data available to certain users. It explains the different steps required." - ] - }, - { - "cell_type": "markdown", - "id": "de384c88-559e-4384-a49b-1664ffdd6692", - "metadata": {}, - "source": [ - "# Start the server" - ] - }, - { - "cell_type": "markdown", - "id": "92f3237b-6f13-4c52-a9f2-82d94f0b7e66", - "metadata": {}, - "source": [ - "## Create a docker volume\n", - "The first step is to create a docker volume for mongodb, which will hold all the \"admin\" data of the server. Docker volumes are persistent storage spaces that are managed by docker and can be mounted in containers. To create the volume use `docker volume create mongodata`. This must be done only once, and we use bind mounts for the server, so no need to create volumes for that." - ] - }, - { - "cell_type": "markdown", - "id": "87093f8e-68b1-4f1e-9e66-97c3885b3e48", - "metadata": {}, - "source": [ - "In a terminal run: `docker volume create mongodata`. In output you should see `mongodata` written." - ] - }, - { - "cell_type": "markdown", - "id": "f6829afb-d822-48e4-ba49-5daf0d79db7e", - "metadata": {}, - "source": [ - "## Start server\n", - "The second step is to start the server. Therefore the config file `configs/example_config.yaml` has to be adapted. The data owner must make sure to set the develop mode to False, specify the database type and ports. For this notebook, we will keep the default and use a mongodb on port 27017. Note: Keep in mind that if the configuration file is modified then the `docker-compose` has to be modified accordingly. This is out of scope for this notebook." - ] - }, - { - "cell_type": "markdown", - "id": "2408425a-13c6-4b89-a1fe-88491850fe10", - "metadata": {}, - "source": [ - "In a terminal run `docker compose up`. This will start the server and the mongodb, each running in its own Docker container. In addition, it will also start a client session container for demonstration purposes and a streamlit container, more on that later." - ] - }, - { - "cell_type": "markdown", - "id": "244da0f8", - "metadata": {}, - "source": [ - "To check that all containers are indeed running, run `docker ps`. You should be able to see a container for the server (`lomas_server_dev`), for the client (`lomas_client_dev`), for the streamlit (`lomas_streamlit_dev`) and one for the mongo database (`mongodb`)." - ] - }, - { - "cell_type": "markdown", - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "source": [ - "## Access the server to administrate the mongoDB" - ] - }, - { - "cell_type": "markdown", - "id": "4a8c8115", - "metadata": {}, - "source": [ - "To interact with the mongoDB, we first need to access the server Docker container from where we will run the commands. To do that from inside this Jupyter Notebook, we will need to use the Docker client library. Let's first install it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f6863a6d", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "!pip install docker" - ] - }, - { - "cell_type": "markdown", - "id": "b12b414a", - "metadata": {}, - "source": [ - "We can now import the library, create the client allowing us to interact with Docker, and finally, access the server container." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "112e4156", - "metadata": {}, - "outputs": [], - "source": [ - "import docker\n", - "client = docker.DockerClient()\n", - "server_container = client.containers.get(\"lomas_server_dev\")" - ] - }, - { - "cell_type": "markdown", - "id": "36b475e6", - "metadata": {}, - "source": [ - "To execute commands inside that Docker container, you can use the `exec_run` method which will return an ExecResult object, from which you can retrieve the output of the command. Let's see in the following example:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "dc0349be", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "__init__.py\n", - "__pycache__\n", - "admin_database\n", - "administration\n", - "app.py\n", - "constants.py\n", - "dataset_store\n", - "dp_queries\n", - "mongodb_admin.py\n", - "mongodb_admin_cli.py\n", - "private_dataset\n", - "tests\n", - "utils\n", - "uvicorn_serve.py\n", - "\n" - ] - } - ], - "source": [ - "response = server_container.exec_run(\"ls\")\n", - "print(response.output.decode('utf-8'))" - ] - }, - { - "cell_type": "markdown", - "id": "9f35fd20-715c-483b-88e4-449c287ba61d", - "metadata": {}, - "source": [ - "Now, you are ready to interact with the database and add users." - ] - }, - { - "cell_type": "markdown", - "id": "d368d6a6-f1fe-4f65-9ce1-38c0b39584d1", - "metadata": {}, - "source": [ - "# Prepare the database" - ] - }, - { - "cell_type": "markdown", - "id": "b37c19b8-303d-4fe8-b515-33ed1099c581", - "metadata": {}, - "source": [ - "## Visualise all options\n", - "You can visualise all the options offered by the database by running the command `python mongodb_admin_cli.py --help`. We will go through through each of them in the rest of the notebook." - ] - }, - { - "cell_type": "markdown", - "id": "e70abf6d", - "metadata": {}, - "source": [ - "We prepare the function `run_command` to have a cleaner output of the commands in the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f9277a43", - "metadata": {}, - "outputs": [], - "source": [ - "from ast import literal_eval\n", - "\n", - "def run(command, to_dict=False):\n", - " response = server_container.exec_run(command)\n", - " output = response.output.decode('utf-8').replace(\"'\", '\"')\n", - " if \"] -\" in output:\n", - " output = output.split(\"] -\")[1].strip()\n", - " if to_dict:\n", - " if len(output):\n", - " output = literal_eval(output)\n", - " return output\n", - " return print(output)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "fafa4e34", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "usage: mongodb_admin_cli.py [-h]\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,add_users_via_yaml,show_archives,get_users,get_user_datasets,add_dataset,add_datasets_via_yaml,del_dataset,show_dataset,show_metadata,get_datasets,drop_collection,show_collection}\n", - " ...\n", - "\n", - "MongoDB administration script for the database\n", - "\n", - "options:\n", - " -h, --help show this help message and exit\n", - "\n", - "subcommands:\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,add_users_via_yaml,show_archives,get_users,get_user_datasets,add_dataset,add_datasets_via_yaml,del_dataset,show_dataset,show_metadata,get_datasets,drop_collection,show_collection}\n", - " user database administration operations\n", - " add_user add user to users collection\n", - " add_user_with_budget\n", - " add user with budget to users collection\n", - " del_user delete user from users collection\n", - " add_dataset_to_user\n", - " add dataset with initialized budget values for a user\n", - " del_dataset_to_user\n", - " delete dataset for user in users collection\n", - " set_budget_field set budget field to given value for given user and\n", - " dataset\n", - " set_may_query set may query field to given value for given user\n", - " show_user show all metadata of user\n", - " add_users_via_yaml create users collection from yaml file\n", - " show_archives show all previous queries from a user\n", - " get_users get the list of all users in \"users\" collection\n", - " get_user_datasets get the list of all datasets from a user\n", - " add_dataset set in which database the dataset is stored\n", - " add_datasets_via_yaml\n", - " create dataset to database type collection\n", - " del_dataset delete dataset and metadata from datasets and metadata\n", - " collection\n", - " show_dataset show a dataset from the dataset collection\n", - " show_metadata show metadata from the metadata collection\n", - " get_datasets get the list of all datasets in \"datasets\" collection\n", - " drop_collection delete collection from database\n", - " show_collection print a collection\n", - "\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py --help\")" - ] - }, - { - "cell_type": "markdown", - "id": "579b9571", - "metadata": {}, - "source": [ - "And finally, let's delete all existing data from database to start clean:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "18a3681c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection datasets.\n", - "Deleted collection metadata.\n", - "Deleted collection users.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py drop_collection --collection datasets\")\n", - "run(\"python mongodb_admin_cli.py drop_collection --collection metadata\")\n", - "run(\"python mongodb_admin_cli.py drop_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "d7edd7d3-20f9-4546-afc8-25661f948d44", - "metadata": {}, - "source": [ - "## Datasets (add and drop)" - ] - }, - { - "cell_type": "markdown", - "id": "ed1597b3-767f-470c-a7d7-8fe41dd82da5", - "metadata": {}, - "source": [ - "We first need to set the dataset meta-information. For each dataset, 2 informations are required:\n", - "- the type of database in which the dataset is stored\n", - "- a path to the metadata of the dataset (stored as a yaml file).\n", - "\n", - "To later perform query on the dataset, metadata are required. In this secure server the metadata information is expected to be in the same format as [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). It is also expected to be in a `yaml` file.\n", - "\n", - "These information (dataset name, dataset type and metadata path) are stored in the `datasets` collection. Then for each dataset, its metadata is fetched from its `yaml` file and stored in a collection named `metadata`." - ] - }, - { - "cell_type": "markdown", - "id": "2678fb3f", - "metadata": {}, - "source": [ - "We then check that there is indeed no data in the dataset and metadata collections yet:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "9b7a7fae", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection datasets\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "d36e03ff", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection metadata\")" - ] - }, - { - "cell_type": "markdown", - "id": "d1d331ea", - "metadata": {}, - "source": [ - "We can add **one dataset** with its name, database type and path to medata file:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "53f5787d-e721-43d9-85ce-da842f173381", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added dataset IRIS with database PATH_DB and associated metadata.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_dataset -d IRIS -db PATH_DB -d_path https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv -m_db PATH_DB -mp ../data/collections/metadata/iris_metadata.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "398f8990", - "metadata": {}, - "source": [ - "We can now see the dataset and metadata collection with the Iris dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "3005eda2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'dataset_name': 'IRIS',\n", - " 'database_type': 'PATH_DB',\n", - " 'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/iris_metadata.yaml'}}]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection datasets\", to_dict=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "7527f3f4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'IRIS': {'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0},\n", - " 'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0},\n", - " 'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0},\n", - " 'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0},\n", - " 'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['setosa', 'versicolor', 'virginica']}}}}]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection metadata\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "a0a2076e", - "metadata": {}, - "source": [ - "Or a path to a yaml file which contains all these informations to do **multiple datasets** in one command:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "0e42f9cb-3a02-45f5-baee-2e06edda739f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "2024-06-05 09:59:46,703 - INFO - [mongodb_admin.py:710 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -c\")" - ] - }, - { - "cell_type": "markdown", - "id": "19b86f6a", - "metadata": {}, - "source": [ - "The argument *-c* or *--clean* allow you to clear the current dataset collection before adding your collection.\n", - "\n", - "By default, *add_datasets* will only add new dataset found from the collection provided." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "88bbdcf2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "2024-06-05 09:59:48,726 - INFO - [mongodb_admin.py:755 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "3a922c76", - "metadata": {}, - "source": [ - "Arguments :\n", - "\n", - "*-od* / *--overwrite_datasets* : Overwrite the values for **exisiting datasets** with the values provided in the yaml.\n", - "\n", - "*-om* / *--overwrite_metadata* : Overwrite the values for **exisiting metadata** with the values provided in the yaml." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "240928ab", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Existing datasets updated with new collection\n", - "2024-06-05 09:59:50,917 - INFO - [mongodb_admin.py:755 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing datasets\n", - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -od\")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "80de6b9c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Metadata updated for dataset : IRIS.\n", - "2024-06-05 09:59:52,741 - INFO - [mongodb_admin.py:749 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing metadata\n", - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -om\")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "b1a9f413", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Existing datasets updated with new collection\n", - "2024-06-05 09:59:54,418 - INFO - [mongodb_admin.py:749 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing datasets & metadata\n", - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -od -om\")" - ] - }, - { - "cell_type": "markdown", - "id": "87d686ae", - "metadata": {}, - "source": [ - "Let's see all the dataset collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "536b5b35", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'dataset_name': 'IRIS',\n", - " 'database_type': 'PATH_DB',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/iris_metadata.yaml'},\n", - " 'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv'},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'database_type': 'PATH_DB',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/penguin_metadata.yaml'},\n", - " 'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv'},\n", - " {'dataset_name': 'TITANIC',\n", - " 'database_type': 'S3_DB',\n", - " 'metadata': {'database_type': 'S3_DB',\n", - " 's3_bucket': 'example',\n", - " 's3_key': 'metadata/titanic_metadata.yaml',\n", - " 'endpoint_url': 'https://api-lomas-minio.lab.sspcloud.fr',\n", - " 'aws_access_key_id': 'admin',\n", - " 'aws_secret_access_key': 'admin123'},\n", - " 's3_bucket': 'example',\n", - " 's3_key': 'data/titanic.csv',\n", - " 'endpoint_url': 'https://api-lomas-minio.lab.sspcloud.fr',\n", - " 'aws_access_key_id': 'admin',\n", - " 'aws_secret_access_key': 'admin123'},\n", - " {'dataset_name': 'FSO_INCOME_SYNTHETIC',\n", - " 'database_type': 'PATH_DB',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/fso_income_synthetic_metadata.yaml'},\n", - " 'dataset_path': '../data/datasets/income_synthetic_data.csv'}]" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection datasets\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "0746b382-8692-445f-9ca9-0d2407a25259", - "metadata": {}, - "source": [ - "Finally let's have a look at the stored metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "c667dda0-5d0f-48c8-956c-8d8a756b7ff7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'IRIS': {'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0},\n", - " 'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0},\n", - " 'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0},\n", - " 'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0},\n", - " 'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['setosa', 'versicolor', 'virginica']}}}},\n", - " {'PENGUIN': {'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}},\n", - " {'TITANIC': {'': {'Schema': {'Table': {'max_ids': 1,\n", - " 'PassengerId': {'type': 'int', 'lower': 1},\n", - " 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3},\n", - " 'Name': {'type': 'string'},\n", - " 'Sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['male', 'female']},\n", - " 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0},\n", - " 'SibSp': {'type': 'int', 'lower': 0},\n", - " 'Parch': {'type': 'int', 'lower': 0},\n", - " 'Ticket': {'type': 'string'},\n", - " 'Fare': {'type': 'float', 'lower': 0.0},\n", - " 'Cabin': {'type': 'string'},\n", - " 'Embarked': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['C', 'Q', 'S']},\n", - " 'Survived': {'type': 'boolean'},\n", - " 'row_privacy': True}}},\n", - " 'engine': 'csv'}},\n", - " {'FSO_INCOME_SYNTHETIC': {'max_ids': 1,\n", - " 'columns': {'region': {'type': 'int'},\n", - " 'eco_branch': {'type': 'int'},\n", - " 'profession': {'type': 'int'},\n", - " 'education': {'type': 'int'},\n", - " 'age': {'type': 'int'},\n", - " 'sex': {'type': 'int'},\n", - " 'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}}]" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection metadata\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "509d0b26", - "metadata": {}, - "source": [ - "If we are interested in a specific dataset, we can also show its collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "3db07639", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'dataset_name': 'IRIS',\n", - " 'database_type': 'PATH_DB',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/iris_metadata.yaml'},\n", - " 'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv'}" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_dataset --dataset IRIS\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "48550826", - "metadata": {}, - "source": [ - "And its associated metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "efd9931f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0},\n", - " 'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0},\n", - " 'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0},\n", - " 'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0},\n", - " 'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['setosa', 'versicolor', 'virginica']}}}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_metadata --dataset IRIS\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "594b83a9", - "metadata": {}, - "source": [ - "We can also get list of all datasets in the 'datasets' collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "a6e21f16", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['IRIS', 'PENGUIN', 'TITANIC', 'FSO_INCOME_SYNTHETIC']" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py get_datasets\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "0e0b85d5", - "metadata": {}, - "source": [ - "## Users" - ] - }, - { - "cell_type": "markdown", - "id": "14ab18db-4b6d-4663-bde0-b5d9d3d3d2ee", - "metadata": {}, - "source": [ - "### Add user\n", - "Let's see which users are alreay loaded:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "7f450145", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "2d2ae627", - "metadata": {}, - "source": [ - "And now let's add few users." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "0f6aa33c-6bd1-4d62-ba06-3533b064340d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Mrs. Daisy with dataset IRIS, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Mrs. Daisy' --dataset 'IRIS' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "7858f019-8783-4fed-acd8-ff0107d33465", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Mr. Coldheart with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Mr. Coldheart' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "231e7d93-05ba-424a-8329-d96b0bfb4fb9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Lord McFreeze with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Lord McFreeze' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "markdown", - "id": "51b0c274-880c-44f9-9182-6cb162a54c55", - "metadata": {}, - "source": [ - "Users must all have different names, otherwise you will have an error and nothing will be done:" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "6276730e-39c2-47f1-962f-342c1acb7944", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Traceback (most recent call last):\n", - " File \"/code/mongodb_admin_cli.py\", line 461, in \n", - " function_map[args.func.__name__](args)\n", - " File \"/code/mongodb_admin_cli.py\", line 396, in \n", - " \"add_user_with_budget\": lambda args: add_user_with_budget(\n", - " ^^^^^^^^^^^^^^^^^^^^^\n", - " File \"/code/mongodb_admin.py\", line 50, in wrapper_decorator\n", - " raise ValueError(\n", - "ValueError: User Lord McFreeze already exists in user collection\n", - "\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "markdown", - "id": "49f81f7e-e086-412f-8467-89b665e5559a", - "metadata": {}, - "source": [ - "If you want to add another dataset access to an existing user, just use the function `add_dataset_to_user` command." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "82a5f498-aed1-4779-9d73-b2b71dde4ce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset IRIS to user Lord McFreeze with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_dataset_to_user --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 5.0 --delta 0.005\")" - ] - }, - { - "cell_type": "markdown", - "id": "06170073-49ed-4329-8101-2debdd77eb98", - "metadata": {}, - "source": [ - "Alternatively, you can create a user without assigned dataset and then add dataset in another command." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "06839270-36cf-4de7-b93c-d143c4866bc8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added user Madame Frostina.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user --user 'Madame Frostina'\")" - ] - }, - { - "cell_type": "markdown", - "id": "df41cea4-8219-41a1-9ce3-fad5409db299", - "metadata": {}, - "source": [ - "Let's see the default parameters after the user creation:" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "1dbe0b34-ef3f-49b9-9153-dd5d09b00e4e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': []}" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_user --user 'Madame Frostina'\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "f4c62a55-92cb-47af-90be-80c7d13db1e6", - "metadata": {}, - "source": [ - "Let's give her access to a dataset with a budget:" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "e83378fe", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset IRIS to user Madame Frostina with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_dataset_to_user --user 'Madame Frostina' --dataset 'IRIS' --epsilon 5.0 --delta 0.005\")" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "919b2652", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset PENGUIN to user Madame Frostina with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_dataset_to_user --user 'Madame Frostina' --dataset 'PENGUIN' --epsilon 5.0 --delta 0.005\")" - ] - }, - { - "cell_type": "markdown", - "id": "2dab150b-4ad0-410a-b1eb-e448f8f0d79e", - "metadata": {}, - "source": [ - "Now let's see the user Madame Frostina details to check all is in order:" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "8833f27e-a342-400a-b868-facf9a44dc6f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'user_name': 'Madame Frostina',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_user --user 'Madame Frostina'\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "0bed2714", - "metadata": {}, - "source": [ - "And we can also modify existing the total budget of a user:" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "e3b75cca", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Dr. Antartica with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Dr. Antartica' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "87eecb9c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Dr. Antartica for dataset PENGUIN of initial_epsilon to 20.0.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py set_budget_field --user 'Dr. Antartica' --dataset 'PENGUIN' --field initial_epsilon --value 20.0\")" - ] - }, - { - "cell_type": "markdown", - "id": "bbeb5dc2-e91e-4440-8df5-3e9506bf4ee1", - "metadata": {}, - "source": [ - "Let's see the current state of the database:" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "3b3f61c6-65dc-4b1e-a32e-47cdd2729ab6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Mrs. Daisy',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Mr. Coldheart',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Lord McFreeze',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Madame Frostina',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 20.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}]" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "4e0ae62f-ff80-4234-8102-4dccec0b284f", - "metadata": {}, - "source": [ - "Do not hesitate to re-run this command after every other command to ensure that everything runs as expected." - ] - }, - { - "cell_type": "markdown", - "id": "9ab1f5ba-68bd-4c96-bacd-b81dfa5d6302", - "metadata": {}, - "source": [ - "### Remove user\n", - "You have just heard that the penguin named Coldheart might have malicious intentions and decide to remove his access until an investigation has been carried out. To ensure that he is not allowed to do any more queries, run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "7f341b3d-5a88-4fd9-8c97-cc70145834f1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set user Mr. Coldheart may query to False.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py set_may_query --user 'Mr. Coldheart' --value False\")" - ] - }, - { - "cell_type": "markdown", - "id": "4cc56586-f9a9-4e88-abed-51ba36a6e4f1", - "metadata": {}, - "source": [ - "Now, he won't be able to do any query (unless you re-run the query with --value True).\n", - "\n", - "A few days have passed and the investigation reveals that he was aiming to do unethical research, you can remove his dataset by doing:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "9153d9af-b4be-4496-9f80-d140870f60fe", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Remove access to dataset PENGUIN from user Mr. Coldheart.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py del_dataset_to_user --user 'Mr. Coldheart' --dataset 'PENGUIN'\")" - ] - }, - { - "cell_type": "markdown", - "id": "18d411ae-a211-4997-8984-81281c6275eb", - "metadata": {}, - "source": [ - "Or delete him completely from the codebase:" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "a54e89eb-1ee1-48ad-9e00-bace8516a3ef", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted user Mr. Coldheart.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py del_user --user 'Mr. Coldheart'\")" - ] - }, - { - "cell_type": "markdown", - "id": "06a7c17f-da34-472a-ad7f-3ae73a1beb7b", - "metadata": {}, - "source": [ - "Let's see the resulting users:" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "79fa414a-f097-4207-a628-19fa434a1ad3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Mrs. Daisy',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Lord McFreeze',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Madame Frostina',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 20.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}]" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "90a46a59-70ed-4a26-88cd-6ca8f1d17318", - "metadata": {}, - "source": [ - "### Change budget\n", - "You also change your mind about the budget allowed to Lord McFreeze and give him a bit more on the penguin dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "0909e6c4-141e-4d57-acd2-bdc0a2d92cea", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Lord McFreeze for dataset PENGUIN of initial_epsilon to 15.0.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_epsilon --value 15.0\")" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "c0e110fe-4297-4559-9a95-bc0ebdfa402c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Lord McFreeze for dataset PENGUIN of initial_delta to 0.005.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_delta --value 0.005\")" - ] - }, - { - "cell_type": "markdown", - "id": "952d7ed4-ce1d-4a87-9319-6b57968ef20e", - "metadata": {}, - "source": [ - "Let's check all our changes by looking at the state of the database:" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "2ab46c5d-1553-4925-bd25-61c9c205dc95", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Mrs. Daisy',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Lord McFreeze',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 15.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Madame Frostina',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 20.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}]" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "ba7cfa86", - "metadata": {}, - "source": [ - "### Finally all can be loaded fom a file direcly" - ] - }, - { - "cell_type": "markdown", - "id": "43340fc9", - "metadata": {}, - "source": [ - "Let's delete the existing user collection first:" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "597cb0b3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection users.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py drop_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "81661298", - "metadata": {}, - "source": [ - "Is is now empty:" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "e1638145", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "20b3cd2c", - "metadata": {}, - "source": [ - "We add the data based on a yaml file:" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "87b776f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added user data from yaml.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "76263ebd", - "metadata": {}, - "source": [ - "By default, *add_users_via_yaml* will only add new users to the database." - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "7f597f68", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "No new users added, they already exist in the server\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "3df278ef", - "metadata": {}, - "source": [ - "If you want to clean the current users collection and replace it, you can use the argument *--clean*. " - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "5a610b9f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "2024-06-05 10:00:45,678 - INFO - [mongodb_admin.py:464 - add_users_via_yaml()\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml --clean\")" - ] - }, - { - "cell_type": "markdown", - "id": "c933165a", - "metadata": {}, - "source": [ - "If you want to add new users and update the existing ones in your collection, you can use the argument *--overwrite*. This will make sure to add new users if they do not exist and replace values from existing users with the collection provided." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "fd621ac3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Existing users updated. \n", - "2024-06-05 10:00:47,300 - INFO - [mongodb_admin.py:466 - add_users_via_yaml()\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml --overwrite\")" - ] - }, - { - "cell_type": "markdown", - "id": "63853e73", - "metadata": {}, - "source": [ - "And let's see the resulting collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "77866f52", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Alice',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.0001,\n", - " 'total_spent_epsilon': 1.0,\n", - " 'total_spent_delta': 1e-06},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.0005,\n", - " 'total_spent_epsilon': 0.2,\n", - " 'total_spent_delta': 1e-07}]},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Dr. FSO',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'FSO_INCOME_SYNTHETIC',\n", - " 'initial_epsilon': 45.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Bob',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.0001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Jack',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'TITANIC',\n", - " 'initial_epsilon': 45.0,\n", - " 'initial_delta': 0.2,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}]" - ] - }, - "execution_count": 48, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "b9510647", - "metadata": {}, - "source": [ - "To get a list of all users in the 'users' collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "7e70e971", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[\"Alice\", \"Dr. Antartica\", \"Dr. FSO\", \"Bob\", \"Jack\"]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py get_users\")" - ] - }, - { - "cell_type": "markdown", - "id": "e559bc1e", - "metadata": {}, - "source": [ - "We can also get a list of all datasets allocated to an user:" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "81b73bd6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[\"IRIS\", \"PENGUIN\"]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py get_user_datasets --user Alice\")" - ] - }, - { - "cell_type": "markdown", - "id": "1a946132", - "metadata": {}, - "source": [ - "## Archives of queries" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "8025ef4d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_archives --user Alice\")" - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "## Stop the server: do not do it now !\n", - "To tear down the service, first do `ctrl+C` in the terminal where you had done `docker compose up`. Wait for the command to finish executing and then run `docker compose down`. This will also delete all the containers but the volume will stay in place. " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.0rc1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/notebooks/local_deployment_notebook.ipynb.txt b/html/de/_sources/notebooks/local_deployment_notebook.ipynb.txt deleted file mode 100644 index 66cbdeb4..00000000 --- a/html/de/_sources/notebooks/local_deployment_notebook.ipynb.txt +++ /dev/null @@ -1,105 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Local Service Deployment - How to" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "This notebook showcases how to set up the service, add and make data available to users in a local environment. In addition, it also shows how to set up a user session for testing.\n", - "\n", - "We use docker and docker compose files to automate the local deployment." - ] - }, - { - "cell_type": "markdown", - "id": "92f3237b-6f13-4c52-a9f2-82d94f0b7e66", - "metadata": {}, - "source": [ - "## Docker volume and config setup\n", - "The first step is to create a docker volume for mongodb, which will hold all the \"admin\" data of the server. Docker volumes are persistent storage spaces that are managed by docker and can be mounted in containers. To create the volume, use\n", - "\n", - "`docker volume create mongodata`\n", - "\n", - "and you should see `mongodata` printed to the console. This must be done only once, and we use bind mounts for the server, so no need to create volumes for that.\n", - "\n", - "Secondly, one must adapt the yaml config files in `server/configs/`. The default values should be enough to start a demo version of the service though. Keep in mind that certain parameter modification require to also update the docker compose files accordingly. This is out of scope for this notebook." - ] - }, - { - "cell_type": "markdown", - "id": "f6829afb-d822-48e4-ba49-5daf0d79db7e", - "metadata": {}, - "source": [ - "## Service start\n", - "\n", - "In `server/` you will find the `docker-compose.yml` file that is used for deploying the service locally. In a terminal, `cd` into `server/`and run\n", - "\n", - "`docker compose up`\n", - "\n", - "This will start the server, the mongodb as well as a user environment, each running in their own container. The first time this command is run, required containers images will automatically be built locally or downloaded from dockerhub, depending on the particular image. Note that container logs are all printed to your terminal.\n", - "\n", - "To check that all containers are indeed running, run `docker ps`. You should be able to see a container for the server (`lomas_server_dev`), for the client (`lomas_client_dev`) and one for the mongo database (`mongodb`).\n", - "\n", - "## Server test\n", - "\n", - "Appart from the logs in the console, we can also check the server state through our web browser. To get the server state, use the following url:\n", - "\n", - "http://127.0.0.1/state\n", - "\n", - "If everything went well, the page should show a few messages and the state should be \"LIVE\".\n", - "\n", - "For exploring and testing API endpoints of the server, one can also use the automatically generated Swagger docummentation page with \n", - "\n", - "http://127.0.0.1/docs\n", - "\n", - "## User session\n", - "\n", - "Now that we know the server is in \"LIVE\" state, one can run one of our client demo notebooks from the user session. The default configuration starts a JupyterLab server at \n", - "\n", - "http://127.0.0.1:8.8.8.8/\n", - "\n", - "and the default password for that is \"dprocks\". The root directory of the client session gives access to all the client demo notebooks, follow them for more!\n", - "\n", - "For starting user sessions alone, there is an alternative `docker-compose.yml` file in the `client` directory of this repository. The configuration files for the user session are located in `client/configs`, regardless of the chosen docker compose file.\n", - "\n", - "## Service stop\n", - "\n", - "For stopping the service, simply run \n", - "\n", - "`CTRL + c` and then `docker compose down`\n", - "\n", - "in your terminal. This will stop and remove all the service containers. Note that the mongodb volume will persist and must not be recreate on subsequent startups of the service.\n", - "\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/html/de/_sources/notebooks/s3_example_notebook.ipynb.txt b/html/de/_sources/notebooks/s3_example_notebook.ipynb.txt deleted file mode 100644 index 4dcba100..00000000 --- a/html/de/_sources/notebooks/s3_example_notebook.ipynb.txt +++ /dev/null @@ -1,968 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3f18d338", - "metadata": {}, - "source": [ - "# S3 example" - ] - }, - { - "cell_type": "markdown", - "id": "01ae30d2", - "metadata": {}, - "source": [ - "## Step 1: Install the library\n", - "To interact with the secure server on which the data is stored, one first needs to install the library `lomas-client` on her local developping environment. \n", - "\n", - "It can be installed via the pip command:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28fbdd79-8c15-49a9-bcf9-fcdeac09d2b5", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install lomas-client" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "6fb569fc", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client.client import Client\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "9c63718b", - "metadata": {}, - "source": [ - "## Step 2: Initialise the client\n", - "\n", - "Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server. \n", - "\n", - "To create the client, one needs to give it a few parameters:\n", - "- a url: the root application endpoint to the remote secure server.\n", - "- user_name: her name as registered in the database (Jack)\n", - "- dataset_name: the name of the dataset that we want to query (TITANIC)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "941991f7", - "metadata": {}, - "outputs": [], - "source": [ - "APP_URL = \"http://localhost:80\"\n", - "USER_NAME = \"Jack\"\n", - "DATASET_NAME = \"TITANIC\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "markdown", - "id": "9b9a5f13", - "metadata": {}, - "source": [ - "## Step 3: Understand the functionnalities of the library" - ] - }, - { - "cell_type": "markdown", - "id": "c7cb5531", - "metadata": {}, - "source": [ - "### Getting dataset metadata" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "d15cbe39", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'PassengerId': {'type': 'int', 'lower': 1},\n", - " 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3},\n", - " 'Name': {'type': 'string'},\n", - " 'Sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['male', 'female']},\n", - " 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0},\n", - " 'SibSp': {'type': 'int', 'lower': 0},\n", - " 'Parch': {'type': 'int', 'lower': 0},\n", - " 'Ticket': {'type': 'string'},\n", - " 'Fare': {'type': 'float', 'lower': 0.0},\n", - " 'Cabin': {'type': 'string'},\n", - " 'Embarked': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['C', 'Q', 'S']},\n", - " 'Survived': {'type': 'boolean'}}}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "titanic_metadata = client.get_dataset_metadata()\n", - "titanic_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "5a3c899d", - "metadata": {}, - "source": [ - "### Get a dummy dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "01f4365a", - "metadata": {}, - "outputs": [], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "3f553b29", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 12)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
PassengerIdPclassNameSexAgeSibSpParchTicketFareCabinEmbarkedSurvived
0273310male96.16085840784452X2421.785941hQFalse
198461Umale23.24699254056743O2503.9821292SFalse
232652Amale94.93695046119621a4833.935352cCFalse
348601qfemale94.14363382665630l399.928019pQFalse
492262Fmale79.94033866342562F6397.051061GCTrue
\n", - "
" - ], - "text/plain": [ - " PassengerId Pclass Name Sex Age SibSp Parch Ticket \n", - "0 2733 1 0 male 96.160858 4078 4452 X \\\n", - "1 9846 1 U male 23.246992 5405 6743 O \n", - "2 3265 2 A male 94.936950 4611 9621 a \n", - "3 4860 1 q female 94.143633 8266 5630 l \n", - "4 9226 2 F male 79.940338 6634 2562 F \n", - "\n", - " Fare Cabin Embarked Survived \n", - "0 2421.785941 h Q False \n", - "1 2503.982129 2 S False \n", - "2 4833.935352 c C False \n", - "3 399.928019 p Q False \n", - "4 6397.051061 G C True " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset(\n", - " nb_rows = NB_ROWS, \n", - " seed = SEED\n", - ")\n", - "\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "98e6fda2-dde7-4f8b-a787-c9a1e3571ebe", - "metadata": {}, - "source": [ - "### Query on dummy dataset" - ] - }, - { - "cell_type": "markdown", - "id": "243c73e3-daec-45d6-a3c8-ae1d60439ec4", - "metadata": {}, - "source": [ - "#### Average and number of rows with smartnoise-sql library on remote dummy" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3946425d", - "metadata": {}, - "outputs": [], - "source": [ - "# Average Age\n", - "QUERY = \"SELECT COUNT(*) AS nb_passengers, \\\n", - " AVG(Age) AS avg_age \\\n", - " FROM df\"" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "90cf2a6d", - "metadata": {}, - "outputs": [], - "source": [ - "# On the remote server dummy dataframe\n", - "dummy_res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0, # make sure to select high values of epsilon and delta to have small differences\n", - " delta = 2.0, # make sure to select high values of epsilon and delta to have small differences\n", - " dummy = True, \n", - " nb_rows = NB_ROWS,\n", - " seed = SEED\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "a30f277e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average age in remote dummy: 49.01 years old.\n", - "Number of rows in remote dummy: 200.\n" - ] - } - ], - "source": [ - "print(f\"Average age in remote dummy: {np.round(dummy_res['query_response']['avg_age'][0], 2)} years old.\")\n", - "print(f\"Number of rows in remote dummy: {np.round(dummy_res['query_response']['nb_passengers'][0], 2)}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "324454ed", - "metadata": {}, - "source": [ - "### Get current budget" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "61a467f3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'initial_epsilon': 45.0, 'initial_delta': 0.2}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_initial_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "afd22f84", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "05daf5a4", - "metadata": {}, - "source": [ - "It will also be useful to know what the remaining budget is. Therefore, we call the function `get_remaining_budget`. It just substarcts the total spent budget from the initial budget." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "6260cf54", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 45.0, 'remaining_delta': 0.2}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "20298e00", - "metadata": {}, - "source": [ - "As expected, for now the remaining budget is equal to the inital budget." - ] - }, - { - "cell_type": "markdown", - "id": "b746374c", - "metadata": {}, - "source": [ - "### Estimate cost of a query\n", - "Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The `estimate cost` function returns the estimated real cost of any query.\n", - "\n", - "Again, of course, this will not impact the user's budget." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "fd5ed08a", - "metadata": {}, - "outputs": [], - "source": [ - "EPSILON = 0.5\n", - "DELTA = 1e-4" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "133020c6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 1.5, 'delta_cost': 0.00014999500000001387}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "e5379edf", - "metadata": {}, - "source": [ - "### Query on real private dataset with smartnoise-sql." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "19e60263", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 45.0, 'remaining_delta': 0.2}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "69767fac", - "metadata": {}, - "outputs": [], - "source": [ - "response = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA,\n", - " dummy = False # Optionnal\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a7a4e2d3-2922-4f95-bdc9-a35c160f157c", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "6dbbdf93", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of passengers in real data: 891.\n", - "Average age in real data: 29.74.\n" - ] - } - ], - "source": [ - "nb_passengers = response['query_response']['nb_passengers'].iloc[0]\n", - "print(f\"Number of passengers in real data: {nb_passengers}.\")\n", - "\n", - "avg_age = np.round(response['query_response']['avg_age'].iloc[0], 2)\n", - "print(f\"Average age in real data: {avg_age}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b2767e65", - "metadata": {}, - "source": [ - "After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "39701fe5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 43.5, 'remaining_delta': 0.199850005}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "e37c587f", - "metadata": {}, - "source": [ - "As can be seen in `get_total_spent_budget()`, it is the budget estimated with `estimate_smartnoise_cost()` that was spent." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "487f835f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 1.5, 'total_spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "04929993", - "metadata": {}, - "source": [ - "## Step 4: Titanic statistics with opendp" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "b9685226", - "metadata": {}, - "outputs": [], - "source": [ - "import opendp as dp\n", - "import opendp.transformations as trans\n", - "import opendp.measurements as meas" - ] - }, - { - "cell_type": "markdown", - "id": "bbbca191", - "metadata": {}, - "source": [ - "### Confidence intervals for age over the whole population" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "4331d86f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'PassengerId': {'type': 'int', 'lower': 1},\n", - " 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3},\n", - " 'Name': {'type': 'string'},\n", - " 'Sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['male', 'female']},\n", - " 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0},\n", - " 'SibSp': {'type': 'int', 'lower': 0},\n", - " 'Parch': {'type': 'int', 'lower': 0},\n", - " 'Ticket': {'type': 'string'},\n", - " 'Fare': {'type': 'float', 'lower': 0.0},\n", - " 'Cabin': {'type': 'string'},\n", - " 'Embarked': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['C', 'Q', 'S']},\n", - " 'Survived': {'type': 'boolean'}}}" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "titanic_metadata" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "ff8cb7b6", - "metadata": {}, - "outputs": [], - "source": [ - "columns = [\"PassengerId\", \"Pclass\", \"Name\", \"Sex\", \"Age\", \"SibSp\", \"Parch\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "70b2bdb1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(0.1, 100.0)" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "age_min = titanic_metadata['columns']['Age']['lower']\n", - "age_max = titanic_metadata['columns']['Age']['upper']\n", - "age_min, age_max" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "75e4933b", - "metadata": {}, - "outputs": [], - "source": [ - "age_transformation_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"Age\", TOA=str) >>\n", - " trans.then_cast_default(TOA=float) >>\n", - " trans.then_clamp(bounds=(age_min, age_max)) >>\n", - " trans.then_resize(size=nb_passengers.tolist(), constant=avg_age) >>\n", - " trans.then_variance()\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "8041a647", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Server error status 400: {\"InvalidQueryException\":\"The pipeline provided is not a measurement. It cannot be processed in this server.\"}\n" - ] - } - ], - "source": [ - "# Expect to fail !!!\n", - "client.opendp_query(\n", - " opendp_pipeline = age_transformation_pipeline,\n", - " dummy=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d06c59dc", - "metadata": {}, - "source": [ - "This is because the server will only allow measurement pipeline with differentially private results. We add Laplacian noise to the pipeline and should be able to instantiate the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "b8162859", - "metadata": {}, - "outputs": [], - "source": [ - "var_age_transformation_pipeline = (\n", - " age_transformation_pipeline >>\n", - " meas.then_laplace(scale=5.0)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "fc7e0ecd", - "metadata": {}, - "source": [ - "Now that there is a measurement, one is able to apply the pipeline on the dummy dataset of the server." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "df61bce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for variance: 142.32\n" - ] - } - ], - "source": [ - "dummy_var_res = client.opendp_query(\n", - " opendp_pipeline = var_age_transformation_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for variance: {np.round(dummy_var_res['query_response'], 2)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "ded11ac4", - "metadata": {}, - "source": [ - "With opendp, the function `estimate_opendp_cost` is particularly useful to estimate the used `epsilon` and `delta` based on the `scale` value." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "7ae7f735", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 2.240181818190626, 'delta_cost': 0}" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = var_age_transformation_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1c791d36", - "metadata": {}, - "source": [ - "One can now execute the query on the real dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "085555a5", - "metadata": {}, - "outputs": [], - "source": [ - "var_res = client.opendp_query(\n", - " opendp_pipeline = var_age_transformation_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "674332e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of passengers: 891 (from previous smartnoise-sql query).\n", - "Average age: 29.74 (from previous smartnoise-sql query).\n", - "Variance of age: 0.393 (from opendp query).\n" - ] - } - ], - "source": [ - "print(f\"Number of passengers: {nb_passengers} (from previous smartnoise-sql query).\")\n", - "\n", - "print(f\"Average age: {np.round(avg_age, 2)} (from previous smartnoise-sql query).\")\n", - "\n", - "var_age = var_res['query_response']\n", - "print(f\"Variance of age: {np.round(var_age, 3)} (from opendp query).\")" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "f72b19d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Standard error of age: 0.02.\n" - ] - } - ], - "source": [ - "# Get standard error\n", - "standard_error = np.sqrt(var_age/nb_passengers)\n", - "print(f\"Standard error of age: {np.round(standard_error, 2)}.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "62630a03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The 95% confidence interval of the age of all passengers is [29.7, 29.78].\n" - ] - } - ], - "source": [ - " # Compute the 95% confidence interval\n", - "ZSCORE = 1.96\n", - "lower_bound = np.round(avg_age - ZSCORE*standard_error, 2)\n", - "upper_bound = np.round(avg_age + ZSCORE*standard_error, 2)\n", - "print(f\"The 95% confidence interval of the age of all passengers is [{lower_bound}, {upper_bound}].\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.0rc1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/notebooks/smartnoise_client_notebook.ipynb.txt b/html/de/_sources/notebooks/smartnoise_client_notebook.ipynb.txt deleted file mode 100644 index e317a8a0..00000000 --- a/html/de/_sources/notebooks/smartnoise_client_notebook.ipynb.txt +++ /dev/null @@ -1,1730 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3f18d338", - "metadata": {}, - "source": [ - "# Secure Data Disclosure: Client side" - ] - }, - { - "cell_type": "markdown", - "id": "1582a2ae", - "metadata": {}, - "source": [ - "This notebook showcases how researcher could use the Secure Data Disclosure system. It explains the different functionnalities provided by the dpserial client library to interact with the secure server.\n", - "\n", - "The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.\n", - "\n", - "Each user has access to one or multiple projects and for each dataset has a limited budget $\\epsilon$, $\\delta$." - ] - }, - { - "cell_type": "markdown", - "id": "5b73135c", - "metadata": {}, - "source": [ - "🐧🐧🐧\n", - "In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.\n", - "\n", - "Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.\n", - "\n", - "This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library ofs_dpserial. \n", - "🐧🐧🐧" - ] - }, - { - "cell_type": "markdown", - "id": "01ae30d2", - "metadata": {}, - "source": [ - "## Step 1: Install the library\n", - "To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library `fso_dpserial` on her local developping environment. \n", - "\n", - "It can be installed via the pip command:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "9d96dcd7", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client.client import Client\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "9c63718b", - "metadata": {}, - "source": [ - "## Step 2: Initialise the client\n", - "\n", - "Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server. \n", - "\n", - "To create the client, Dr. Antartica needs to give it a few parameters:\n", - "- a url: the root application endpoint to the remote secure server.\n", - "- user_name: her name as registered in the database (Dr. Alice Antartica)\n", - "- dataset_name: the name of the dataset that she wants to query (PENGUIN)\n", - "\n", - "She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit. (As is done in the Secure Data Disclosure Notebook: Server side)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "941991f7", - "metadata": {}, - "outputs": [], - "source": [ - "APP_URL = \"http://lomas_server_dev:80\"\n", - "USER_NAME = \"Dr. Antartica\"\n", - "DATASET_NAME = \"PENGUIN\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "markdown", - "id": "0ec400c8", - "metadata": {}, - "source": [ - "And that's it for the preparation. She is now ready to use the various functionnalities offered by `fso_dpserial`." - ] - }, - { - "cell_type": "markdown", - "id": "9b9a5f13", - "metadata": {}, - "source": [ - "## Step 3: Understand the functionnalities of the library" - ] - }, - { - "cell_type": "markdown", - "id": "c7cb5531", - "metadata": {}, - "source": [ - "### Getting dataset metadata\n", - "\n", - "Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the `get_dataset_metadata()` function of the client. As this is public information, this does not cost any budget.\n", - "\n", - "This function returns metadata information in the same format as [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details)." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "d15cbe39", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "metadata = client.get_dataset_metadata()\n", - "metadata" - ] - }, - { - "cell_type": "markdown", - "id": "d338ed96", - "metadata": {}, - "source": [ - "Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds. She also knows based on the field `max_ids: 1` that each penguin can only be once in the dataset and on the field `row_privacy: True` that each row represents a single penguin. " - ] - }, - { - "cell_type": "markdown", - "id": "5a3c899d", - "metadata": {}, - "source": [ - "### Get a dummy dataset\n", - "\n", - "Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset. \n", - "\n", - "Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets.\n", - "Getting a dummy dataset does not affect the budget as there is no differential privacy here, it is not a synthetic dataset and all that could be learn here is already present in the public metadata.\n", - "\n", - "Dr. Antartica first create a dummy dataset with the default options." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "be07091f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(100, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0ChinstrapTorgersen43.10890413.314292214.2031652258.408606FEMALE
1AdelieDream63.27500119.364104158.4139964656.773158FEMALE
2AdelieDream55.61978816.143560166.1628714703.175608FEMALE
3AdelieBiscoe50.95304718.085707239.8554195187.149507MALE
4GentooTorgersen35.46065222.075665210.6429065630.456669MALE
\n", - "
" - ], - "text/plain": [ - " species island bill_length_mm bill_depth_mm flipper_length_mm \n", - "0 Chinstrap Torgersen 43.108904 13.314292 214.203165 \\\n", - "1 Adelie Dream 63.275001 19.364104 158.413996 \n", - "2 Adelie Dream 55.619788 16.143560 166.162871 \n", - "3 Adelie Biscoe 50.953047 18.085707 239.855419 \n", - "4 Gentoo Torgersen 35.460652 22.075665 210.642906 \n", - "\n", - " body_mass_g sex \n", - "0 2258.408606 FEMALE \n", - "1 4656.773158 FEMALE \n", - "2 4703.175608 FEMALE \n", - "3 5187.149507 MALE \n", - "4 5630.456669 MALE " - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset()\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "4f85e950", - "metadata": {}, - "source": [ - "However, she would prefer to have a dataset with 200 rows and chooses a seed of 0, hence:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "01f4365a", - "metadata": {}, - "outputs": [], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3f553b29", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0GentooBiscoe49.20847316.117959190.1259502873.291927FEMALE
1GentooTorgersen55.03162819.963435242.9291423639.940005FEMALE
2ChinstrapTorgersen51.09671816.777518159.9614935401.743330MALE
3AdelieBiscoe49.07091114.796037244.5301532316.038092MALE
4ChinstrapBiscoe44.82791813.246787236.9488535036.246870FEMALE
\n", - "
" - ], - "text/plain": [ - " species island bill_length_mm bill_depth_mm flipper_length_mm \n", - "0 Gentoo Biscoe 49.208473 16.117959 190.125950 \\\n", - "1 Gentoo Torgersen 55.031628 19.963435 242.929142 \n", - "2 Chinstrap Torgersen 51.096718 16.777518 159.961493 \n", - "3 Adelie Biscoe 49.070911 14.796037 244.530153 \n", - "4 Chinstrap Biscoe 44.827918 13.246787 236.948853 \n", - "\n", - " body_mass_g sex \n", - "0 2873.291927 FEMALE \n", - "1 3639.940005 FEMALE \n", - "2 5401.743330 MALE \n", - "3 2316.038092 MALE \n", - "4 5036.246870 FEMALE " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset(nb_rows = NB_ROWS, seed = SEED)\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "69dac96e", - "metadata": {}, - "source": [ - "### Query dummy dataset\n", - "\n", - "Now that she has an idea of what the data looks like, she wants to start querying the real dataset to for her research. However, before this other tools are at her disposal to reduce potential error risks and avoid spending budget on irrelevant queries. Of course, this does not have any impact on the budget.\n", - "\n", - "It is possible to specify the flag `dummy=True` in the various queries to perform the query on the dummy dataset instead of the real dataset and ensure that the queries are doing what is expected of them. \n", - "\n", - "Therefore Dr. Antartica computes the results that she gets on the dummy dataframe that she created locally and on the same dummy dataframe in the server via a query and compare them to ensure that the query is well defined and works within the server.\n", - "\n", - "She tests with an example on the average bill length on the dataframe." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "b6caee55", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "47.51532" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# On the local dummy dataframe\n", - "result_local_dummy = round(df_dummy['bill_length_mm'].mean(), 5)\n", - "result_local_dummy" - ] - }, - { - "cell_type": "markdown", - "id": "c3a37d8d", - "metadata": {}, - "source": [ - "As the query on the server goes through the same workflow for dummies and real data, she still has to set values for theoratical budget to spend on the dummy query. Of course, this theoretical budget will NOT affect her real budget as this is on dummy data. \n", - "\n", - "It is recommended to use very high values on the budget parameters here to have little noise and small difference between the exact local result and the 'little noisy' server result. \n", - "\n", - "Also, make sure to use the same values of number of rows and seed to have the same dummy datasets." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3946425d", - "metadata": {}, - "outputs": [], - "source": [ - "# Average bill length in mm\n", - "QUERY = \"SELECT AVG(bill_length_mm) AS avg_bill_length_mm FROM df\"" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "90cf2a6d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "47.50783673123655" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# On the remote server dummy dataframe\n", - "res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0, # make sure to select high values of epsilon and delta to have small differences\n", - " delta = 2.0, # make sure to select high values of epsilon and delta to have small differences\n", - " dummy = True, \n", - " nb_rows = NB_ROWS,\n", - " seed = SEED\n", - ")\n", - "res_server_dummy = res['query_response'][\"avg_bill_length_mm\"][0]\n", - "res_server_dummy" - ] - }, - { - "cell_type": "markdown", - "id": "bb3fa8eb", - "metadata": {}, - "source": [ - "She then checks that the responses on the dummy locally and the dummy on the server are close enough (difference would be only due to small noise addition)." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "0f2fff82", - "metadata": {}, - "outputs": [], - "source": [ - "np.testing.assert_almost_equal(\n", - " result_local_dummy, \n", - " res_server_dummy,\n", - " decimal=2, \n", - " err_msg=\"Responses are different, either try with a bigger budget or query is not doing what is intended.\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "5a82abcd", - "metadata": {}, - "source": [ - "As you can see res_local and res_server are close. We can accept that the small difference is due to the small noise added due to the large values of $\\epsilon$ and $\\delta$." - ] - }, - { - "cell_type": "markdown", - "id": "324454ed", - "metadata": {}, - "source": [ - "### Get current budget\n", - "\n", - "It is the first time that Dr. Antartica connects to the server and she wants to know how much buget the queen assigned her.\n", - "Therefore, she calls the fonction `get_initial_budget`." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "61a467f3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'initial_epsilon': 10, 'initial_delta': 0.005}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_initial_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "bc8f7a74", - "metadata": {}, - "source": [ - "She sees that she has 10.0 epsilon and 0.0004 epsilon at her disposal.\n", - "\n", - "Then she checks her total spent budget `get_total_spent_budget`. As she only did queries on metadata on dummy dataframes, this should still be 0." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "afd22f84", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0, 'total_spent_delta': 0}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "05daf5a4", - "metadata": {}, - "source": [ - "It will also be useful to know what the remaining budget is. Therefore, she calls the function `get_remaining_budget`. It just substarcts the total spent budget from the initial budget." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "6260cf54", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "20298e00", - "metadata": {}, - "source": [ - "As expected, for now the remaining budget is equal to the inital budget." - ] - }, - { - "cell_type": "markdown", - "id": "b746374c", - "metadata": {}, - "source": [ - "### Estimate cost of a query\n", - "Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The `estimate cost` function returns the estimated real cost of any query.\n", - "\n", - "Again, of course, this will not impact the user's budget.\n", - "\n", - "Dr. Antartica checks the budget that computing the average bill length will really cost her if she asks the query with an `epsilon` and a `delta`." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "133020c6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 2.0, 'delta_cost': 4.999999999999449e-05}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = 1.0, \n", - " delta = 1e-4\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "71580822", - "metadata": {}, - "source": [ - "So this query would actually cost her 3.0 epsilon and a little 1.499e-4 delta. As she does not want to spend to much budget here she tries other values of budget." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "df487c62", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 0.4, 'delta_cost': 5.000000000032756e-06}" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = 0.2, \n", - " delta = 1e-5\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "3c6a3a8c", - "metadata": {}, - "source": [ - "This query would actually cost her 0.6 epsilon and a similar delta. She decides that it is good enough." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "c9c8d3e7", - "metadata": {}, - "outputs": [], - "source": [ - "EPSILON = 0.2\n", - "DELTA = 1e-5" - ] - }, - { - "cell_type": "markdown", - "id": "e5379edf", - "metadata": {}, - "source": [ - "### Query real dataset\n", - "Now that all the safeguard functions were tested, Dr. Antartica is ready to query on the real dataset and get a differentially private response of the average bill length. By default, the flag `dummy` is False so setting it is optional. She uses the values of `epsilon` and `delta` that she selected just before.\n", - "\n", - "Careful: This command DOES spend the budget of the user and the remaining budget is updated for every query." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "19e60263", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "69767fac", - "metadata": {}, - "outputs": [], - "source": [ - "avg_bill_length_response = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA,\n", - " dummy = False\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "6dbbdf93", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average bill length: 44.34mm.\n" - ] - } - ], - "source": [ - "avg_bill_length = avg_bill_length_response['query_response']['avg_bill_length_mm'].iloc[0]\n", - "print(f\"Average bill length: {np.round(avg_bill_length, 2)}mm.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b2767e65", - "metadata": {}, - "source": [ - "After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "39701fe5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 9.6, 'remaining_delta': 0.004994999999999967}" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "e37c587f", - "metadata": {}, - "source": [ - "As can be seen in `get_total_spent_budget()`, it is the budget estimated with `estimate_cost()` that was spent." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "487f835f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0.4, 'total_spent_delta': 5.000000000032756e-06}" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "eef4afcd", - "metadata": {}, - "source": [ - "Dr. Antartica has now a differentially private estimation of the bill length of all birds and is confident to use the library for the rest of her analyses." - ] - }, - { - "cell_type": "markdown", - "id": "04929993", - "metadata": {}, - "source": [ - "## Step 4: Penguin statistics" - ] - }, - { - "cell_type": "markdown", - "id": "bbbca191", - "metadata": {}, - "source": [ - "### Confidence intervals for flipper length over the whole population" - ] - }, - { - "cell_type": "markdown", - "id": "9d41bd58", - "metadata": {}, - "source": [ - "She is first interested to have a better idea of the distribution of flipper length of all species. She already has the mean from step 3, so she just need to compute the standard deviation and know the number of penguins in the dataset. As it is just an exploration step, she uses very little budget values." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "04b376ef", - "metadata": {}, - "outputs": [], - "source": [ - "QUERY = \"SELECT COUNT(bill_length_mm) AS nb_penguin, STD(bill_length_mm) AS std_bill_length_mm FROM df\"" - ] - }, - { - "cell_type": "markdown", - "id": "0b041c81", - "metadata": {}, - "source": [ - "She again first verifies that her query works on the dummy dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "5aa9c304", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nb_penguinstd_bill_length_mm
09910.291426
\n", - "
" - ], - "text/plain": [ - " nb_penguin std_bill_length_mm\n", - "0 99 10.291426" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dummy_res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0, \n", - " delta = 10.0, \n", - " dummy = True\n", - ")\n", - "dummy_res['query_response']" - ] - }, - { - "cell_type": "markdown", - "id": "74f68994", - "metadata": {}, - "source": [ - "The syntax of the query works, now she checks the budget:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "a8fa2c49", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 1.5, 'delta_cost': 5.000000000032756e-06}" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = 0.5, \n", - " delta = 1e-5\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "bed840d3", - "metadata": {}, - "source": [ - "It is a bit too much, she decides to test for less:" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "edc97e73", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 0.75, 'delta_cost': 5.000000000032756e-06}" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = 0.25, \n", - " delta = 1e-5\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "da9f81c4", - "metadata": {}, - "source": [ - "That's fine, she is ready to query:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "534979fb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nb_penguinstd_bill_length_mm
03379.193759
\n", - "
" - ], - "text/plain": [ - " nb_penguin std_bill_length_mm\n", - "0 337 9.193759" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "response = client.smartnoise_query(query = QUERY, epsilon = 0.25, delta = 1e-5)\n", - "response = response['query_response']\n", - "response" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "674332e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins: 337.\n", - "Standard deviation of bill length: 9.19.\n" - ] - } - ], - "source": [ - "nb_penguin = response['nb_penguin'].iloc[0]\n", - "print(f\"Number of penguins: {nb_penguin}.\")\n", - "\n", - "std_bill_length = response['std_bill_length_mm'].iloc[0]\n", - "print(f\"Standard deviation of bill length: {np.round(std_bill_length, 2)}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "367081be-1159-45d8-9129-88fba20fb697", - "metadata": {}, - "source": [ - "She can now do all the postprocessing that she wants with the returned data without adding any privacy risk. " - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "f72b19d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Standard error of bill length: 0.5.\n" - ] - } - ], - "source": [ - "# Get standard error\n", - "standard_error = std_bill_length/np.sqrt(nb_penguin)\n", - "print(f\"Standard error of bill length: {np.round(standard_error, 2)}.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "62630a03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The 95% confidence interval of the bill length of all penguins is [43.36, 45.32].\n" - ] - } - ], - "source": [ - " # Compute the 95% confidence interval\n", - "ZSCORE = 1.96\n", - "lower_bound, upper_bound = avg_bill_length - ZSCORE*standard_error, avg_bill_length + ZSCORE*standard_error\n", - "print(f\"The 95% confidence interval of the bill length of all penguins is [{np.round(lower_bound, 2)}, {np.round(upper_bound, 2)}].\")" - ] - }, - { - "cell_type": "markdown", - "id": "b5ee7ad2", - "metadata": {}, - "source": [ - "### Hypothesis testing" - ] - }, - { - "cell_type": "markdown", - "id": "0f1a3a8d", - "metadata": {}, - "source": [ - "So, Dr. Antartica has now the opportunity to study the various penguins dimensions and will do an hypotheses testing analysis to discover if flipper length differ between the penguins species.\n", - "\n", - "She will do a two-tailed two-sample $t$ test.\n", - "\n", - "- The null hypothese $H_0$ is flipper length does not differ between species.\n", - "- The alternative hypothesis $H_a$ is flipper length does differ between species.\n", - "\n", - "She set the level of significance at 0.05." - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "7d9ae766-4c0d-4dc5-9c9a-5f7eb99718f9", - "metadata": {}, - "outputs": [], - "source": [ - "CRITICAL_VALUE = 0.05" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "5006201d", - "metadata": {}, - "outputs": [], - "source": [ - "QUERY = \"SELECT \\\n", - " species AS species, \\\n", - " COUNT(bill_length_mm) AS nb_penguin, \\\n", - " AVG(bill_length_mm) AS avg_bill_length_mm, \\\n", - " STD(bill_length_mm) AS std_bill_length_mm \\\n", - " FROM df GROUP BY species\"" - ] - }, - { - "cell_type": "markdown", - "id": "e725eb3f-d12f-4f62-8f57-06fb00639f91", - "metadata": {}, - "source": [ - "She estimates how much budget it would really cost:" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "0255550b-7fd2-4244-a8eb-da809ddc6a5b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 3.0, 'delta_cost': 4.999999999999449e-05}" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(query = QUERY, epsilon = 1, delta = 1e-4)" - ] - }, - { - "cell_type": "markdown", - "id": "56bf804f-d877-48cb-b405-709b30cda3d1", - "metadata": {}, - "source": [ - "The real cost seems to be 3 times the epsilon that she sets. It is a lot but she tries on the dummy dataset to verify all is working properly." - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "80d9933b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'query_response': species nb_penguin avg_bill_length_mm std_bill_length_mm\n", - " 0 Adelie 36 48.807157 8.785415\n", - " 1 Chinstrap 32 46.193166 18.700943\n", - " 2 Gentoo 30 41.849626 6.128719}" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dummy_res = client.smartnoise_query(query = QUERY, epsilon = 1, delta = 1.0, dummy = True)\n", - "dummy_res" - ] - }, - { - "cell_type": "markdown", - "id": "5691680f-8716-4a99-999a-a2bd5ef6a679", - "metadata": {}, - "source": [ - "She did not give enough budget for the query to work. This is why there are 'NANs' in the output. She has to spend more budget for the query to work." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "6b014db4-acbd-4ae1-a3b6-397035851583", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 8.85, 'remaining_delta': 0.004989999999999935}" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "43d3488d-3987-4fec-a840-78385e956832", - "metadata": {}, - "source": [ - "The maximum she can do with all her remaining budget of 7.4 is around 7.4/4 = 1.85. Let's check:" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "99d7998d-daa1-4d5e-aa42-abc5aabdf2e3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 9.0, 'delta_cost': 4.999999999999449e-05}" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(query = QUERY, epsilon = 12/4, delta = 1e-4)" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "0e07fde9-9430-4a12-8337-0503ac162c26", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'query_response': species nb_penguin avg_bill_length_mm std_bill_length_mm\n", - " 0 Adelie 37 48.580124 2.255215\n", - " 1 Chinstrap 33 46.983725 9.051034\n", - " 2 Gentoo 28 43.886555 10.928323}" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dummy_res = client.smartnoise_query(query = QUERY, epsilon = 12/4, delta = 1e-4, dummy = True)\n", - "dummy_res" - ] - }, - { - "cell_type": "markdown", - "id": "e8dadd17", - "metadata": {}, - "source": [ - "If it errors, she might need to re-run the query a few times until it works. The budget is not affected by dummy queries anyway." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "59f2d665", - "metadata": {}, - "outputs": [], - "source": [ - "flipper_length_response = client.smartnoise_query(query = QUERY, epsilon = 12/4, delta = 1e-4)" - ] - }, - { - "cell_type": "markdown", - "id": "78d23e66-2c05-4e35-bba9-7b92710b872a", - "metadata": {}, - "source": [ - "And now she should not have any remaining budget:" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "6eb20cfb-fa53-496f-940d-9b17b05fa074", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 1.0, 'remaining_delta': 0.004950000000000006}" - ] - }, - "execution_count": 48, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "cb96f406-d409-4531-ac86-05f1c9296705", - "metadata": {}, - "source": [ - "But she can do her post-processing and hypothesis analysis." - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "748f125f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesnb_penguinavg_bill_length_mmstd_bill_length_mm
0Adelie15038.9029430.805783
1Chinstrap6749.2625606.123036
2Gentoo12247.4695632.851064
\n", - "
" - ], - "text/plain": [ - " species nb_penguin avg_bill_length_mm std_bill_length_mm\n", - "0 Adelie 150 38.902943 0.805783\n", - "1 Chinstrap 67 49.262560 6.123036\n", - "2 Gentoo 122 47.469563 2.851064" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_flipper = flipper_length_response['query_response']\n", - "df_flipper" - ] - }, - { - "cell_type": "markdown", - "id": "099129cf", - "metadata": {}, - "source": [ - "And finally the $t$-test:" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "0a7d7d4d", - "metadata": {}, - "outputs": [], - "source": [ - "def t_test(avg_1, avg_2, std_1, std_2, nb_1, nb_2):\n", - " standard_error = (std_1 * (nb_1 - 1) + std_2 * (nb_2 - 1))/(nb_1 + nb_2 - 2)\n", - " return (avg_1 - avg_2)/np.sqrt(standard_error**2*(1/nb_1 + 1 /nb_2))" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "bc3ee48a", - "metadata": {}, - "outputs": [], - "source": [ - "nb_0, avg_0, std_0 = df_flipper[['nb_penguin', 'avg_bill_length_mm', 'std_bill_length_mm']].iloc[0]\n", - "nb_1, avg_1, std_1 = df_flipper[['nb_penguin', 'avg_bill_length_mm', 'std_bill_length_mm']].iloc[1]\n", - "nb_2, avg_2, std_2 = df_flipper[['nb_penguin', 'avg_bill_length_mm', 'std_bill_length_mm']].iloc[2]" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "id": "1717f9ea", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "T test between specie 0 and specie 1: -28.92. Reject null hypothesis: True.\n", - "T test between specie 0 and specie 2: -40.8. Reject null hypothesis: True.\n", - "T test between specie 1 and specie 2: 2.94. Reject null hypothesis: True.\n" - ] - } - ], - "source": [ - "t_01 = t_test(avg_0, avg_1, std_0, std_1, nb_0, nb_1)\n", - "t_02 = t_test(avg_0, avg_2, std_0, std_2, nb_0, nb_2)\n", - "t_12 = t_test(avg_1, avg_2, std_1, std_2, nb_1, nb_2)\n", - "\n", - "print(f\"T test between specie 0 and specie 1: {np.round(t_01, 2)}. Reject null hypothesis: {abs(t_01) > CRITICAL_VALUE}.\")\n", - "print(f\"T test between specie 0 and specie 2: {np.round(t_02, 2)}. Reject null hypothesis: {abs(t_02) > CRITICAL_VALUE}.\")\n", - "print(f\"T test between specie 1 and specie 2: {np.round(t_12, 2)}. Reject null hypothesis: {abs(t_12) > CRITICAL_VALUE}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "a557b754-ae7a-490a-8c4b-3928de7101d1", - "metadata": {}, - "source": [ - "All t-tests are above the critical value of 0.5. She can reject the null hypothesis." - ] - }, - { - "cell_type": "markdown", - "id": "b4df475b", - "metadata": {}, - "source": [ - "She finally computes the confidence intervals for the flipper length of each species" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "id": "9289bc26", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesnb_penguinavg_bill_length_mmstd_bill_length_mmstandard_errorci_95_lower_boundci_95_upper_bound
0Adelie15038.9029430.8057830.06579238.77399139.031895
1Chinstrap6749.2625606.1230360.74804847.79638650.728733
2Gentoo12247.4695632.8510640.25812346.96364247.975485
\n", - "
" - ], - "text/plain": [ - " species nb_penguin avg_bill_length_mm std_bill_length_mm \n", - "0 Adelie 150 38.902943 0.805783 \\\n", - "1 Chinstrap 67 49.262560 6.123036 \n", - "2 Gentoo 122 47.469563 2.851064 \n", - "\n", - " standard_error ci_95_lower_bound ci_95_upper_bound \n", - "0 0.065792 38.773991 39.031895 \n", - "1 0.748048 47.796386 50.728733 \n", - "2 0.258123 46.963642 47.975485 " - ] - }, - "execution_count": 53, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ZSCORE = 1.96\n", - "df_flipper['standard_error'] = df_flipper['std_bill_length_mm']/np.sqrt(df_flipper['nb_penguin'])\n", - "df_flipper['ci_95_lower_bound'] = df_flipper['avg_bill_length_mm'] - ZSCORE * df_flipper['standard_error']\n", - "df_flipper['ci_95_upper_bound'] = df_flipper['avg_bill_length_mm'] + ZSCORE * df_flipper['standard_error']\n", - "df_flipper" - ] - }, - { - "cell_type": "markdown", - "id": "f79e8333-1f06-4019-af3c-94ff2362d036", - "metadata": {}, - "source": [ - "She can now go and present her findings to queen Icebergina." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cce8dde4-0c9e-43d4-b6ce-890ac7c8efd2", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/_sources/server_administration.rst.txt b/html/de/_sources/server_administration.rst.txt deleted file mode 100644 index 2f180dee..00000000 --- a/html/de/_sources/server_administration.rst.txt +++ /dev/null @@ -1,20 +0,0 @@ -Administration -============== - -This section explains how the data owner can manage the administrative database. -It covers some tasks such as adding data, making data available to certain users, -managing the budget, etc. - - -To familiarize yourself with this tool, we provide multiple notebooks demonstrating various -CLI commands that users can use. These notebooks can be found in our -`GitHub repository `_. - -In addition to the notebooks, we also provide a Streamlit application that facilitates these -administrative tasks. - -.. toctree:: - :maxdepth: 2 - - server_cli - server_streamlit \ No newline at end of file diff --git a/html/de/_sources/server_api.rst.txt b/html/de/_sources/server_api.rst.txt deleted file mode 100644 index d594f3c0..00000000 --- a/html/de/_sources/server_api.rst.txt +++ /dev/null @@ -1,8 +0,0 @@ -Server API -================== - -.. toctree:: - :maxdepth: 2 - :glob: - - server_modules diff --git a/html/de/_sources/server_cli.rst.txt b/html/de/_sources/server_cli.rst.txt deleted file mode 100644 index dbb5607f..00000000 --- a/html/de/_sources/server_cli.rst.txt +++ /dev/null @@ -1,205 +0,0 @@ -CLI -================== - -This section provides detailed instructions on how to use the CLI to perform various -administrative tasks efficiently. - - -Overview ----------------- - -The CLI allows data owners to interact with the administrative database directly from the command -line. It provides functionalities for managing users, datasets, metatada and queries_archive collections within the MongoDB environment. - -NOTE: it is possible to use a :doc:`streamlit app ` to interact with the database instead. - -MongoDB Connection ------------------- -The CLI requires connection parameters to establish a connection with MongoDB. These parameters must always for provided (for all functions): - -- ``-db_u, --username``: MongoDB username (default: "user") -- ``-db_pwd, --password``: MongoDB password (default: "user_pwd") -- ``-db_a, --address``: MongoDB server address (default: "mongodb") -- ``-db_p, --port``: MongoDB server port (default: 27017) -- ``-db_n, --db_name``: MongoDB database name (default: "defaultdb") - -MongoDB Administration ----------------------- - -Users -~~~~~ -- ``add_user``: Add a user to the users collection. - - - ``-u, --user``: Username of the user to be added (required). - -- ``add_user_with_budget``: Add a user with a budget to the users collection. - - - ``-u, --user``: Username of the user to be added (required). - - ``-d, --dataset``: Name of the dataset for the user (required). - - ``-e, --epsilon``: Epsilon value for the dataset (required). - - ``-del, --delta``: Delta value for the dataset (required). - -- ``del_user``: Delete a user from the users collection. - - - ``-u, --user``: Username of the user to be deleted (required). - -- ``add_dataset_to_user``: Add a dataset with initialized budget values for a user. - - - ``-u, --user``: Username of the user (required). - - ``-d, --dataset``: Name of the dataset to be added (required). - - ``-e, --epsilon``: Epsilon value for the dataset (required). - - ``-del, --delta``: Delta value for the dataset (required). - -- ``del_dataset_to_user``: Delete a dataset for a user from the users collection. - - - ``-u, --user``: Username of the user (required). - - ``-d, --dataset``: Name of the dataset to be deleted (required). - -- ``set_budget_field``: Set a budget field to a given value for a specified user and dataset. - - - ``-u, --user``: Username of the user (required). - - ``-d, --dataset``: Name of the dataset (required). - - ``-f, --field``: Field to be set ("initial_epsilon" or "initial_delta") (required). - - ``-v, --value``: Value to set for the field (required). - -- ``set_may_query``: Set the "may query" field to a given value for a specified user. - - - ``-u, --user``: Username of the user (required). - - ``-v, --value``: Value to set for "may query" (choices: "False" or "True") (required). - -- ``show_user``: Show all information about a user in the users collection. - - - ``-u, --user``: Username of the user to be shown (required). - -- ``add_users_via_yaml``: Create users collection from a YAML file. - - - ``-yf, --yaml_file``: Path to the YAML file (required). - - ``-c, --clean``: Clean the existing users collection (optional, default: False). - - ``-o, --overwrite``: Overwrite the existing users collection (optional, default: False). - -- ``show_archives``: Show all previous queries from a user. - - - ``-u, --user``: Username of the user to show archives (required). - -- ``get_users``: Get the list of all users in the 'users' collection. - -- ``get_user_datasets``: Get the list of all datasets from a user. - - - ``-u, --user``: Username of the user to show datasets (required). - -Datasets -~~~~~~~~ -- ``add_dataset``: Add a dataset to the datasets collection. - - - ``-d, --dataset_name``: Name of the dataset (required). - - ``-db, --database_type``: Type of the database where the dataset is stored (required). - - ``-d_path, --dataset_path``: Path to the dataset (required if database_type is 'PATH_DB'). - - ``-s3b, --s3_bucket``: S3 bucket name for the dataset file (required if database_type is 'S3_DB'). - - ``-s3k, --s3_key``: S3 key for the dataset file (required if database_type is 'S3_DB'). - - ``-s3_url, --endpoint_url``: S3 endpoint URL for the dataset file (required if database_type is 'S3_DB'). - - ``-s3_ak, --aws_access_key_id``: AWS access key ID for S3 for the dataset file (required if database_type is 'S3_DB'). - - ``-s3_sak, --aws_secret_access_key``: AWS secret access key for S3 for the dataset file (required if database_type is 'S3_DB'). - - ``-m_db, --metadata_database_type``: Type of the database where metadata is stored (required). - - ``-mp, --metadata_path``: Path to the metadata (required if metadata_database_type is 'PATH_DB'). - - ``-m_s3b, --metadata_s3_bucket``: S3 bucket name for metadata (required if metadata_database_type is 'S3_DB'). - - ``-m_s3k, --metadata_s3_key``: S3 key for metadata (required if metadata_database_type is 'S3_DB'). - - ``-m_s3_url, --metadata_endpoint_url``: S3 endpoint URL for metadata (required if metadata_database_type is 'S3_DB'). - - ``-m_s3_ak, --metadata_aws_access_key_id``: AWS access key ID for metadata (required if metadata_database_type is 'S3_DB'). - - ``-m_s3_sak, --metadata_aws_secret_access_key``: AWS secret access key for metadata (required if metadata_database_type is 'S3_DB'). - -- ``add_datasets_via_yaml``: Create datasets to database type collection based on a yaml file. - - - ``-yf, --yaml_file``: Path to the YAML file (required). - - ``-c, --clean``: Clean the existing datasets collection (optional, default: False). - - ``-od, --overwrite_datasets``: Overwrite the existing datasets collection (optional, default: False). - - ``-om, --overwrite_metadata``: Overwrite the existing metadata collection (optional, default: False). - -- ``del_dataset``: Delete dataset and metadata from datasets and metadata collection. - - - ``-d, --dataset``: Name of the dataset to be deleted (required). - -- ``show_dataset``: Show a dataset from the dataset collection. - - - ``-d, --dataset``: Name of the dataset to show (required). - -- ``show_metadata``: Show metadata from the metadata collection. - - - ``-d, --dataset``: Name of the dataset of the metadata to show (required). - -- ``get_datasets``: Get the list of all datasets in the 'datasets' collection. - -Collections -~~~~~~~~~~~ -- ``drop_collection``: Delete a collection from the database. - - - ``-c, --collection``: Name of the collection to be deleted. Choices: "users", "datasets", "metadata", "queries_archives" (required). - -- ``show_collection``: Print a collection. - - - ``-c, --collection``: Name of the collection to be shown. Choices: "users", "datasets", "metadata", "queries_archives" (required). - -Examples --------- -.. code-block:: console - - # Add a user - python mongodb_admin_cli.py add_user -u username - - # Add a user with budget - python mongodb_admin_cli.py add_user_with_budget -u username -d dataset_name -e 0.5 -del 0.1 - - # Delete a user - python mongodb_admin_cli.py del_user -u username - - # Add a dataset to a user - python mongodb_admin_cli.py add_dataset_to_user -u username -d dataset_name -e 0.5 -del 0.1 - - # Delete a dataset from a user - python mongodb_admin_cli.py del_dataset_to_user -u username -d dataset_name - - # Set budget field for a user and dataset - python mongodb_admin_cli.py set_budget_field -u username -d dataset_name -f initial_epsilon -v 0.5 - - # Set may query field for a user - python mongodb_admin_cli.py set_may_query -u username -v True - - # Show user metadata - python mongodb_admin_cli.py show_user -u username - - # Create users collection from a YAML file - python mongodb_admin_cli.py add_users_via_yaml -yf users.yaml -c - - # Show all previous queries from user "username" - python mongodb_admin_cli.py show_archives -u username - - # Get the list of all users - python mongodb_admin_cli.py get_users - - # Get the list of all datasets from user "username" - python mongodb_admin_cli.py get_user_datasets -u username - - # Add a dataset - python mongodb_admin_cli.py add_dataset -d dataset_name -db database_type -d_path dataset_path -m_db metadata_database_type - - # Create datasets from a YAML file - python mongodb_admin_cli.py add_datasets_via_yaml -yf datasets.yaml -c -od -om - - # Delete a dataset - python mongodb_admin_cli.py del_dataset -d dataset_name - - # Show dataset "dataset_name" - python mongodb_admin_cli.py show_dataset -d dataset_name - - # Show metadata for dataset "dataset_name" - python mongodb_admin_cli.py show_metadata -d dataset_name - - # Drop a collection - python mongodb_admin_cli.py drop_collection -c users - - # Show a collection - python mongodb_admin_cli.py show_collection -c datasets - -.. toctree:: - :maxdepth: 2 - - notebooks/local_admin_notebook \ No newline at end of file diff --git a/html/de/_sources/server_deployment.rst.txt b/html/de/_sources/server_deployment.rst.txt deleted file mode 100644 index 1305ec54..00000000 --- a/html/de/_sources/server_deployment.rst.txt +++ /dev/null @@ -1,23 +0,0 @@ -Deployment -============ - -This documentation provides guidance on deploying the Lomas server using various methods. -You can choose the deployment option that best suits your needs: - -- **Local Deployment**: - Learn how to deploy the server locally on your machine using Docker. - -- **Kubernetes Deployment**: - Explore instructions for deploying the server on a Kubernetes cluster using Helm. - -- **Onyxia Deployment**: - Discover how to deploy the server on Onyxia. - -Choose the deployment method that fits your infrastructure and deployment requirements. - -.. toctree:: - :maxdepth: 2 - - server_local - server_kubernetes - server_onyxia \ No newline at end of file diff --git a/html/de/_sources/server_kubernetes.rst.txt b/html/de/_sources/server_kubernetes.rst.txt deleted file mode 100644 index f8811967..00000000 --- a/html/de/_sources/server_kubernetes.rst.txt +++ /dev/null @@ -1,74 +0,0 @@ -Kubernetes -================== - -In this chapter, we will guide you through deploying the service on Kubernetes. -We provide a Helm chart to simplify this process. - -Prerequisites -------------- - -Before you begin, make sure you have the following: - -1. **Kubernetes Cluster**: A running Kubernetes cluster. - If you don't have one, you can set up a local cluster using Minikube - or Kind, or use a cloud provider like GKE, EKS, or AKS. -2. **Helm**: Helm installed on your local machine. - Follow the `official Helm installation guide `_ - if you haven't installed Helm yet. -3. **kubectl**: Kubernetes command-line tool ``kubectl`` - installed and configured to communicate with your cluster. - You can install ``kubectl`` by following the - `official Kubernetes installation guide - `_. - -Deploying the Service on Kubernetes ------------------------------------ - -To deploy the service on Kubernetes, follow the instructions below. - -Accessing the Helm Chart ------------------------- - -The Helm chart for deploying the service on Kubernetes is available here: - -.. code-block:: sh - helm repo add lomas https://dscc-admin-ch.github.io/helm-charts - -Modifying ``values.yaml`` -------------------------- - -Before installing the Helm chart, you need to adapt the ``values.yaml`` file to -fit your specific requirements, especially the ``ingress`` configuration. - -Modifying the ``ingress`` Section -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To change the ``ingress`` configuration, follow these steps: - -1. **Get the default values** - -.. code-block:: sh - - helm show values lomas/lomas-server > values.yaml - -2. **Edit values.yaml file** - -3. **Save the Changes** - -Installing the Helm Chart -------------------------- - -Once you have modified the ``values.yaml`` file, you can proceed -to install the Helm chart with your custom configurations: - -1. **Install the Helm Chart** - - Navigate to the directory containing the modified ``values.yaml`` - file and run the following command: - - .. code-block:: sh - - helm install lomas-sever lomas/lomas-server -f values.yaml - -By following these steps, you will have successfully configured and deployed the service -on Kubernetes using the provided Helm chart. diff --git a/html/de/_sources/server_local.rst.txt b/html/de/_sources/server_local.rst.txt deleted file mode 100644 index 0ee2f657..00000000 --- a/html/de/_sources/server_local.rst.txt +++ /dev/null @@ -1,55 +0,0 @@ -Local -================== - -This chapter provides instructions on how to deploy the server locally. -Follow these steps to get your server up and running on your local machine. - -Prerequisites -------------- - -- Ensure you have Docker and Docker Compose installed on your system. - -Steps to Deploy Locally ------------------------ - -1. Clone the Repository - - First, you need to clone the repository. Open your terminal and run: - - .. code-block:: bash - - git clone https://github.com/dscc-admin-ch/lomas.git - -2. Navigate to the Server Directory - - Move into the server directory where the Dockerfile is located: - - .. code-block:: bash - - cd server - -3. Create a Docker Volume for MongoDB - - You need to create a Docker volume for MongoDB data persistence. - Run the following command: - - .. code-block:: bash - - docker volume create mongodata - -4. Start the Server - - With Docker and Docker Compose set up, you can now start the server. - In the same directory, run: - - .. code-block:: bash - - docker compose up - -5. Access the Server - - Once the server is up and running, it should be accessible on `localhost`. Open your web browser and go to: - - .. code-block:: text - - http://localhost diff --git a/html/de/_sources/server_modules.rst.txt b/html/de/_sources/server_modules.rst.txt deleted file mode 100644 index 19193098..00000000 --- a/html/de/_sources/server_modules.rst.txt +++ /dev/null @@ -1,7 +0,0 @@ -lomas_server -============ - -.. toctree:: - :maxdepth: 4 - - lomas_server diff --git a/html/de/_sources/server_onyxia.rst.txt b/html/de/_sources/server_onyxia.rst.txt deleted file mode 100644 index baafcb25..00000000 --- a/html/de/_sources/server_onyxia.rst.txt +++ /dev/null @@ -1,28 +0,0 @@ -Onyxia -================== - -Introduction ------------- - -Onyxia is a datalab developped by INSEE (France) allowing to -easily deploy services on demand. - -Deploying Lomas on Onyxia -------------------------- - -To start the lomas-server on Onyxia, folow those steps: - - -1. **Select the LOMAS Service**: - Within the Onyxia platform, locate the Lomas-server service. - You can find the service following this - `link `_. - -2. **Customize Parameters (Optional)**: - Depending on your specific requirements, you may choose to customize the - administration and runtime parameters. This step allows for fine-tuning the - deployment according to your project's needs. - -3. **Initiate Deployment**: - Once satisfied with the parameter settings, click on the "Lancer" button to - initiate the deployment process. diff --git a/html/de/_sources/server_streamlit.rst.txt b/html/de/_sources/server_streamlit.rst.txt deleted file mode 100644 index c1685628..00000000 --- a/html/de/_sources/server_streamlit.rst.txt +++ /dev/null @@ -1,10 +0,0 @@ -Streamlit -================== - -Deployment ------------- -TODO - -Onyxia ------------- -TODO \ No newline at end of file diff --git a/html/de/_static/_sphinx_javascript_frameworks_compat.js b/html/de/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 81415803..00000000 --- a/html/de/_static/_sphinx_javascript_frameworks_compat.js +++ /dev/null @@ -1,123 +0,0 @@ -/* Compatability shim for jQuery and underscores.js. - * - * Copyright Sphinx contributors - * Released under the two clause BSD licence - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} diff --git a/html/de/_static/base-stemmer.js b/html/de/_static/base-stemmer.js deleted file mode 100644 index ca6cca15..00000000 --- a/html/de/_static/base-stemmer.js +++ /dev/null @@ -1,294 +0,0 @@ -/**@constructor*/ -BaseStemmer = function() { - this.setCurrent = function(value) { - this.current = value; - this.cursor = 0; - this.limit = this.current.length; - this.limit_backward = 0; - this.bra = this.cursor; - this.ket = this.limit; - }; - - this.getCurrent = function() { - return this.current; - }; - - this.copy_from = function(other) { - this.current = other.current; - this.cursor = other.cursor; - this.limit = other.limit; - this.limit_backward = other.limit_backward; - this.bra = other.bra; - this.ket = other.ket; - }; - - this.in_grouping = function(s, min, max) { - if (this.cursor >= this.limit) return false; - var ch = this.current.charCodeAt(this.cursor); - if (ch > max || ch < min) return false; - ch -= min; - if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) return false; - this.cursor++; - return true; - }; - - this.in_grouping_b = function(s, min, max) { - if (this.cursor <= this.limit_backward) return false; - var ch = this.current.charCodeAt(this.cursor - 1); - if (ch > max || ch < min) return false; - ch -= min; - if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) return false; - this.cursor--; - return true; - }; - - this.out_grouping = function(s, min, max) { - if (this.cursor >= this.limit) return false; - var ch = this.current.charCodeAt(this.cursor); - if (ch > max || ch < min) { - this.cursor++; - return true; - } - ch -= min; - if ((s[ch >>> 3] & (0X1 << (ch & 0x7))) == 0) { - this.cursor++; - return true; - } - return false; - }; - - this.out_grouping_b = function(s, min, max) { - if (this.cursor <= this.limit_backward) return false; - var ch = this.current.charCodeAt(this.cursor - 1); - if (ch > max || ch < min) { - this.cursor--; - return true; - } - ch -= min; - if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) { - this.cursor--; - return true; - } - return false; - }; - - this.eq_s = function(s) - { - if (this.limit - this.cursor < s.length) return false; - if (this.current.slice(this.cursor, this.cursor + s.length) != s) - { - return false; - } - this.cursor += s.length; - return true; - }; - - this.eq_s_b = function(s) - { - if (this.cursor - this.limit_backward < s.length) return false; - if (this.current.slice(this.cursor - s.length, this.cursor) != s) - { - return false; - } - this.cursor -= s.length; - return true; - }; - - /** @return {number} */ this.find_among = function(v) - { - var i = 0; - var j = v.length; - - var c = this.cursor; - var l = this.limit; - - var common_i = 0; - var common_j = 0; - - var first_key_inspected = false; - - while (true) - { - var k = i + ((j - i) >>> 1); - var diff = 0; - var common = common_i < common_j ? common_i : common_j; // smaller - // w[0]: string, w[1]: substring_i, w[2]: result, w[3]: function (optional) - var w = v[k]; - var i2; - for (i2 = common; i2 < w[0].length; i2++) - { - if (c + common == l) - { - diff = -1; - break; - } - diff = this.current.charCodeAt(c + common) - w[0].charCodeAt(i2); - if (diff != 0) break; - common++; - } - if (diff < 0) - { - j = k; - common_j = common; - } - else - { - i = k; - common_i = common; - } - if (j - i <= 1) - { - if (i > 0) break; // v->s has been inspected - if (j == i) break; // only one item in v - - // - but now we need to go round once more to get - // v->s inspected. This looks messy, but is actually - // the optimal approach. - - if (first_key_inspected) break; - first_key_inspected = true; - } - } - do { - var w = v[i]; - if (common_i >= w[0].length) - { - this.cursor = c + w[0].length; - if (w.length < 4) return w[2]; - var res = w[3](this); - this.cursor = c + w[0].length; - if (res) return w[2]; - } - i = w[1]; - } while (i >= 0); - return 0; - }; - - // find_among_b is for backwards processing. Same comments apply - this.find_among_b = function(v) - { - var i = 0; - var j = v.length - - var c = this.cursor; - var lb = this.limit_backward; - - var common_i = 0; - var common_j = 0; - - var first_key_inspected = false; - - while (true) - { - var k = i + ((j - i) >> 1); - var diff = 0; - var common = common_i < common_j ? common_i : common_j; - var w = v[k]; - var i2; - for (i2 = w[0].length - 1 - common; i2 >= 0; i2--) - { - if (c - common == lb) - { - diff = -1; - break; - } - diff = this.current.charCodeAt(c - 1 - common) - w[0].charCodeAt(i2); - if (diff != 0) break; - common++; - } - if (diff < 0) - { - j = k; - common_j = common; - } - else - { - i = k; - common_i = common; - } - if (j - i <= 1) - { - if (i > 0) break; - if (j == i) break; - if (first_key_inspected) break; - first_key_inspected = true; - } - } - do { - var w = v[i]; - if (common_i >= w[0].length) - { - this.cursor = c - w[0].length; - if (w.length < 4) return w[2]; - var res = w[3](this); - this.cursor = c - w[0].length; - if (res) return w[2]; - } - i = w[1]; - } while (i >= 0); - return 0; - }; - - /* to replace chars between c_bra and c_ket in this.current by the - * chars in s. - */ - this.replace_s = function(c_bra, c_ket, s) - { - var adjustment = s.length - (c_ket - c_bra); - this.current = this.current.slice(0, c_bra) + s + this.current.slice(c_ket); - this.limit += adjustment; - if (this.cursor >= c_ket) this.cursor += adjustment; - else if (this.cursor > c_bra) this.cursor = c_bra; - return adjustment; - }; - - this.slice_check = function() - { - if (this.bra < 0 || - this.bra > this.ket || - this.ket > this.limit || - this.limit > this.current.length) - { - return false; - } - return true; - }; - - this.slice_from = function(s) - { - var result = false; - if (this.slice_check()) - { - this.replace_s(this.bra, this.ket, s); - result = true; - } - return result; - }; - - this.slice_del = function() - { - return this.slice_from(""); - }; - - this.insert = function(c_bra, c_ket, s) - { - var adjustment = this.replace_s(c_bra, c_ket, s); - if (c_bra <= this.bra) this.bra += adjustment; - if (c_bra <= this.ket) this.ket += adjustment; - }; - - this.slice_to = function() - { - var result = ''; - if (this.slice_check()) - { - result = this.current.slice(this.bra, this.ket); - } - return result; - }; - - this.assign_to = function() - { - return this.current.slice(0, this.limit); - }; -}; diff --git a/html/de/_static/basic.css b/html/de/_static/basic.css deleted file mode 100644 index f316efcb..00000000 --- a/html/de/_static/basic.css +++ /dev/null @@ -1,925 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li p.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 360px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a:visited { - color: #551A8B; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, figure.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, figure.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, figure.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, figure.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar, -aside.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -nav.contents, -aside.topic, -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -nav.contents, -aside.topic, -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -aside.sidebar > :last-child, -nav.contents > :last-child, -aside.topic > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -aside.sidebar::after, -nav.contents::after, -aside.topic::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure, figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption, figcaption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number, -figcaption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text, -figcaption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - -/* -- object description styles --------------------------------------------- */ - -.sig { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; -} - -.sig-name, code.descname { - background-color: transparent; - font-weight: bold; -} - -.sig-name { - font-size: 1.1em; -} - -code.descname { - font-size: 1.2em; -} - -.sig-prename, code.descclassname { - background-color: transparent; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.sig-param.n { - font-style: italic; -} - -/* C++ specific styling */ - -.sig-inline.c-texpr, -.sig-inline.cpp-texpr { - font-family: unset; -} - -.sig.c .k, .sig.c .kt, -.sig.cpp .k, .sig.cpp .kt { - color: #0033B3; -} - -.sig.c .m, -.sig.cpp .m { - color: #1750EB; -} - -.sig.c .s, .sig.c .sc, -.sig.cpp .s, .sig.cpp .sc { - color: #067D17; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -aside.footnote > span, -div.citation > span { - float: left; -} -aside.footnote > span:last-of-type, -div.citation > span:last-of-type { - padding-right: 0.5em; -} -aside.footnote > p { - margin-left: 2em; -} -div.citation > p { - margin-left: 4em; -} -aside.footnote > p:last-of-type, -div.citation > p:last-of-type { - margin-bottom: 0em; -} -aside.footnote > p:last-of-type:after, -div.citation > p:last-of-type:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -.sig dd { - margin-top: 0px; - margin-bottom: 0px; -} - -.sig dl { - margin-top: 0px; - margin-bottom: 0px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0 0.5em; - content: ":"; - display: inline-block; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -.translated { - background-color: rgba(207, 255, 207, 0.2) -} - -.untranslated { - background-color: rgba(255, 207, 207, 0.2) -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; - white-space: nowrap; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; - -webkit-user-select: text; /* Safari fallback only */ - -webkit-user-select: none; /* Chrome/Safari */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* IE10+ */ -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/html/de/_static/css/badge_only.css b/html/de/_static/css/badge_only.css deleted file mode 100644 index c718cee4..00000000 --- a/html/de/_static/css/badge_only.css +++ /dev/null @@ -1 +0,0 @@ -.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/html/de/_static/css/fonts/Roboto-Slab-Bold.woff b/html/de/_static/css/fonts/Roboto-Slab-Bold.woff deleted file mode 100644 index 6cb60000..00000000 Binary files a/html/de/_static/css/fonts/Roboto-Slab-Bold.woff and /dev/null differ diff --git a/html/de/_static/css/fonts/Roboto-Slab-Bold.woff2 b/html/de/_static/css/fonts/Roboto-Slab-Bold.woff2 deleted file mode 100644 index 7059e231..00000000 Binary files a/html/de/_static/css/fonts/Roboto-Slab-Bold.woff2 and /dev/null differ diff --git a/html/de/_static/css/fonts/Roboto-Slab-Regular.woff b/html/de/_static/css/fonts/Roboto-Slab-Regular.woff deleted file mode 100644 index f815f63f..00000000 Binary files a/html/de/_static/css/fonts/Roboto-Slab-Regular.woff and /dev/null differ diff --git a/html/de/_static/css/fonts/Roboto-Slab-Regular.woff2 b/html/de/_static/css/fonts/Roboto-Slab-Regular.woff2 deleted file mode 100644 index f2c76e5b..00000000 Binary files a/html/de/_static/css/fonts/Roboto-Slab-Regular.woff2 and /dev/null differ diff --git a/html/de/_static/css/fonts/fontawesome-webfont.eot b/html/de/_static/css/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca9..00000000 Binary files a/html/de/_static/css/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/html/de/_static/css/fonts/fontawesome-webfont.svg b/html/de/_static/css/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e..00000000 --- a/html/de/_static/css/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/html/de/_static/css/fonts/fontawesome-webfont.ttf b/html/de/_static/css/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2f..00000000 Binary files a/html/de/_static/css/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/html/de/_static/css/fonts/fontawesome-webfont.woff b/html/de/_static/css/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4..00000000 Binary files a/html/de/_static/css/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/html/de/_static/css/fonts/fontawesome-webfont.woff2 b/html/de/_static/css/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc60..00000000 Binary files a/html/de/_static/css/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/html/de/_static/css/fonts/lato-bold-italic.woff b/html/de/_static/css/fonts/lato-bold-italic.woff deleted file mode 100644 index 88ad05b9..00000000 Binary files a/html/de/_static/css/fonts/lato-bold-italic.woff and /dev/null differ diff --git a/html/de/_static/css/fonts/lato-bold-italic.woff2 b/html/de/_static/css/fonts/lato-bold-italic.woff2 deleted file mode 100644 index c4e3d804..00000000 Binary files a/html/de/_static/css/fonts/lato-bold-italic.woff2 and /dev/null differ diff --git a/html/de/_static/css/fonts/lato-bold.woff b/html/de/_static/css/fonts/lato-bold.woff deleted file mode 100644 index c6dff51f..00000000 Binary files a/html/de/_static/css/fonts/lato-bold.woff and /dev/null differ diff --git a/html/de/_static/css/fonts/lato-bold.woff2 b/html/de/_static/css/fonts/lato-bold.woff2 deleted file mode 100644 index bb195043..00000000 Binary files a/html/de/_static/css/fonts/lato-bold.woff2 and /dev/null differ diff --git a/html/de/_static/css/fonts/lato-normal-italic.woff b/html/de/_static/css/fonts/lato-normal-italic.woff deleted file mode 100644 index 76114bc0..00000000 Binary files a/html/de/_static/css/fonts/lato-normal-italic.woff and /dev/null differ diff --git a/html/de/_static/css/fonts/lato-normal-italic.woff2 b/html/de/_static/css/fonts/lato-normal-italic.woff2 deleted file mode 100644 index 3404f37e..00000000 Binary files a/html/de/_static/css/fonts/lato-normal-italic.woff2 and /dev/null differ diff --git a/html/de/_static/css/fonts/lato-normal.woff b/html/de/_static/css/fonts/lato-normal.woff deleted file mode 100644 index ae1307ff..00000000 Binary files a/html/de/_static/css/fonts/lato-normal.woff and /dev/null differ diff --git a/html/de/_static/css/fonts/lato-normal.woff2 b/html/de/_static/css/fonts/lato-normal.woff2 deleted file mode 100644 index 3bf98433..00000000 Binary files a/html/de/_static/css/fonts/lato-normal.woff2 and /dev/null differ diff --git a/html/de/_static/css/theme.css b/html/de/_static/css/theme.css deleted file mode 100644 index 19a446a0..00000000 --- a/html/de/_static/css/theme.css +++ /dev/null @@ -1,4 +0,0 @@ -html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/html/de/_static/doctools.js b/html/de/_static/doctools.js deleted file mode 100644 index 4d67807d..00000000 --- a/html/de/_static/doctools.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ -"use strict"; - -const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ - "TEXTAREA", - "INPUT", - "SELECT", - "BUTTON", -]); - -const _ready = (callback) => { - if (document.readyState !== "loading") { - callback(); - } else { - document.addEventListener("DOMContentLoaded", callback); - } -}; - -/** - * Small JavaScript module for the documentation. - */ -const Documentation = { - init: () => { - Documentation.initDomainIndexTable(); - Documentation.initOnKeyListeners(); - }, - - /** - * i18n support - */ - TRANSLATIONS: {}, - PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), - LOCALE: "unknown", - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext: (string) => { - const translated = Documentation.TRANSLATIONS[string]; - switch (typeof translated) { - case "undefined": - return string; // no translation - case "string": - return translated; // translation exists - default: - return translated[0]; // (singular, plural) translation tuple exists - } - }, - - ngettext: (singular, plural, n) => { - const translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated !== "undefined") - return translated[Documentation.PLURAL_EXPR(n)]; - return n === 1 ? singular : plural; - }, - - addTranslations: (catalog) => { - Object.assign(Documentation.TRANSLATIONS, catalog.messages); - Documentation.PLURAL_EXPR = new Function( - "n", - `return (${catalog.plural_expr})` - ); - Documentation.LOCALE = catalog.locale; - }, - - /** - * helper function to focus on search bar - */ - focusSearchBar: () => { - document.querySelectorAll("input[name=q]")[0]?.focus(); - }, - - /** - * Initialise the domain index toggle buttons - */ - initDomainIndexTable: () => { - const toggler = (el) => { - const idNumber = el.id.substr(7); - const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); - if (el.src.substr(-9) === "minus.png") { - el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; - toggledRows.forEach((el) => (el.style.display = "none")); - } else { - el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; - toggledRows.forEach((el) => (el.style.display = "")); - } - }; - - const togglerElements = document.querySelectorAll("img.toggler"); - togglerElements.forEach((el) => - el.addEventListener("click", (event) => toggler(event.currentTarget)) - ); - togglerElements.forEach((el) => (el.style.display = "")); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); - }, - - initOnKeyListeners: () => { - // only install a listener if it is really needed - if ( - !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && - !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS - ) - return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.altKey || event.ctrlKey || event.metaKey) return; - - if (!event.shiftKey) { - switch (event.key) { - case "ArrowLeft": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const prevLink = document.querySelector('link[rel="prev"]'); - if (prevLink && prevLink.href) { - window.location.href = prevLink.href; - event.preventDefault(); - } - break; - case "ArrowRight": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const nextLink = document.querySelector('link[rel="next"]'); - if (nextLink && nextLink.href) { - window.location.href = nextLink.href; - event.preventDefault(); - } - break; - } - } - - // some keyboard layouts may need Shift to get / - switch (event.key) { - case "/": - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; - Documentation.focusSearchBar(); - event.preventDefault(); - } - }); - }, -}; - -// quick alias for translations -const _ = Documentation.gettext; - -_ready(Documentation.init); diff --git a/html/de/_static/documentation_options.js b/html/de/_static/documentation_options.js deleted file mode 100644 index ad2e26b0..00000000 --- a/html/de/_static/documentation_options.js +++ /dev/null @@ -1,13 +0,0 @@ -const DOCUMENTATION_OPTIONS = { - VERSION: '0.0.1', - LANGUAGE: 'de', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt', - NAVIGATION_WITH_KEYS: false, - SHOW_SEARCH_SUMMARY: true, - ENABLE_SEARCH_SHORTCUTS: true, -}; \ No newline at end of file diff --git a/html/de/_static/file.png b/html/de/_static/file.png deleted file mode 100644 index a858a410..00000000 Binary files a/html/de/_static/file.png and /dev/null differ diff --git a/html/de/_static/german-stemmer.js b/html/de/_static/german-stemmer.js deleted file mode 100644 index f5ff81bc..00000000 --- a/html/de/_static/german-stemmer.js +++ /dev/null @@ -1,577 +0,0 @@ -// Generated by Snowball 2.1.0 - https://snowballstem.org/ - -/**@constructor*/ -GermanStemmer = function() { - var base = new BaseStemmer(); - /** @const */ var a_0 = [ - ["", -1, 5], - ["U", 0, 2], - ["Y", 0, 1], - ["\u00E4", 0, 3], - ["\u00F6", 0, 4], - ["\u00FC", 0, 2] - ]; - - /** @const */ var a_1 = [ - ["e", -1, 2], - ["em", -1, 1], - ["en", -1, 2], - ["ern", -1, 1], - ["er", -1, 1], - ["s", -1, 3], - ["es", 5, 2] - ]; - - /** @const */ var a_2 = [ - ["en", -1, 1], - ["er", -1, 1], - ["st", -1, 2], - ["est", 2, 1] - ]; - - /** @const */ var a_3 = [ - ["ig", -1, 1], - ["lich", -1, 1] - ]; - - /** @const */ var a_4 = [ - ["end", -1, 1], - ["ig", -1, 2], - ["ung", -1, 1], - ["lich", -1, 3], - ["isch", -1, 2], - ["ik", -1, 2], - ["heit", -1, 3], - ["keit", -1, 4] - ]; - - /** @const */ var /** Array */ g_v = [17, 65, 16, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 32, 8]; - - /** @const */ var /** Array */ g_s_ending = [117, 30, 5]; - - /** @const */ var /** Array */ g_st_ending = [117, 30, 4]; - - var /** number */ I_x = 0; - var /** number */ I_p2 = 0; - var /** number */ I_p1 = 0; - - - /** @return {boolean} */ - function r_prelude() { - var /** number */ v_1 = base.cursor; - while(true) - { - var /** number */ v_2 = base.cursor; - lab0: { - lab1: { - var /** number */ v_3 = base.cursor; - lab2: { - base.bra = base.cursor; - if (!(base.eq_s("\u00DF"))) - { - break lab2; - } - base.ket = base.cursor; - if (!base.slice_from("ss")) - { - return false; - } - break lab1; - } - base.cursor = v_3; - if (base.cursor >= base.limit) - { - break lab0; - } - base.cursor++; - } - continue; - } - base.cursor = v_2; - break; - } - base.cursor = v_1; - while(true) - { - var /** number */ v_4 = base.cursor; - lab3: { - golab4: while(true) - { - var /** number */ v_5 = base.cursor; - lab5: { - if (!(base.in_grouping(g_v, 97, 252))) - { - break lab5; - } - base.bra = base.cursor; - lab6: { - var /** number */ v_6 = base.cursor; - lab7: { - if (!(base.eq_s("u"))) - { - break lab7; - } - base.ket = base.cursor; - if (!(base.in_grouping(g_v, 97, 252))) - { - break lab7; - } - if (!base.slice_from("U")) - { - return false; - } - break lab6; - } - base.cursor = v_6; - if (!(base.eq_s("y"))) - { - break lab5; - } - base.ket = base.cursor; - if (!(base.in_grouping(g_v, 97, 252))) - { - break lab5; - } - if (!base.slice_from("Y")) - { - return false; - } - } - base.cursor = v_5; - break golab4; - } - base.cursor = v_5; - if (base.cursor >= base.limit) - { - break lab3; - } - base.cursor++; - } - continue; - } - base.cursor = v_4; - break; - } - return true; - }; - - /** @return {boolean} */ - function r_mark_regions() { - I_p1 = base.limit; - I_p2 = base.limit; - var /** number */ v_1 = base.cursor; - { - var /** number */ c1 = base.cursor + 3; - if (c1 > base.limit) - { - return false; - } - base.cursor = c1; - } - I_x = base.cursor; - base.cursor = v_1; - golab0: while(true) - { - lab1: { - if (!(base.in_grouping(g_v, 97, 252))) - { - break lab1; - } - break golab0; - } - if (base.cursor >= base.limit) - { - return false; - } - base.cursor++; - } - golab2: while(true) - { - lab3: { - if (!(base.out_grouping(g_v, 97, 252))) - { - break lab3; - } - break golab2; - } - if (base.cursor >= base.limit) - { - return false; - } - base.cursor++; - } - I_p1 = base.cursor; - lab4: { - if (!(I_p1 < I_x)) - { - break lab4; - } - I_p1 = I_x; - } - golab5: while(true) - { - lab6: { - if (!(base.in_grouping(g_v, 97, 252))) - { - break lab6; - } - break golab5; - } - if (base.cursor >= base.limit) - { - return false; - } - base.cursor++; - } - golab7: while(true) - { - lab8: { - if (!(base.out_grouping(g_v, 97, 252))) - { - break lab8; - } - break golab7; - } - if (base.cursor >= base.limit) - { - return false; - } - base.cursor++; - } - I_p2 = base.cursor; - return true; - }; - - /** @return {boolean} */ - function r_postlude() { - var /** number */ among_var; - while(true) - { - var /** number */ v_1 = base.cursor; - lab0: { - base.bra = base.cursor; - among_var = base.find_among(a_0); - if (among_var == 0) - { - break lab0; - } - base.ket = base.cursor; - switch (among_var) { - case 1: - if (!base.slice_from("y")) - { - return false; - } - break; - case 2: - if (!base.slice_from("u")) - { - return false; - } - break; - case 3: - if (!base.slice_from("a")) - { - return false; - } - break; - case 4: - if (!base.slice_from("o")) - { - return false; - } - break; - case 5: - if (base.cursor >= base.limit) - { - break lab0; - } - base.cursor++; - break; - } - continue; - } - base.cursor = v_1; - break; - } - return true; - }; - - /** @return {boolean} */ - function r_R1() { - if (!(I_p1 <= base.cursor)) - { - return false; - } - return true; - }; - - /** @return {boolean} */ - function r_R2() { - if (!(I_p2 <= base.cursor)) - { - return false; - } - return true; - }; - - /** @return {boolean} */ - function r_standard_suffix() { - var /** number */ among_var; - var /** number */ v_1 = base.limit - base.cursor; - lab0: { - base.ket = base.cursor; - among_var = base.find_among_b(a_1); - if (among_var == 0) - { - break lab0; - } - base.bra = base.cursor; - if (!r_R1()) - { - break lab0; - } - switch (among_var) { - case 1: - if (!base.slice_del()) - { - return false; - } - break; - case 2: - if (!base.slice_del()) - { - return false; - } - var /** number */ v_2 = base.limit - base.cursor; - lab1: { - base.ket = base.cursor; - if (!(base.eq_s_b("s"))) - { - base.cursor = base.limit - v_2; - break lab1; - } - base.bra = base.cursor; - if (!(base.eq_s_b("nis"))) - { - base.cursor = base.limit - v_2; - break lab1; - } - if (!base.slice_del()) - { - return false; - } - } - break; - case 3: - if (!(base.in_grouping_b(g_s_ending, 98, 116))) - { - break lab0; - } - if (!base.slice_del()) - { - return false; - } - break; - } - } - base.cursor = base.limit - v_1; - var /** number */ v_3 = base.limit - base.cursor; - lab2: { - base.ket = base.cursor; - among_var = base.find_among_b(a_2); - if (among_var == 0) - { - break lab2; - } - base.bra = base.cursor; - if (!r_R1()) - { - break lab2; - } - switch (among_var) { - case 1: - if (!base.slice_del()) - { - return false; - } - break; - case 2: - if (!(base.in_grouping_b(g_st_ending, 98, 116))) - { - break lab2; - } - { - var /** number */ c1 = base.cursor - 3; - if (c1 < base.limit_backward) - { - break lab2; - } - base.cursor = c1; - } - if (!base.slice_del()) - { - return false; - } - break; - } - } - base.cursor = base.limit - v_3; - var /** number */ v_4 = base.limit - base.cursor; - lab3: { - base.ket = base.cursor; - among_var = base.find_among_b(a_4); - if (among_var == 0) - { - break lab3; - } - base.bra = base.cursor; - if (!r_R2()) - { - break lab3; - } - switch (among_var) { - case 1: - if (!base.slice_del()) - { - return false; - } - var /** number */ v_5 = base.limit - base.cursor; - lab4: { - base.ket = base.cursor; - if (!(base.eq_s_b("ig"))) - { - base.cursor = base.limit - v_5; - break lab4; - } - base.bra = base.cursor; - { - var /** number */ v_6 = base.limit - base.cursor; - lab5: { - if (!(base.eq_s_b("e"))) - { - break lab5; - } - base.cursor = base.limit - v_5; - break lab4; - } - base.cursor = base.limit - v_6; - } - if (!r_R2()) - { - base.cursor = base.limit - v_5; - break lab4; - } - if (!base.slice_del()) - { - return false; - } - } - break; - case 2: - { - var /** number */ v_7 = base.limit - base.cursor; - lab6: { - if (!(base.eq_s_b("e"))) - { - break lab6; - } - break lab3; - } - base.cursor = base.limit - v_7; - } - if (!base.slice_del()) - { - return false; - } - break; - case 3: - if (!base.slice_del()) - { - return false; - } - var /** number */ v_8 = base.limit - base.cursor; - lab7: { - base.ket = base.cursor; - lab8: { - var /** number */ v_9 = base.limit - base.cursor; - lab9: { - if (!(base.eq_s_b("er"))) - { - break lab9; - } - break lab8; - } - base.cursor = base.limit - v_9; - if (!(base.eq_s_b("en"))) - { - base.cursor = base.limit - v_8; - break lab7; - } - } - base.bra = base.cursor; - if (!r_R1()) - { - base.cursor = base.limit - v_8; - break lab7; - } - if (!base.slice_del()) - { - return false; - } - } - break; - case 4: - if (!base.slice_del()) - { - return false; - } - var /** number */ v_10 = base.limit - base.cursor; - lab10: { - base.ket = base.cursor; - if (base.find_among_b(a_3) == 0) - { - base.cursor = base.limit - v_10; - break lab10; - } - base.bra = base.cursor; - if (!r_R2()) - { - base.cursor = base.limit - v_10; - break lab10; - } - if (!base.slice_del()) - { - return false; - } - } - break; - } - } - base.cursor = base.limit - v_4; - return true; - }; - - this.stem = /** @return {boolean} */ function() { - var /** number */ v_1 = base.cursor; - r_prelude(); - base.cursor = v_1; - var /** number */ v_2 = base.cursor; - r_mark_regions(); - base.cursor = v_2; - base.limit_backward = base.cursor; base.cursor = base.limit; - r_standard_suffix(); - base.cursor = base.limit_backward; - var /** number */ v_4 = base.cursor; - r_postlude(); - base.cursor = v_4; - return true; - }; - - /**@return{string}*/ - this['stemWord'] = function(/**string*/word) { - base.setCurrent(word); - this.stem(); - return base.getCurrent(); - }; -}; diff --git a/html/de/_static/jquery.js b/html/de/_static/jquery.js deleted file mode 100644 index c4c6022f..00000000 --- a/html/de/_static/jquery.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/html/de/_static/js/html5shiv.min.js b/html/de/_static/js/html5shiv.min.js deleted file mode 100644 index cd1c674f..00000000 --- a/html/de/_static/js/html5shiv.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/** -* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed -*/ -!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/html/de/_static/js/theme.js b/html/de/_static/js/theme.js deleted file mode 100644 index 1fddb6ee..00000000 --- a/html/de/_static/js/theme.js +++ /dev/null @@ -1 +0,0 @@ -!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t=this.limit)return false;var s=this.current.charCodeAt(this.cursor);if(s>i||s>>3]&1<<(s&7))==0)return false;this.cursor++;return true};this.in_grouping_b=function(r,t,i){if(this.cursor<=this.limit_backward)return false;var s=this.current.charCodeAt(this.cursor-1);if(s>i||s>>3]&1<<(s&7))==0)return false;this.cursor--;return true};this.out_grouping=function(r,t,i){if(this.cursor>=this.limit)return false;var s=this.current.charCodeAt(this.cursor);if(s>i||s>>3]&1<<(s&7))==0){this.cursor++;return true}return false};this.out_grouping_b=function(r,t,i){if(this.cursor<=this.limit_backward)return false;var s=this.current.charCodeAt(this.cursor-1);if(s>i||s>>3]&1<<(s&7))==0){this.cursor--;return true}return false};this.eq_s=function(r){if(this.limit-this.cursor>>1);var a=0;var f=h0)break;if(i==t)break;if(n)break;n=true}}do{var l=r[t];if(h>=l[0].length){this.cursor=s+l[0].length;if(l.length<4)return l[2];var v=l[3](this);this.cursor=s+l[0].length;if(v)return l[2]}t=l[1]}while(t>=0);return 0};this.find_among_b=function(r){var t=0;var i=r.length;var s=this.cursor;var e=this.limit_backward;var h=0;var u=0;var n=false;while(true){var c=t+(i-t>>1);var a=0;var f=h=0;o--){if(s-f==e){a=-1;break}a=this.current.charCodeAt(s-1-f)-l[0].charCodeAt(o);if(a!=0)break;f++}if(a<0){i=c;u=f}else{t=c;h=f}if(i-t<=1){if(t>0)break;if(i==t)break;if(n)break;n=true}}do{var l=r[t];if(h>=l[0].length){this.cursor=s-l[0].length;if(l.length<4)return l[2];var v=l[3](this);this.cursor=s-l[0].length;if(v)return l[2]}t=l[1]}while(t>=0);return 0};this.replace_s=function(r,t,i){var s=i.length-(t-r);this.current=this.current.slice(0,r)+i+this.current.slice(t);this.limit+=s;if(this.cursor>=t)this.cursor+=s;else if(this.cursor>r)this.cursor=r;return s};this.slice_check=function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>this.current.length){return false}return true};this.slice_from=function(r){var t=false;if(this.slice_check()){this.replace_s(this.bra,this.ket,r);t=true}return t};this.slice_del=function(){return this.slice_from("")};this.insert=function(r,t,i){var s=this.replace_s(r,t,i);if(r<=this.bra)this.bra+=s;if(r<=this.ket)this.ket+=s};this.slice_to=function(){var r="";if(this.slice_check()){r=this.current.slice(this.bra,this.ket)}return r};this.assign_to=function(){return this.current.slice(0,this.limit)}}; -GermanStemmer=function(){var r=new BaseStemmer;var e=[["",-1,5],["U",0,2],["Y",0,1],["ä",0,3],["ö",0,4],["ü",0,2]];var i=[["e",-1,2],["em",-1,1],["en",-1,2],["ern",-1,1],["er",-1,1],["s",-1,3],["es",5,2]];var s=[["en",-1,1],["er",-1,1],["st",-1,2],["est",2,1]];var u=[["ig",-1,1],["lich",-1,1]];var a=[["end",-1,1],["ig",-1,2],["ung",-1,1],["lich",-1,3],["isch",-1,2],["ik",-1,2],["heit",-1,3],["keit",-1,4]];var c=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8];var t=[117,30,5];var o=[117,30,4];var f=0;var l=0;var n=0;function b(){var e=r.cursor;while(true){var i=r.cursor;r:{e:{var s=r.cursor;i:{r.bra=r.cursor;if(!r.eq_s("ß")){break i}r.ket=r.cursor;if(!r.slice_from("ss")){return false}break e}r.cursor=s;if(r.cursor>=r.limit){break r}r.cursor++}continue}r.cursor=i;break}r.cursor=e;while(true){var u=r.cursor;r:{e:while(true){var a=r.cursor;i:{if(!r.in_grouping(c,97,252)){break i}r.bra=r.cursor;s:{var t=r.cursor;u:{if(!r.eq_s("u")){break u}r.ket=r.cursor;if(!r.in_grouping(c,97,252)){break u}if(!r.slice_from("U")){return false}break s}r.cursor=t;if(!r.eq_s("y")){break i}r.ket=r.cursor;if(!r.in_grouping(c,97,252)){break i}if(!r.slice_from("Y")){return false}}r.cursor=a;break e}r.cursor=a;if(r.cursor>=r.limit){break r}r.cursor++}continue}r.cursor=u;break}return true}function k(){n=r.limit;l=r.limit;var e=r.cursor;{var i=r.cursor+3;if(i>r.limit){return false}r.cursor=i}f=r.cursor;r.cursor=e;r:while(true){e:{if(!r.in_grouping(c,97,252)){break e}break r}if(r.cursor>=r.limit){return false}r.cursor++}r:while(true){e:{if(!r.out_grouping(c,97,252)){break e}break r}if(r.cursor>=r.limit){return false}r.cursor++}n=r.cursor;r:{if(!(n=r.limit){return false}r.cursor++}r:while(true){e:{if(!r.out_grouping(c,97,252)){break e}break r}if(r.cursor>=r.limit){return false}r.cursor++}l=r.cursor;return true}function m(){var i;while(true){var s=r.cursor;r:{r.bra=r.cursor;i=r.find_among(e);if(i==0){break r}r.ket=r.cursor;switch(i){case 1:if(!r.slice_from("y")){return false}break;case 2:if(!r.slice_from("u")){return false}break;case 3:if(!r.slice_from("a")){return false}break;case 4:if(!r.slice_from("o")){return false}break;case 5:if(r.cursor>=r.limit){break r}r.cursor++;break}continue}r.cursor=s;break}return true}function _(){if(!(n<=r.cursor)){return false}return true}function v(){if(!(l<=r.cursor)){return false}return true}function g(){var e;var c=r.limit-r.cursor;r:{r.ket=r.cursor;e=r.find_among_b(i);if(e==0){break r}r.bra=r.cursor;if(!_()){break r}switch(e){case 1:if(!r.slice_del()){return false}break;case 2:if(!r.slice_del()){return false}var f=r.limit-r.cursor;e:{r.ket=r.cursor;if(!r.eq_s_b("s")){r.cursor=r.limit-f;break e}r.bra=r.cursor;if(!r.eq_s_b("nis")){r.cursor=r.limit-f;break e}if(!r.slice_del()){return false}}break;case 3:if(!r.in_grouping_b(t,98,116)){break r}if(!r.slice_del()){return false}break}}r.cursor=r.limit-c;var l=r.limit-r.cursor;r:{r.ket=r.cursor;e=r.find_among_b(s);if(e==0){break r}r.bra=r.cursor;if(!_()){break r}switch(e){case 1:if(!r.slice_del()){return false}break;case 2:if(!r.in_grouping_b(o,98,116)){break r}{var n=r.cursor-3;if(n - - - diff --git a/html/de/_static/nbsphinx-code-cells.css b/html/de/_static/nbsphinx-code-cells.css deleted file mode 100644 index a3fb27c3..00000000 --- a/html/de/_static/nbsphinx-code-cells.css +++ /dev/null @@ -1,259 +0,0 @@ -/* remove conflicting styling from Sphinx themes */ -div.nbinput.container div.prompt *, -div.nboutput.container div.prompt *, -div.nbinput.container div.input_area pre, -div.nboutput.container div.output_area pre, -div.nbinput.container div.input_area .highlight, -div.nboutput.container div.output_area .highlight { - border: none; - padding: 0; - margin: 0; - box-shadow: none; -} - -div.nbinput.container > div[class*=highlight], -div.nboutput.container > div[class*=highlight] { - margin: 0; -} - -div.nbinput.container div.prompt *, -div.nboutput.container div.prompt * { - background: none; -} - -div.nboutput.container div.output_area .highlight, -div.nboutput.container div.output_area pre { - background: unset; -} - -div.nboutput.container div.output_area div.highlight { - color: unset; /* override Pygments text color */ -} - -/* avoid gaps between output lines */ -div.nboutput.container div[class*=highlight] pre { - line-height: normal; -} - -/* input/output containers */ -div.nbinput.container, -div.nboutput.container { - display: -webkit-flex; - display: flex; - align-items: flex-start; - margin: 0; - width: 100%; -} -@media (max-width: 540px) { - div.nbinput.container, - div.nboutput.container { - flex-direction: column; - } -} - -/* input container */ -div.nbinput.container { - padding-top: 5px; -} - -/* last container */ -div.nblast.container { - padding-bottom: 5px; -} - -/* input prompt */ -div.nbinput.container div.prompt pre, -/* for sphinx_immaterial theme: */ -div.nbinput.container div.prompt pre > code { - color: #307FC1; -} - -/* output prompt */ -div.nboutput.container div.prompt pre, -/* for sphinx_immaterial theme: */ -div.nboutput.container div.prompt pre > code { - color: #BF5B3D; -} - -/* all prompts */ -div.nbinput.container div.prompt, -div.nboutput.container div.prompt { - width: 4.5ex; - padding-top: 5px; - position: relative; - user-select: none; -} - -div.nbinput.container div.prompt > div, -div.nboutput.container div.prompt > div { - position: absolute; - right: 0; - margin-right: 0.3ex; -} - -@media (max-width: 540px) { - div.nbinput.container div.prompt, - div.nboutput.container div.prompt { - width: unset; - text-align: left; - padding: 0.4em; - } - div.nboutput.container div.prompt.empty { - padding: 0; - } - - div.nbinput.container div.prompt > div, - div.nboutput.container div.prompt > div { - position: unset; - } -} - -/* disable scrollbars and line breaks on prompts */ -div.nbinput.container div.prompt pre, -div.nboutput.container div.prompt pre { - overflow: hidden; - white-space: pre; -} - -/* input/output area */ -div.nbinput.container div.input_area, -div.nboutput.container div.output_area { - -webkit-flex: 1; - flex: 1; - overflow: auto; -} -@media (max-width: 540px) { - div.nbinput.container div.input_area, - div.nboutput.container div.output_area { - width: 100%; - } -} - -/* input area */ -div.nbinput.container div.input_area { - border: 1px solid #e0e0e0; - border-radius: 2px; - /*background: #f5f5f5;*/ -} - -/* override MathJax center alignment in output cells */ -div.nboutput.container div[class*=MathJax] { - text-align: left !important; -} - -/* override sphinx.ext.imgmath center alignment in output cells */ -div.nboutput.container div.math p { - text-align: left; -} - -/* standard error */ -div.nboutput.container div.output_area.stderr { - background: #fdd; -} - -/* ANSI colors */ -.ansi-black-fg { color: #3E424D; } -.ansi-black-bg { background-color: #3E424D; } -.ansi-black-intense-fg { color: #282C36; } -.ansi-black-intense-bg { background-color: #282C36; } -.ansi-red-fg { color: #E75C58; } -.ansi-red-bg { background-color: #E75C58; } -.ansi-red-intense-fg { color: #B22B31; } -.ansi-red-intense-bg { background-color: #B22B31; } -.ansi-green-fg { color: #00A250; } -.ansi-green-bg { background-color: #00A250; } -.ansi-green-intense-fg { color: #007427; } -.ansi-green-intense-bg { background-color: #007427; } -.ansi-yellow-fg { color: #DDB62B; } -.ansi-yellow-bg { background-color: #DDB62B; } -.ansi-yellow-intense-fg { color: #B27D12; } -.ansi-yellow-intense-bg { background-color: #B27D12; } -.ansi-blue-fg { color: #208FFB; } -.ansi-blue-bg { background-color: #208FFB; } -.ansi-blue-intense-fg { color: #0065CA; } -.ansi-blue-intense-bg { background-color: #0065CA; } -.ansi-magenta-fg { color: #D160C4; } -.ansi-magenta-bg { background-color: #D160C4; } -.ansi-magenta-intense-fg { color: #A03196; } -.ansi-magenta-intense-bg { background-color: #A03196; } -.ansi-cyan-fg { color: #60C6C8; } -.ansi-cyan-bg { background-color: #60C6C8; } -.ansi-cyan-intense-fg { color: #258F8F; } -.ansi-cyan-intense-bg { background-color: #258F8F; } -.ansi-white-fg { color: #C5C1B4; } -.ansi-white-bg { background-color: #C5C1B4; } -.ansi-white-intense-fg { color: #A1A6B2; } -.ansi-white-intense-bg { background-color: #A1A6B2; } - -.ansi-default-inverse-fg { color: #FFFFFF; } -.ansi-default-inverse-bg { background-color: #000000; } - -.ansi-bold { font-weight: bold; } -.ansi-underline { text-decoration: underline; } - - -div.nbinput.container div.input_area div[class*=highlight] > pre, -div.nboutput.container div.output_area div[class*=highlight] > pre, -div.nboutput.container div.output_area div[class*=highlight].math, -div.nboutput.container div.output_area.rendered_html, -div.nboutput.container div.output_area > div.output_javascript, -div.nboutput.container div.output_area:not(.rendered_html) > img{ - padding: 5px; - margin: 0; -} - -/* fix copybtn overflow problem in chromium (needed for 'sphinx_copybutton') */ -div.nbinput.container div.input_area > div[class^='highlight'], -div.nboutput.container div.output_area > div[class^='highlight']{ - overflow-y: hidden; -} - -/* hide copy button on prompts for 'sphinx_copybutton' extension ... */ -.prompt .copybtn, -/* ... and 'sphinx_immaterial' theme */ -.prompt .md-clipboard.md-icon { - display: none; -} - -/* Some additional styling taken form the Jupyter notebook CSS */ -.jp-RenderedHTMLCommon table, -div.rendered_html table { - border: none; - border-collapse: collapse; - border-spacing: 0; - color: black; - font-size: 12px; - table-layout: fixed; -} -.jp-RenderedHTMLCommon thead, -div.rendered_html thead { - border-bottom: 1px solid black; - vertical-align: bottom; -} -.jp-RenderedHTMLCommon tr, -.jp-RenderedHTMLCommon th, -.jp-RenderedHTMLCommon td, -div.rendered_html tr, -div.rendered_html th, -div.rendered_html td { - text-align: right; - vertical-align: middle; - padding: 0.5em 0.5em; - line-height: normal; - white-space: normal; - max-width: none; - border: none; -} -.jp-RenderedHTMLCommon th, -div.rendered_html th { - font-weight: bold; -} -.jp-RenderedHTMLCommon tbody tr:nth-child(odd), -div.rendered_html tbody tr:nth-child(odd) { - background: #f5f5f5; -} -.jp-RenderedHTMLCommon tbody tr:hover, -div.rendered_html tbody tr:hover { - background: rgba(66, 165, 245, 0.2); -} - diff --git a/html/de/_static/nbsphinx-gallery.css b/html/de/_static/nbsphinx-gallery.css deleted file mode 100644 index 365c27a9..00000000 --- a/html/de/_static/nbsphinx-gallery.css +++ /dev/null @@ -1,31 +0,0 @@ -.nbsphinx-gallery { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - gap: 5px; - margin-top: 1em; - margin-bottom: 1em; -} - -.nbsphinx-gallery > a { - padding: 5px; - border: 1px dotted currentColor; - border-radius: 2px; - text-align: center; -} - -.nbsphinx-gallery > a:hover { - border-style: solid; -} - -.nbsphinx-gallery img { - max-width: 100%; - max-height: 100%; -} - -.nbsphinx-gallery > a > div:first-child { - display: flex; - align-items: start; - justify-content: center; - height: 120px; - margin-bottom: 5px; -} diff --git a/html/de/_static/nbsphinx-no-thumbnail.svg b/html/de/_static/nbsphinx-no-thumbnail.svg deleted file mode 100644 index 9dca7588..00000000 --- a/html/de/_static/nbsphinx-no-thumbnail.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/html/de/_static/plus.png b/html/de/_static/plus.png deleted file mode 100644 index 7107cec9..00000000 Binary files a/html/de/_static/plus.png and /dev/null differ diff --git a/html/de/_static/pygments.css b/html/de/_static/pygments.css deleted file mode 100644 index 84ab3030..00000000 --- a/html/de/_static/pygments.css +++ /dev/null @@ -1,75 +0,0 @@ -pre { line-height: 125%; } -td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -.highlight .hll { background-color: #ffffcc } -.highlight { background: #f8f8f8; } -.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #008000; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ -.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #9C6500 } /* Comment.Preproc */ -.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ -.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ -.highlight .gr { color: #E40000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #008400 } /* Generic.Inserted */ -.highlight .go { color: #717171 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0044DD } /* Generic.Traceback */ -.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #008000 } /* Keyword.Pseudo */ -.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #B00040 } /* Keyword.Type */ -.highlight .m { color: #666666 } /* Literal.Number */ -.highlight .s { color: #BA2121 } /* Literal.String */ -.highlight .na { color: #687822 } /* Name.Attribute */ -.highlight .nb { color: #008000 } /* Name.Builtin */ -.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ -.highlight .no { color: #880000 } /* Name.Constant */ -.highlight .nd { color: #AA22FF } /* Name.Decorator */ -.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #0000FF } /* Name.Function */ -.highlight .nl { color: #767600 } /* Name.Label */ -.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #19177C } /* Name.Variable */ -.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mb { color: #666666 } /* Literal.Number.Bin */ -.highlight .mf { color: #666666 } /* Literal.Number.Float */ -.highlight .mh { color: #666666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666666 } /* Literal.Number.Oct */ -.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ -.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -.highlight .sc { color: #BA2121 } /* Literal.String.Char */ -.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ -.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ -.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -.highlight .ss { color: #19177C } /* Literal.String.Symbol */ -.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -.highlight .fm { color: #0000FF } /* Name.Function.Magic */ -.highlight .vc { color: #19177C } /* Name.Variable.Class */ -.highlight .vg { color: #19177C } /* Name.Variable.Global */ -.highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .vm { color: #19177C } /* Name.Variable.Magic */ -.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/html/de/_static/searchtools.js b/html/de/_static/searchtools.js deleted file mode 100644 index 92da3f8b..00000000 --- a/html/de/_static/searchtools.js +++ /dev/null @@ -1,619 +0,0 @@ -/* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ -"use strict"; - -/** - * Simple result scoring code. - */ -if (typeof Scorer === "undefined") { - var Scorer = { - // Implement the following function to further tweak the score for each result - // The function takes a result array [docname, title, anchor, descr, score, filename] - // and returns the new score. - /* - score: result => { - const [docname, title, anchor, descr, score, filename] = result - return score - }, - */ - - // query matches the full name of an object - objNameMatch: 11, - // or matches in the last dotted part of the object name - objPartialMatch: 6, - // Additive scores depending on the priority of the object - objPrio: { - 0: 15, // used to be importantResults - 1: 5, // used to be objectResults - 2: -5, // used to be unimportantResults - }, - // Used when the priority is not in the mapping. - objPrioDefault: 0, - - // query found in title - title: 15, - partialTitle: 7, - // query found in terms - term: 5, - partialTerm: 2, - }; -} - -const _removeChildren = (element) => { - while (element && element.lastChild) element.removeChild(element.lastChild); -}; - -/** - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping - */ -const _escapeRegExp = (string) => - string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string - -const _displayItem = (item, searchTerms, highlightTerms) => { - const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; - const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; - const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; - const contentRoot = document.documentElement.dataset.content_root; - - const [docName, title, anchor, descr, score, _filename] = item; - - let listItem = document.createElement("li"); - let requestUrl; - let linkUrl; - if (docBuilder === "dirhtml") { - // dirhtml builder - let dirname = docName + "/"; - if (dirname.match(/\/index\/$/)) - dirname = dirname.substring(0, dirname.length - 6); - else if (dirname === "index/") dirname = ""; - requestUrl = contentRoot + dirname; - linkUrl = requestUrl; - } else { - // normal html builders - requestUrl = contentRoot + docName + docFileSuffix; - linkUrl = docName + docLinkSuffix; - } - let linkEl = listItem.appendChild(document.createElement("a")); - linkEl.href = linkUrl + anchor; - linkEl.dataset.score = score; - linkEl.innerHTML = title; - if (descr) { - listItem.appendChild(document.createElement("span")).innerHTML = - " (" + descr + ")"; - // highlight search terms in the description - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - } - else if (showSearchSummary) - fetch(requestUrl) - .then((responseData) => responseData.text()) - .then((data) => { - if (data) - listItem.appendChild( - Search.makeSearchSummary(data, searchTerms, anchor) - ); - // highlight search terms in the summary - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - }); - Search.output.appendChild(listItem); -}; -const _finishSearch = (resultCount) => { - Search.stopPulse(); - Search.title.innerText = _("Search Results"); - if (!resultCount) - Search.status.innerText = Documentation.gettext( - "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." - ); - else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." - ).replace('${resultCount}', resultCount); -}; -const _displayNextItem = ( - results, - resultCount, - searchTerms, - highlightTerms, -) => { - // results left, load the summary and display it - // this is intended to be dynamic (don't sub resultsCount) - if (results.length) { - _displayItem(results.pop(), searchTerms, highlightTerms); - setTimeout( - () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), - 5 - ); - } - // search finished, update title and status message - else _finishSearch(resultCount); -}; -// Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. -// Order the results by score (in opposite order of appearance, since the -// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. -const _orderResultsByScoreThenName = (a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; -}; - -/** - * Default splitQuery function. Can be overridden in ``sphinx.search`` with a - * custom function per language. - * - * The regular expression works by splitting the string on consecutive characters - * that are not Unicode letters, numbers, underscores, or emoji characters. - * This is the same as ``\W+`` in Python, preserving the surrogate pair area. - */ -if (typeof splitQuery === "undefined") { - var splitQuery = (query) => query - .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) - .filter(term => term) // remove remaining empty strings -} - -/** - * Search Module - */ -const Search = { - _index: null, - _queued_query: null, - _pulse_status: -1, - - htmlToText: (htmlString, anchor) => { - const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - for (const removalQuery of [".headerlinks", "script", "style"]) { - htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); - } - if (anchor) { - const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); - if (anchorContent) return anchorContent.textContent; - - console.warn( - `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` - ); - } - - // if anchor not specified or not found, fall back to main content - const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent) return docContent.textContent; - - console.warn( - "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." - ); - return ""; - }, - - init: () => { - const query = new URLSearchParams(window.location.search).get("q"); - document - .querySelectorAll('input[name="q"]') - .forEach((el) => (el.value = query)); - if (query) Search.performSearch(query); - }, - - loadIndex: (url) => - (document.body.appendChild(document.createElement("script")).src = url), - - setIndex: (index) => { - Search._index = index; - if (Search._queued_query !== null) { - const query = Search._queued_query; - Search._queued_query = null; - Search.query(query); - } - }, - - hasIndex: () => Search._index !== null, - - deferQuery: (query) => (Search._queued_query = query), - - stopPulse: () => (Search._pulse_status = -1), - - startPulse: () => { - if (Search._pulse_status >= 0) return; - - const pulse = () => { - Search._pulse_status = (Search._pulse_status + 1) % 4; - Search.dots.innerText = ".".repeat(Search._pulse_status); - if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); - }; - pulse(); - }, - - /** - * perform a search for something (or wait until index is loaded) - */ - performSearch: (query) => { - // create the required interface elements - const searchText = document.createElement("h2"); - searchText.textContent = _("Searching"); - const searchSummary = document.createElement("p"); - searchSummary.classList.add("search-summary"); - searchSummary.innerText = ""; - const searchList = document.createElement("ul"); - searchList.classList.add("search"); - - const out = document.getElementById("search-results"); - Search.title = out.appendChild(searchText); - Search.dots = Search.title.appendChild(document.createElement("span")); - Search.status = out.appendChild(searchSummary); - Search.output = out.appendChild(searchList); - - const searchProgress = document.getElementById("search-progress"); - // Some themes don't use the search progress node - if (searchProgress) { - searchProgress.innerText = _("Preparing search..."); - } - Search.startPulse(); - - // index already loaded, the browser was quick! - if (Search.hasIndex()) Search.query(query); - else Search.deferQuery(query); - }, - - _parseQuery: (query) => { - // stem the search terms and add them to the correct list - const stemmer = new Stemmer(); - const searchTerms = new Set(); - const excludedTerms = new Set(); - const highlightTerms = new Set(); - const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); - splitQuery(query.trim()).forEach((queryTerm) => { - const queryTermLower = queryTerm.toLowerCase(); - - // maybe skip this "word" - // stopwords array is from language_data.js - if ( - stopwords.indexOf(queryTermLower) !== -1 || - queryTerm.match(/^\d+$/) - ) - return; - - // stem the word - let word = stemmer.stemWord(queryTermLower); - // select the correct list - if (word[0] === "-") excludedTerms.add(word.substr(1)); - else { - searchTerms.add(word); - highlightTerms.add(queryTermLower); - } - }); - - if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js - localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) - } - - // console.debug("SEARCH: searching for:"); - // console.info("required: ", [...searchTerms]); - // console.info("excluded: ", [...excludedTerms]); - - return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; - }, - - /** - * execute search (requires search index to be loaded) - */ - _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - - // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. - const normalResults = []; - const nonMainIndexResults = []; - - _removeChildren(document.getElementById("search-progress")); - - const queryLower = query.toLowerCase().trim(); - for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { - for (const [file, id] of foundTitles) { - let score = Math.round(100 * queryLower.length / title.length) - normalResults.push([ - docNames[file], - titles[file] !== title ? `${titles[file]} > ${title}` : title, - id !== null ? "#" + id : "", - null, - score, - filenames[file], - ]); - } - } - } - - // search for explicit entries in index directives - for (const [entry, foundEntries] of Object.entries(indexEntries)) { - if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id, isMain] of foundEntries) { - const score = Math.round(100 * queryLower.length / entry.length); - const result = [ - docNames[file], - titles[file], - id ? "#" + id : "", - null, - score, - filenames[file], - ]; - if (isMain) { - normalResults.push(result); - } else { - nonMainIndexResults.push(result); - } - } - } - } - - // lookup as object - objectTerms.forEach((term) => - normalResults.push(...Search.performObjectSearch(term, objectTerms)) - ); - - // lookup as search terms in fulltext - normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); - - // let the scorer override scores with a custom scoring function - if (Scorer.score) { - normalResults.forEach((item) => (item[4] = Scorer.score(item))); - nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); - } - - // Sort each group of results by score and then alphabetically by name. - normalResults.sort(_orderResultsByScoreThenName); - nonMainIndexResults.sort(_orderResultsByScoreThenName); - - // Combine the result groups in (reverse) order. - // Non-main index entries are typically arbitrary cross-references, - // so display them after other results. - let results = [...nonMainIndexResults, ...normalResults]; - - // remove duplicate search results - // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept - let seen = new Set(); - results = results.reverse().reduce((acc, result) => { - let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); - if (!seen.has(resultStr)) { - acc.push(result); - seen.add(resultStr); - } - return acc; - }, []); - - return results.reverse(); - }, - - query: (query) => { - const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); - const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); - - // for debugging - //Search.lastresults = results.slice(); // a copy - // console.info("search results:", Search.lastresults); - - // print the results - _displayNextItem(results, results.length, searchTerms, highlightTerms); - }, - - /** - * search for object names - */ - performObjectSearch: (object, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const objects = Search._index.objects; - const objNames = Search._index.objnames; - const titles = Search._index.titles; - - const results = []; - - const objectSearchCallback = (prefix, match) => { - const name = match[4] - const fullname = (prefix ? prefix + "." : "") + name; - const fullnameLower = fullname.toLowerCase(); - if (fullnameLower.indexOf(object) < 0) return; - - let score = 0; - const parts = fullnameLower.split("."); - - // check for different match types: exact matches of full name or - // "last name" (i.e. last dotted part) - if (fullnameLower === object || parts.slice(-1)[0] === object) - score += Scorer.objNameMatch; - else if (parts.slice(-1)[0].indexOf(object) > -1) - score += Scorer.objPartialMatch; // matches in last name - - const objName = objNames[match[1]][2]; - const title = titles[match[0]]; - - // If more than one term searched for, we require other words to be - // found in the name/title/description - const otherTerms = new Set(objectTerms); - otherTerms.delete(object); - if (otherTerms.size > 0) { - const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); - if ( - [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) - ) - return; - } - - let anchor = match[3]; - if (anchor === "") anchor = fullname; - else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; - - const descr = objName + _(", in ") + title; - - // add custom score for some objects according to scorer - if (Scorer.objPrio.hasOwnProperty(match[2])) - score += Scorer.objPrio[match[2]]; - else score += Scorer.objPrioDefault; - - results.push([ - docNames[match[0]], - fullname, - "#" + anchor, - descr, - score, - filenames[match[0]], - ]); - }; - Object.keys(objects).forEach((prefix) => - objects[prefix].forEach((array) => - objectSearchCallback(prefix, array) - ) - ); - return results; - }, - - /** - * search for full-text terms in the index - */ - performTermsSearch: (searchTerms, excludedTerms) => { - // prepare search - const terms = Search._index.terms; - const titleTerms = Search._index.titleterms; - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - - const scoreMap = new Map(); - const fileMap = new Map(); - - // perform the search on the required terms - searchTerms.forEach((word) => { - const files = []; - const arr = [ - { files: terms[word], score: Scorer.term }, - { files: titleTerms[word], score: Scorer.title }, - ]; - // add support for partial matches - if (word.length > 2) { - const escapedWord = _escapeRegExp(word); - if (!terms.hasOwnProperty(word)) { - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - } - if (!titleTerms.hasOwnProperty(word)) { - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); - }); - } - } - - // no match but word was a required one - if (arr.every((record) => record.files === undefined)) return; - - // found search word in contents - arr.forEach((record) => { - if (record.files === undefined) return; - - let recordFiles = record.files; - if (recordFiles.length === undefined) recordFiles = [recordFiles]; - files.push(...recordFiles); - - // set score for the word in each file - recordFiles.forEach((file) => { - if (!scoreMap.has(file)) scoreMap.set(file, {}); - scoreMap.get(file)[word] = record.score; - }); - }); - - // create the mapping - files.forEach((file) => { - if (!fileMap.has(file)) fileMap.set(file, [word]); - else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); - }); - }); - - // now check if the files don't contain excluded terms - const results = []; - for (const [file, wordList] of fileMap) { - // check if all requirements are matched - - // as search terms with length < 3 are discarded - const filteredTermCount = [...searchTerms].filter( - (term) => term.length > 2 - ).length; - if ( - wordList.length !== searchTerms.size && - wordList.length !== filteredTermCount - ) - continue; - - // ensure that none of the excluded terms is in the search result - if ( - [...excludedTerms].some( - (term) => - terms[term] === file || - titleTerms[term] === file || - (terms[term] || []).includes(file) || - (titleTerms[term] || []).includes(file) - ) - ) - break; - - // select one (max) score for the file. - const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); - // add result to the result list - results.push([ - docNames[file], - titles[file], - "", - null, - score, - filenames[file], - ]); - } - return results; - }, - - /** - * helper function to return a node containing the - * search summary for a given text. keywords is a list - * of stemmed words. - */ - makeSearchSummary: (htmlText, keywords, anchor) => { - const text = Search.htmlToText(htmlText, anchor); - if (text === "") return null; - - const textLower = text.toLowerCase(); - const actualStartPosition = [...keywords] - .map((k) => textLower.indexOf(k.toLowerCase())) - .filter((i) => i > -1) - .slice(-1)[0]; - const startWithContext = Math.max(actualStartPosition - 120, 0); - - const top = startWithContext === 0 ? "" : "..."; - const tail = startWithContext + 240 < text.length ? "..." : ""; - - let summary = document.createElement("p"); - summary.classList.add("context"); - summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; - - return summary; - }, -}; - -_ready(Search.init); diff --git a/html/de/_static/sphinx_highlight.js b/html/de/_static/sphinx_highlight.js deleted file mode 100644 index 8a96c69a..00000000 --- a/html/de/_static/sphinx_highlight.js +++ /dev/null @@ -1,154 +0,0 @@ -/* Highlighting utilities for Sphinx HTML documentation. */ -"use strict"; - -const SPHINX_HIGHLIGHT_ENABLED = true - -/** - * highlight a given string on a node by wrapping it in - * span elements with the given class name. - */ -const _highlight = (node, addItems, text, className) => { - if (node.nodeType === Node.TEXT_NODE) { - const val = node.nodeValue; - const parent = node.parentNode; - const pos = val.toLowerCase().indexOf(text); - if ( - pos >= 0 && - !parent.classList.contains(className) && - !parent.classList.contains("nohighlight") - ) { - let span; - - const closestNode = parent.closest("body, svg, foreignObject"); - const isInSVG = closestNode && closestNode.matches("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.classList.add(className); - } - - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - const rest = document.createTextNode(val.substr(pos + text.length)); - parent.insertBefore( - span, - parent.insertBefore( - rest, - node.nextSibling - ) - ); - node.nodeValue = val.substr(0, pos); - /* There may be more occurrences of search term in this node. So call this - * function recursively on the remaining fragment. - */ - _highlight(rest, addItems, text, className); - - if (isInSVG) { - const rect = document.createElementNS( - "http://www.w3.org/2000/svg", - "rect" - ); - const bbox = parent.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute("class", className); - addItems.push({ parent: parent, target: rect }); - } - } - } else if (node.matches && !node.matches("button, select, textarea")) { - node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); - } -}; -const _highlightText = (thisNode, text, className) => { - let addItems = []; - _highlight(thisNode, addItems, text, className); - addItems.forEach((obj) => - obj.parent.insertAdjacentElement("beforebegin", obj.target) - ); -}; - -/** - * Small JavaScript module for the documentation. - */ -const SphinxHighlight = { - - /** - * highlight the search words provided in localstorage in the text - */ - highlightSearchWords: () => { - if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight - - // get and clear terms from localstorage - const url = new URL(window.location); - const highlight = - localStorage.getItem("sphinx_highlight_terms") - || url.searchParams.get("highlight") - || ""; - localStorage.removeItem("sphinx_highlight_terms") - url.searchParams.delete("highlight"); - window.history.replaceState({}, "", url); - - // get individual terms from highlight string - const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); - if (terms.length === 0) return; // nothing to do - - // There should never be more than one element matching "div.body" - const divBody = document.querySelectorAll("div.body"); - const body = divBody.length ? divBody[0] : document.querySelector("body"); - window.setTimeout(() => { - terms.forEach((term) => _highlightText(body, term, "highlighted")); - }, 10); - - const searchBox = document.getElementById("searchbox"); - if (searchBox === null) return; - searchBox.appendChild( - document - .createRange() - .createContextualFragment( - '" - ) - ); - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords: () => { - document - .querySelectorAll("#searchbox .highlight-link") - .forEach((el) => el.remove()); - document - .querySelectorAll("span.highlighted") - .forEach((el) => el.classList.remove("highlighted")); - localStorage.removeItem("sphinx_highlight_terms") - }, - - initEscapeListener: () => { - // only install a listener if it is really needed - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; - if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { - SphinxHighlight.hideSearchWords(); - event.preventDefault(); - } - }); - }, -}; - -_ready(() => { - /* Do not call highlightSearchWords() when we are on the search page. - * It will highlight words from the *previous* search query. - */ - if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); - SphinxHighlight.initEscapeListener(); -}); diff --git a/html/de/_static/translations.js b/html/de/_static/translations.js deleted file mode 100644 index 2106c7d9..00000000 --- a/html/de/_static/translations.js +++ /dev/null @@ -1,60 +0,0 @@ -Documentation.addTranslations({ - "locale": "de", - "messages": { - "%(filename)s — %(docstitle)s": "", - "© %(copyright_prefix)s %(copyright)s.": "", - ", in ": ", in ", - "About these documents": "\u00dcber dieses Dokument", - "Automatically generated list of changes in version %(version)s": "Automatisch generierte Liste der \u00c4nderungen in Version %(version)s", - "C API changes": "C API-\u00c4nderungen", - "Changes in Version %(version)s — %(docstitle)s": "", - "Collapse sidebar": "Seitenleiste einklappen", - "Complete Table of Contents": "Vollst\u00e4ndiges Inhaltsverzeichnis", - "Contents": "Inhalt", - "Copyright": "Copyright", - "Created using Sphinx %(sphinx_version)s.": "", - "Expand sidebar": "Seitenleiste ausklappen", - "Full index on one page": "Gesamtes Stichwortverzeichnis auf einer Seite", - "General Index": "Stichwortverzeichnis", - "Global Module Index": "Globaler Modulindex", - "Go": "Los", - "Hide Search Matches": "Suchergebnisse ausblenden", - "Index": "Stichwortverzeichnis", - "Index – %(key)s": "Stichwortverzeichnis – %(key)s", - "Index pages by letter": "Stichwortverzeichnis nach Anfangsbuchstabe", - "Indices and tables:": "Verzeichnisse und Tabellen:", - "Last updated on %(last_updated)s.": "Zuletzt aktualisiert am %(last_updated)s.", - "Library changes": "Bibliotheks-\u00c4nderungen", - "Navigation": "Navigation", - "Next topic": "N\u00e4chstes Thema", - "Other changes": "Andere \u00c4nderungen", - "Overview": "\u00dcbersicht", - "Please activate JavaScript to enable the search\n functionality.": "Bitte aktivieren Sie JavaScript, wenn Sie die Suchfunktion nutzen wollen.", - "Preparing search...": "Suche wird vorbereitet...", - "Previous topic": "Vorheriges Thema", - "Quick search": "Schnellsuche", - "Search": "Suche", - "Search Page": "Suche", - "Search Results": "Suchergebnisse", - "Search finished, found ${resultCount} page(s) matching the search query.": "", - "Search within %(docstitle)s": "Suche in %(docstitle)s", - "Searching": "Suchen", - "Searching for multiple words only shows matches that contain\n all words.": "", - "Show Source": "Quellcode anzeigen", - "Table of Contents": "Inhaltsverzeichnis", - "This Page": "Diese Seite", - "Welcome! This is": "Willkommen! Dies ist", - "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.": "Ihre Suche ergab keine Treffer. Bitte stellen Sie sicher, dass alle W\u00f6rter richtig geschrieben sind und gen\u00fcgend Kategorien ausgew\u00e4hlt sind.", - "all functions, classes, terms": "alle Funktionen, Klassen, Begriffe", - "can be huge": "kann gro\u00df sein", - "last updated": "zuletzt aktualisiert", - "lists all sections and subsections": "Liste aller Kapitel und Unterkapitel", - "next chapter": "n\u00e4chstes Kapitel", - "previous chapter": "vorheriges Kapitel", - "quick access to all modules": "schneller Zugriff auf alle Module", - "search": "suchen", - "search this documentation": "durchsuche diese Dokumentation", - "the documentation for": "die Dokumentation f\u00fcr" - }, - "plural_expr": "(n != 1)" -}); \ No newline at end of file diff --git a/html/de/api.html b/html/de/api.html deleted file mode 100644 index 2cfa3de5..00000000 --- a/html/de/api.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - API Documentation — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

API Documentation

- -
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/client_api.html b/html/de/client_api.html deleted file mode 100644 index 9aaf16cc..00000000 --- a/html/de/client_api.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - Client API — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Client API

- -
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/client_errors.html b/html/de/client_errors.html deleted file mode 100644 index 273f2118..00000000 --- a/html/de/client_errors.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - - Errors — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Errors

-
-
They are 4 custom errors describing the reasons why the queries fail and what to do:
    -
  1. InvalidQueryException

  2. -
  3. ExternalLibraryException

  4. -
  5. UnauthorizedAccessException

  6. -
  7. InternalServerException

  8. -
-
-
-
-
-

InvalidQueryException

-
    -
  • Happens when: error is due to an an invalid query from the user

  • -
  • Example: an „opendp“ pipeline which is not a measurement, not enough budget for query

  • -
  • What to do: Based on the specific error message, the user should check and fix their relevant query parameters.

  • -
-
-
-

ExternalLibraryException

-
    -
  • Happens when: error comes from an external differential privacy library („smartnoise-sql“ or „opendp“).

  • -
  • Example: invalid values in Smartnoise-SQL mechanisms parameter.

  • -
  • What to do: Based on the specific error message, the user should check and fix their „smartnoise-sql“ query parameter or „opendp“ pipeline parameter.

  • -
-
-
-

UnauthorizedAccessException

-
    -
  • -
    Happens when: user tries to query a dataset without sufficient authorisation:
      -
    • when the user does not exist,

    • -
    • when the user does not have access to the dataset,

    • -
    • when the user may not query.

    • -
    -
    -
    -
  • -
  • Example: user tries to query a dataframe to which they do not have access

  • -
  • What to do: The user should decrease the budget parameters and check their access rights.

  • -
-
-
-

InternalServerException

-
    -
  • Happens when: there are internal issues within the server.

  • -
  • Example: failed startup of the server

  • -
  • What to do: contact the administrative team for help. Users are not responsible for the failure of their request.

  • -
-

This error should never occur while the server is deployed.

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/client_examples.html b/html/de/client_examples.html deleted file mode 100644 index 692d0bfb..00000000 --- a/html/de/client_examples.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - Examples — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Examples

-

In this section, you will find various examples demonstrating how to use Lomas-client in different -scenarios. These examples will help you understand the practical applications and features of lomas.

-

You can also find additional notebooks and examples in our -GitHub repository.

- -

Here are some examples for using Lomas-client.

-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/client_modules.html b/html/de/client_modules.html deleted file mode 100644 index 7490b42d..00000000 --- a/html/de/client_modules.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - lomas_client — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/client_quickstart.html b/html/de/client_quickstart.html deleted file mode 100644 index 4b3885ab..00000000 --- a/html/de/client_quickstart.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - Quickstart — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Quickstart

-

This is the quickstart guide for Lomas-client, providing initial setup and usage instructions.

-
-

Client

-
-

Installation

-

To install lomas-client, follow these steps:

-
    -
  1. Open a terminal.

  2. -
  3. Run the following command:

  4. -
-
pip install lomas-client
-
-
-
    -
  1. You’re all set!

  2. -
-
-
-

First steps

-

To use FSO lomas client, you can do the following:

-
    -
  1. Import the library in your Python code.

  2. -
  3. Initialise the client with required url, name and dataset.

  4. -
  5. You can now use any function as long as you have access to the dataset!

    -
    # Step 1
    -from lomas_client import Client
    -
    -# Step 2
    -APP_URL = "your_deployement_url"
    -USER_NAME = "your_name"
    -DATASET_NAME = "name_of_dataset_you_want_to_query"
    -client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)
    -
    -# Step 3
    -res = client.any_query(parameters)
    -
    -
    -
  6. -
-

with any_query being one of the function presented below and parameters being its associated parameters.

-
-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/genindex.html b/html/de/genindex.html deleted file mode 100644 index 3ed73911..00000000 --- a/html/de/genindex.html +++ /dev/null @@ -1,1752 +0,0 @@ - - - - - - Stichwortverzeichnis — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Stichwortverzeichnis

- -
- A - | B - | C - | D - | E - | F - | G - | H - | I - | J - | L - | M - | O - | P - | Q - | R - | S - | T - | U - | W - | Y - | Z - -
-

A

- - - -
- -

B

- - - -
- -

C

- - - -
- -

D

- - - -
- -

E

- - - -
- -

F

- - - -
- -

G

- - - -
- -

H

- - - -
- -

I

- - - -
- -

J

- - -
- -

L

- - - -
- -

M

- - - -
- -

O

- - - -
- -

P

- - - -
- -

Q

- - - -
- -

R

- - - -
- -

S

- - - -
- -

T

- - - -
- -

U

- - - -
- -

W

- - -
- -

Y

- - - -
- -

Z

- - -
- - - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/index.html b/html/de/index.html deleted file mode 100644 index 479b2214..00000000 --- a/html/de/index.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - Welcome to Lomas documentation — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Welcome to Lomas documentation

-

The lomas platform follows a classic server/client model. On the client side, the -user prepares queries for statistical analyses which are sent to the -service’s REST API via HTTP. The user never has direct access to the sensitive -data. On the server side, the service is implemented in a micro-service architecture and is -thus split into two parts: the administration database and the client-facing HTTP server -(which we call server for brevity) that implements the service logic. The server -is responsible for processing the client requests and updating its own state as -well as administrative data (users data, budgets, query archives, etc.) in -the administration database.

-

The service is not responsible for storing and managing private datasets, -these are usually already stored on the provider’s infrastructure.

-

You can find our GitHub repository -following this link.

-
-
-

Client

-

The lomas_client library is a client to interact with the Lomas server. It is available on -Pypi. Reasearcher and Data Scientists ‚using‘ the service to query the sensitive data will -only interact with the client and never with the server.

-

Utilizing this client library is strongly advised for querying and interacting with the -server, as it takes care of all the necessary tasks such as serialization, deserialization, -REST API calls, and ensures the correct installation of other required libraries. In short, -it enables a seamless interaction with the server.

-

For additional informations about the client, please see the -README.md of -the client and for additional examples please see the -examples section.

-
-
-

Server

-

The server side, implemented in a micro-service architecture, is composed of two main services:

-
    -
  • A client-facing HTTP server, that uses FastAPI for processing user requests and executing diverse queries.

  • -
-

Its primary function is to efficiently handle incoming requests from the client (user) and to execute the different -queries (SmartnoiseSQL, OpenDP, etc.).

-
    -
  • A MongoDB administration database to manage the server state. This database serves as a repository for user and metadata about the dataset. User-related data include access permissions to specific datasets, allocated budgets for each user, remaining budgets and queries executed so far by the user (that we also refer to as „archives“). Dataset-related data includes details such as dataset names, information and credentials for accessing the sensitive dataset (e.g., S3, local, HTTP), and references to associated metadata.

  • -
-

The server connects to external databases, typically deployed by a data owner, to download the -sensitive datasets for query execution. Currently, the server can manage adapters to S3, -http file download and local files.

-

For extensive informations about how to administrate the MongoDB database, -please refer to Administration section.

-

We aim to facilitate the platform configuration, deployment and testing on commonly available -IT infrastructure for NSOs and other potential users.

-

In this regard, we provide two Helm charts for deploying the server components (server and -MongoDB database) and a client development environment in a Kubernetes cluster.

-

For extensive informations about how to deploy, please refer to Deployment -documentation.

-
-
-

History

-

The starting point of our platform was the code shared to us by Oblivious.

-

They originally developed a client/server platform for the UN PET Lab Hackathon 2022.

-
-
-
-
-
-
-
-
-
-
-

Indices and tables

- -
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_client.html b/html/de/lomas_client.html deleted file mode 100644 index ce6608df..00000000 --- a/html/de/lomas_client.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - - - - lomas_client package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_client package

-
-

Submodules

-
-
-

lomas_client.client module

-
-
-class lomas_client.client.Client(url: str, user_name: str, dataset_name: str)[Quellcode]
-

Bases: object

-

Client class to send requests to the server -Handle all serialisation and deserialisation steps

-
-
-estimate_opendp_cost(opendp_pipeline: Measurement, fixed_delta: float | None = None) dict[str, float] | None[Quellcode]
-

This function estimates the cost of executing an OpenDP query.

-
-
Parameter:
-
    -
  • opendp_pipeline (dp.Measurement) – The OpenDP pipeline for the query.

  • -
  • fixed_delta (Optional[float], optional) – If the pipeline measurement is of type “ZeroConcentratedDivergence” (e.g. with make_gaussian) then it is converted to “SmoothedMaxDivergence” with make_zCDP_to_approxDP (See Smartnoise-SQL postprocessing documentation.). In that case a fixed_delta must be provided by the user. Defaults to None.

  • -
-
-
Rückgabe:
-

A dictionary containing the estimated cost.

-
-
Rückgabetyp:
-

Optional[dict[str, float]]

-
-
-
- -
-
-estimate_smartnoise_cost(query: str, epsilon: float, delta: float, mechanisms: dict[str, str] = {}) dict[str, float] | None[Quellcode]
-

This function estimates the cost of executing a SmartNoise query.

-
-
Parameter:
-
    -
  • query (str) – The SQL query to estimate the cost for. NOTE: the table name is df, the query must end with “FROM df”.

  • -
  • epsilon (float) – Privacy parameter (e.g., 0.1).

  • -
  • delta (float) – Privacy parameter (e.g., 1e-5). -mechanisms (dict[str, str], optional): Dictionary of mechanisms for the query See Smartnoise-SQL postprocessing documentation. -Defaults to {}.

  • -
-
-
Rückgabe:
-

A dictionary containing the estimated cost.

-
-
Rückgabetyp:
-

Optional[dict[str, float]]

-
-
-
- -
-
-get_dataset_metadata() Dict[str, int | bool | Dict[str, str | int]] | None[Quellcode]
-

This function retrieves metadata for the dataset.

-
-
Rückgabe:
-

A dictionary containing dataset metadata.

-
-
Rückgabetyp:
-

Optional[Dict[str, Union[int, bool, Dict[str, Union[str, int]]]]]

-
-
-
- -
-
-get_dummy_dataset(nb_rows: int = 100, seed: int = 42) DataFrame | None[Quellcode]
-

This function retrieves a dummy dataset with optional parameters.

-
-
Parameter:
-
    -
  • nb_rows (int, optional) –

    The number of rows in the dummy dataset.

    -

    Defaults to DUMMY_NB_ROWS.

    -

  • -
  • seed (int, optional) –

    The random seed for generating the dummy dataset.

    -

    Defaults to DUMMY_SEED.

    -

  • -
-
-
Rückgabe:
-

A Pandas DataFrame representing the dummy dataset.

-
-
Rückgabetyp:
-

Optional[pd.DataFrame]

-
-
-
- -
-
-get_initial_budget() dict[str, float] | None[Quellcode]
-

This function retrieves the initial budget.

-
-
Rückgabe:
-

A dictionary containing the initial budget.

-
-
Rückgabetyp:
-

Optional[dict[str, float]]

-
-
-
- -
-
-get_previous_queries() List[dict] | None[Quellcode]
-

This function retrieves the previous queries of the user.

-
-
Verursacht:
-

ValueError – If an unknown query type is encountered during deserialization.

-
-
Rückgabe:
-

A list of dictionary containing the different queries -on the private dataset.

-
-
Rückgabetyp:
-

Optional[List[dict]]

-
-
-
- -
-
-get_remaining_budget() dict[str, float] | None[Quellcode]
-

This function retrieves the remaining budget.

-
-
Rückgabe:
-

A dictionary containing the remaining budget.

-
-
Rückgabetyp:
-

Optional[dict[str, float]]

-
-
-
- -
-
-get_total_spent_budget() dict[str, float] | None[Quellcode]
-

This function retrieves the total spent budget.

-
-
Rückgabe:
-

A dictionary containing the total spent budget.

-
-
Rückgabetyp:
-

Optional[dict[str, float]]

-
-
-
- -
-
-opendp_query(opendp_pipeline: Measurement, fixed_delta: float | None = None, dummy: bool = False, nb_rows: int = 100, seed: int = 42) dict | None[Quellcode]
-

This function executes an OpenDP query.

-
-
Parameter:
-
    -
  • opendp_pipeline (dp.Measurement) – The OpenDP pipeline for the query.

  • -
  • fixed_delta (Optional[float], optional) – If the pipeline measurement is of type “ZeroConcentratedDivergence” (e.g. with make_gaussian) then it is converted to “SmoothedMaxDivergence” with make_zCDP_to_approxDP (See Smartnoise-SQL postprocessing documentation.). -In that case a fixed_delta must be provided by the user. -Defaults to None.

  • -
  • dummy (bool, optional) – Whether to use a dummy dataset. Defaults to False.

  • -
  • nb_rows (int, optional) – The number of rows in the dummy dataset. Defaults to DUMMY_NB_ROWS.

  • -
  • seed (int, optional) – The random seed for generating the dummy dataset. Defaults to DUMMY_SEED.

  • -
-
-
Verursacht:
-

Exception – If the server returns dataframes

-
-
Rückgabe:
-

A Pandas DataFrame containing the query results.

-
-
Rückgabetyp:
-

Optional[dict]

-
-
-
- -
-
-smartnoise_query(query: str, epsilon: float, delta: float, mechanisms: dict[str, str] = {}, postprocess: bool = True, dummy: bool = False, nb_rows: int = 100, seed: int = 42) dict | None[Quellcode]
-

This function executes a SmartNoise query.

-
-
Parameter:
-
    -
  • query (str) – The SQL query to execute. -NOTE: the table name is df, the query must end with “FROM df”.

  • -
  • epsilon (float) – Privacy parameter (e.g., 0.1).

  • -
  • delta (float) – Privacy parameter (e.g., 1e-5).

  • -
  • mechanisms (dict[str, str], optional) –

    Dictionary of mechanisms for the query See Smartnoise-SQL postprocessing documentation.

    -

    Defaults to {}.

    -

  • -
  • postprocess (bool, optional) –

    Whether to postprocess the query results. See Smartnoise-SQL postprocessing documentation.

    -

    Defaults to True.

    -

  • -
  • dummy (bool, optional) –

    Whether to use a dummy dataset.

    -

    Defaults to False.

    -

  • -
  • nb_rows (int, optional) –

    The number of rows in the dummy dataset.

    -

    Defaults to DUMMY_NB_ROWS.

    -

  • -
  • seed (int, optional) –

    The random seed for generating the dummy dataset.

    -

    Defaults to DUMMY_SEED.

    -

  • -
-
-
Rückgabe:
-

A Pandas DataFrame containing the query results.

-
-
Rückgabetyp:
-

Optional[dict]

-
-
-
- -
- -
-
-class lomas_client.client.DPLibraries(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[Quellcode]
-

Bases: StrEnum

-

Enum of the DP librairies used in the server -WARNING: MUST match those of lomas_server

-
-
-OPENDP = 'opendp'
-
- -
-
-SMARTNOISE_SQL = 'smartnoise_sql'
-
- -
- -
-
-lomas_client.client.error_message(res: Response) str[Quellcode]
-

Generates an error message based on the HTTP response.

-
-
Parameter:
-

res (requests.Response) – The response object from an HTTP request.

-
-
Rückgabe:
-

-
A formatted string describing the server error,

including the status code and response text.

-
-
-

-
-
Rückgabetyp:
-

str

-
-
-
- -
-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_server.admin_database.html b/html/de/lomas_server.admin_database.html deleted file mode 100644 index bc1e9793..00000000 --- a/html/de/lomas_server.admin_database.html +++ /dev/null @@ -1,1221 +0,0 @@ - - - - - - - lomas_server.admin_database package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server.admin_database package

-
-

Submodules

-
-
-

lomas_server.admin_database.admin_database module

-
-
-class lomas_server.admin_database.admin_database.AdminDatabase(**connection_parameters: Dict[str, str])[Quellcode]
-

Bases: ABC

-

Overall database management for server state.

-

This is an abstract class.

-
-
-abstract does_dataset_exist(dataset_name: str) bool[Quellcode]
-

Checks if dataset exist in the database

-
-
Parameter:
-

dataset_name (str) – name of the dataset to check

-
-
Rückgabe:
-

True if the dataset exists, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-abstract does_user_exist(user_name: str) bool[Quellcode]
-

Checks if user exist in the database

-
-
Parameter:
-

user_name (str) – name of the user to check

-
-
Rückgabe:
-

True if the user exists, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-abstract get_and_set_may_user_query(user_name: str, may_query: bool) bool[Quellcode]
-

Atomic operation to check and set if the user may query the server.

-

(Set False before querying and True after updating budget)

-

Wrapped by user_must_exist().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • may_query (bool) – flag give or remove access to user

  • -
-
-
Rückgabe:
-

The may_query status of the user before the update.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-abstract get_dataset_field(dataset_name: str, key: str) str[Quellcode]
-

Get dataset field type based on dataset name and key

-

Wrapped by dataset_must_exist().

-
-
Parameter:
-
    -
  • dataset_name (str) – Name of the dataset.

  • -
  • key (str) – Key for the value to get in the dataset dict.

  • -
-
-
Rückgabe:
-

The requested value.

-
-
Rückgabetyp:
-

str

-
-
-
- -
-
-abstract get_dataset_metadata(dataset_name: str) dict[Quellcode]
-

Returns the metadata dictionnary of the dataset.

-

Wrapped by dataset_must_exist().

-
-
Parameter:
-

dataset_name (str) – name of the dataset to get the metadata

-
-
Rückgabe:
-

The metadata dict.

-
-
Rückgabetyp:
-

dict

-
-
-
- -
-
-abstract get_epsilon_or_delta(user_name: str, dataset_name: str, parameter: str) float[Quellcode]
-

Get the total spent epsilon or delta by a specific user -on a specific dataset

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
  • parameter (str) – total_spent_epsilon or total_spent_delta

  • -
-
-
Rückgabe:
-

The requested budget value.

-
-
Rückgabetyp:
-

float

-
-
-
- -
-
-get_initial_budget(user_name: str, dataset_name: str) List[float][Quellcode]
-

Get the initial epsilon and delta budget

-

Wrapped by user_must_have_access_to_dataset().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
-
-
Rückgabe:
-

-
The first value of the list is the epsilon value,

the second value is the delta value.

-
-
-

-
-
Rückgabetyp:
-

List[float]

-
-
-
- -
-
-get_remaining_budget(user_name: str, dataset_name: str) List[float][Quellcode]
-

Get the remaining epsilon and delta budget (initial - total spent)

-

Wrapped by user_must_have_access_to_dataset().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
-
-
Rückgabe:
-

-
The first value of the list is the epsilon value,

the second value is the delta value.

-
-
-

-
-
Rückgabetyp:
-

List[float]

-
-
-
- -
-
-get_total_spent_budget(user_name: str, dataset_name: str) List[float][Quellcode]
-

Get the total spent epsilon and delta spent by a specific user -on a specific dataset (since the initialisation)

-

Wrapped by user_must_have_access_to_dataset().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
-
-
Rückgabe:
-

-
The first value of the list is the epsilon value,

the second value is the delta value.

-
-
-

-
-
Rückgabetyp:
-

List[float]

-
-
-
- -
-
-abstract get_user_previous_queries(user_name: str, dataset_name: str) List[dict][Quellcode]
-

Retrieves and return the queries already done by a user

-

Wrapped by user_must_have_access_to_dataset().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
-
-
Rückgabe:
-

List of previous queries.

-
-
Rückgabetyp:
-

List[dict]

-
-
-
- -
-
-abstract has_user_access_to_dataset(user_name: str, dataset_name: str) bool[Quellcode]
-

Checks if a user may access a particular dataset

-

Wrapped by user_must_exist().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
-
-
Rückgabe:
-

True if the user has access, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-abstract may_user_query(user_name: str) bool[Quellcode]
-

Checks if a user may query the server. -Cannot query if already querying.

-

Wrapped by user_must_exist().

-
-
Parameter:
-

user_name (str) – name of the user

-
-
Rückgabe:
-

True if the user exists, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-prepare_save_query(user_name: str, query_json: dict, response: dict) dict[Quellcode]
-

Prepare the query to save in archives

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • query_json (dict) – json received from client

  • -
  • response (dict) – response sent to the client

  • -
-
-
Verursacht:
-

InternalServerException – If the type of query is unknown.

-
-
Rückgabe:
-

The query archive dictionary.

-
-
Rückgabetyp:
-

dict

-
-
-
- -
-
-abstract save_query(user_name: str, query_json: dict, response: dict) None[Quellcode]
-

Save queries of user on datasets in a separate collection (table) -named „queries_archives“ in the DB

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • query_json (dict) – json received from client

  • -
  • response (dict) – response sent to the client

  • -
-
-
-
- -
-
-abstract set_may_user_query(user_name: str, may_query: bool) bool[Quellcode]
-

Sets if a user may query the server..

-

(Set False before querying and True after updating budget)

-

Wrapped by user_must_exist().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • may_query (bool) – flag give or remove access to user

  • -
-
-
-
- -
-
-update_budget(user_name: str, dataset_name: str, spent_epsilon: float, spent_delta: float) None[Quellcode]
-

Update the current epsilon and delta spent by a specific user -with the last spent delta

-

Wrapped by user_must_have_access_to_dataset().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
  • spent_epsilon (float) – value of epsilon spent on last query

  • -
  • spent_delta (float) – value of delta spent on last query

  • -
-
-
-
- -
-
-update_delta(user_name: str, dataset_name: str, spent_delta: float) None[Quellcode]
-

Update the spent delta spent by a specific user -with the total spent delta of the user

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
  • spent_delta (float) – value of delta spent on last query

  • -
-
-
-
- -
-
-update_epsilon(user_name: str, dataset_name: str, spent_epsilon: float) None[Quellcode]
-

Update the spent epsilon by a specific user -with the total spent epsilon

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
  • spent_epsilon (float) – value of epsilon spent on last query

  • -
-
-
-
- -
-
-abstract update_epsilon_or_delta(user_name: str, dataset_name: str, parameter: str, spent_value: float) None[Quellcode]
-

Update the current budget spent by a specific user -with the last spent budget.

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
  • parameter (str) – „current_epsilon“ or „current_delta“

  • -
  • spent_value (float) – spending of epsilon or delta on last query

  • -
-
-
-
- -
- -
-
-lomas_server.admin_database.admin_database.dataset_must_exist(func: Callable) Callable[Quellcode]
-

Decorator function to verify that a dataset exists.

-
-
Parameter:
-

func (Callable) – Function to be decorated. -Wrapped function arguments must include: -- args[0] (str): dataset name

-
-
Verursacht:
-

InvalidQueryException – If the dataset does not exist.

-
-
Rückgabe:
-

-
Wrapper function that checks if the dataset exists

before calling the wrapped function.

-
-
-

-
-
Rückgabetyp:
-

Callable

-
-
-
- -
-
-lomas_server.admin_database.admin_database.user_must_exist(func: Callable) Callable[Quellcode]
-

Decorator function to verify that a user exists.

-
-
Parameter:
-

func (Callable) – Function to be decorated. -Wrapped function arguments must include: -- args[0] (str): username

-
-
Verursacht:
-

UnauthorizedAccessException – If the user does not exist.

-
-
Rückgabe:
-

-
Wrapper function that verifies the user exists

before calling func.

-
-
-

-
-
Rückgabetyp:
-

Callable

-
-
-
- -
-
-lomas_server.admin_database.admin_database.user_must_have_access_to_dataset(func: Callable) Callable[Quellcode]
-

Decorator function to enforce a user has access to a dataset

-
-
Parameter:
-

func (Callable) – Function to be decorated. -Wrapped function arguments must include: -- args[0] (str): user name -- args[1] (str): dataset name

-
-
Verursacht:
-

UnauthorizedAccessException – If the user does not have - access to the dataset.

-
-
Rückgabe:
-

-
Wrapper function that checks if the user has access

to the dataset before calling the wrapped function.

-
-
-

-
-
Rückgabetyp:
-

Callable

-
-
-
- -
-
-

lomas_server.admin_database.mongodb_database module

-
-
-class lomas_server.admin_database.mongodb_database.AdminMongoDatabase(connection_string: str, database_name: str)[Quellcode]
-

Bases: AdminDatabase

-

Overall MongoDB database management for server state.

-
-
-does_dataset_exist(dataset_name: str) bool[Quellcode]
-

Checks if dataset exist in the database

-
-
Parameter:
-

dataset_name (str) – name of the dataset to check

-
-
Rückgabe:
-

True if the dataset exists, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-does_user_exist(user_name: str) bool[Quellcode]
-

Checks if user exist in the database

-
-
Parameter:
-

user_name (str) – name of the user to check

-
-
Rückgabe:
-

True if the user exists, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-get_and_set_may_user_query(user_name: str, may_query: bool) bool[Quellcode]
-

Atomic operation to check and set if the user may query the server.

-

(Set False before querying and True after updating budget)

-

Wrapped by user_must_exist().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • may_query (bool) – flag give or remove access to user

  • -
-
-
Rückgabe:
-

The may_query status of the user before the update.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-get_dataset_field(dataset_name: str, key: str) str[Quellcode]
-

Get dataset field type based on dataset name and key

-

Wrapped by dataset_must_exist().

-
-
Parameter:
-
    -
  • dataset_name (str) – Name of the dataset.

  • -
  • key (str) – Key for the value to get in the dataset dict.

  • -
-
-
Rückgabe:
-

The requested value.

-
-
Rückgabetyp:
-

str

-
-
-
- -
-
-get_dataset_metadata(dataset_name: str) dict[Quellcode]
-

Returns the metadata dictionnary of the dataset.

-

Wrapped by dataset_must_exist().

-
-
Parameter:
-

dataset_name (str) – name of the dataset to get the metadata

-
-
Rückgabe:
-

The metadata dict.

-
-
Rückgabetyp:
-

dict

-
-
-
- -
-
-get_epsilon_or_delta(user_name: str, dataset_name: str, parameter: str) float[Quellcode]
-

Get the total spent epsilon or delta by a specific user -on a specific dataset

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
  • parameter (str) – total_spent_epsilon or total_spent_delta

  • -
-
-
Rückgabe:
-

The requested budget value.

-
-
Rückgabetyp:
-

float

-
-
-
- -
-
-get_user_previous_queries(user_name: str, dataset_name: str) List[dict][Quellcode]
-

Retrieves and return the queries already done by a user

-

Wrapped by user_must_have_access_to_dataset().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
-
-
Rückgabe:
-

List of previous queries.

-
-
Rückgabetyp:
-

List[dict]

-
-
-
- -
-
-has_user_access_to_dataset(user_name: str, dataset_name: str) bool[Quellcode]
-

Checks if a user may access a particular dataset

-

Wrapped by user_must_exist().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
-
-
Rückgabe:
-

True if the user has access, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-may_user_query(user_name: str) bool[Quellcode]
-

Checks if a user may query the server. -Cannot query if already querying.

-

Wrapped by user_must_exist().

-
-
Parameter:
-

user_name (str) – name of the user

-
-
Rückgabe:
-

True if the user exists, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-save_query(user_name: str, query_json: dict, response: dict) None[Quellcode]
-

Save queries of user on datasets in a separate collection (table) -named „queries_archives“ in the DB

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • query_json (dict) – json received from client

  • -
  • response (dict) – response sent to the client

  • -
-
-
Verursacht:
-

WriteConcernError – If the result is not acknowledged.

-
-
-
- -
-
-set_may_user_query(user_name: str, may_query: bool) None[Quellcode]
-

Sets if a user may query the server.

-

(Set False before querying and True after updating budget)

-

Wrapped by user_must_exist().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • may_query (bool) – flag give or remove access to user

  • -
-
-
Verursacht:
-

WriteConcernError – If the result is not acknowledged.

-
-
-
- -
-
-update_epsilon_or_delta(user_name: str, dataset_name: str, parameter: str, spent_value: float) None[Quellcode]
-

Update the current budget spent by a specific user -with the last spent budget.

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
  • parameter (str) – „current_epsilon“ or „current_delta“

  • -
  • spent_value (float) – spending of epsilon or delta on last query

  • -
-
-
Verursacht:
-

WriteConcernError – If the result is not acknowledged.

-
-
-
- -
- -
-
-lomas_server.admin_database.mongodb_database.check_result_acknowledged(res: _WriteResult) None[Quellcode]
-

Raises an exception if the result is not acknowledged.

-
-
Parameter:
-

res (_WriteResult) – The PyMongo WriteResult to check.

-
-
Verursacht:
-

WriteConcernError – If the result is not acknowledged.

-
-
-
- -
-
-

lomas_server.admin_database.utils module

-
-
-lomas_server.admin_database.utils.database_factory(config: DBConfig) AdminDatabase[Quellcode]
-

Instantiates and returns the correct database type described in the -provided config.

-
-
Parameter:
-

config (DBConfig) – An instance of DBconfig.

-
-
Verursacht:
-
-
-
Rückgabe:
-

A instance of the correct type of AdminDatabase.

-
-
Rückgabetyp:
-

AdminDatabase

-
-
-
- -
-
-lomas_server.admin_database.utils.get_mongodb() Database[Quellcode]
-

Get URL of the administration MongoDB.

-
-
Parameter:
-

config (DBConfig) – An instance of DBConfig.

-
-
Rückgabe:
-

-
A correctly formatted url for connecting to the

MongoDB database.

-
-
-

-
-
Rückgabetyp:
-

str

-
-
-
- -
-
-lomas_server.admin_database.utils.get_mongodb_url(config: DBConfig) str[Quellcode]
-

Get URL of the administration MongoDB.

-
-
Parameter:
-

config (DBConfig) – An instance of DBConfig.

-
-
Rückgabe:
-

-
A correctly formatted url for connecting to the

MongoDB database.

-
-
-

-
-
Rückgabetyp:
-

str

-
-
-
- -
-
-

lomas_server.admin_database.yaml_database module

-
-
-class lomas_server.admin_database.yaml_database.AdminYamlDatabase(yaml_db_path: str)[Quellcode]
-

Bases: AdminDatabase

-

Overall Yaml database management for server state

-
-
-does_dataset_exist(dataset_name: str) bool[Quellcode]
-

Checks if dataset exist in the database

-
-
Parameter:
-

dataset_name (str) – name of the dataset to check

-
-
Rückgabe:
-

True if the dataset exists, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-does_user_exist(user_name: str) bool[Quellcode]
-

Checks if user exist in the database

-
-
Parameter:
-

user_name (str) – name of the user to check

-
-
Rückgabe:
-

True if the user exists, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-get_and_set_may_user_query(user_name: str, may_query: bool) bool[Quellcode]
-

Atomic operation to check and set if the user may query the server.

-

(Set False before querying and True after updating budget)

-

Wrapped by user_must_exist().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • may_query (bool) – flag give or remove access to user

  • -
-
-
Rückgabe:
-

The may_query status of the user before the update.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-get_dataset_field(dataset_name: str, key: str) str[Quellcode]
-

Get dataset field type based on dataset name and key

-

Wrapped by dataset_must_exist().

-
-
Parameter:
-
    -
  • dataset_name (str) – Name of the dataset.

  • -
  • key (str) – Key for the value to get in the dataset dict.

  • -
-
-
Rückgabe:
-

The requested value.

-
-
Rückgabetyp:
-

str

-
-
-
- -
-
-get_dataset_metadata(dataset_name: str) dict[Quellcode]
-

Returns the metadata dictionnary of the dataset.

-

Wrapped by dataset_must_exist().

-
-
Parameter:
-

dataset_name (str) – name of the dataset to get the metadata

-
-
Rückgabe:
-

The metadata dict.

-
-
Rückgabetyp:
-

dict

-
-
-
- -
-
-get_epsilon_or_delta(user_name: str, dataset_name: str, parameter: str) float[Quellcode]
-

Get the total spent epsilon or delta by a specific user -on a specific dataset

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
  • parameter (str) – total_spent_epsilon or total_spent_delta

  • -
-
-
Rückgabe:
-

The requested budget value.

-
-
Rückgabetyp:
-

float

-
-
-
- -
-
-get_user_previous_queries(user_name: str, dataset_name: str) List[dict][Quellcode]
-

Retrieves and return the queries already done by a user

-

Wrapped by user_must_have_access_to_dataset().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
-
-
Rückgabe:
-

List of previous queries.

-
-
Rückgabetyp:
-

List[dict]

-
-
-
- -
-
-has_user_access_to_dataset(user_name: str, dataset_name: str) bool[Quellcode]
-

Checks if a user may access a particular dataset

-

Wrapped by user_must_exist().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
-
-
Rückgabe:
-

True if the user has access, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-may_user_query(user_name: str) bool[Quellcode]
-

Checks if a user may query the server. -Cannot query if already querying.

-

Wrapped by user_must_exist().

-
-
Parameter:
-

user_name (str) – name of the user

-
-
Rückgabe:
-

True if the user exists, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-save_current_database() None[Quellcode]
-

Saves the current database with updated parameters in new yaml -with the date and hour in the path -Might be useful to verify state of DB during development

-
- -
-
-save_query(user_name: str, query_json: dict, response: dict) None[Quellcode]
-

Save queries of user on datasets in a separate collection (table) -named „queries_archives“ in the DB

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • query_json (dict) – json received from client

  • -
  • response (dict) – response sent to the client

  • -
-
-
-
- -
-
-set_may_user_query(user_name: str, may_query: bool) None[Quellcode]
-

Sets if a user may query the server.

-

(Set False before querying and True after updating budget)

-

Wrapped by user_must_exist().

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • may_query (bool) – flag give or remove access to user

  • -
-
-
-
- -
-
-update_epsilon_or_delta(user_name: str, dataset_name: str, parameter: str, spent_value: float) None[Quellcode]
-

Update the current budget spent by a specific user -with the last spent budget.

-
-
Parameter:
-
    -
  • user_name (str) – name of the user

  • -
  • dataset_name (str) – name of the dataset

  • -
  • parameter (str) – „current_epsilon“ or „current_delta“

  • -
  • spent_value (float) – spending of epsilon or delta on last query

  • -
-
-
-
- -
- -
-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_server.administration.html b/html/de/lomas_server.administration.html deleted file mode 100644 index a6409d4a..00000000 --- a/html/de/lomas_server.administration.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - lomas_server.administration package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server.administration package

-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_server.dataset_store.html b/html/de/lomas_server.dataset_store.html deleted file mode 100644 index 42860310..00000000 --- a/html/de/lomas_server.dataset_store.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - - lomas_server.dataset_store package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server.dataset_store package

-
-

Submodules

-
-
-

lomas_server.dataset_store.basic_dataset_store module

-
-
-class lomas_server.dataset_store.basic_dataset_store.BasicDatasetStore(admin_database: AdminDatabase)[Quellcode]
-

Bases: DatasetStore

-

Basic implementation of the QuerierManager interface.

-

The queriers are initialized lazily and put into a dict. -There is no memory management => The manager will fail if the datasets are -too large to fit in memory.

-
-
-dp_queriers: Dict[str, Dict[str, DPQuerier]] = {}
-
- -
-
-get_querier(dataset_name: str, query_type: str) DPQuerier[Quellcode]
-

Returns the querier for the given dataset and library

-
-
Parameter:
-
    -
  • dataset_name (str) – The dataset name.

  • -
  • query_type (str) – The type of DP library. -One of constants.DPLibraries

  • -
-
-
Rückgabe:
-

The DPQuerier for the specified dataset and library.

-
-
Rückgabetyp:
-

DPQuerier

-
-
-
- -
- -
-
-

lomas_server.dataset_store.dataset_store module

-
-
-class lomas_server.dataset_store.dataset_store.DatasetStore(admin_database: AdminDatabase)[Quellcode]
-

Bases: ABC

-

Manages the DPQueriers for the different datasets and libraries

-

Holds a reference to the user database in order to get information -about users.

-

We make the _add_dataset function private to enforce lazy loading -of queriers.

-
-
-admin_database: AdminDatabase
-
- -
-
-abstract get_querier(dataset_name: str, library: str) DPQuerier[Quellcode]
-

Returns the querier for the given dataset and library

-
-
Parameter:
-
    -
  • dataset_name (str) – The dataset name.

  • -
  • library (str) – The type of DP library. -One of constants.DPLibraries

  • -
-
-
Rückgabe:
-

The DPQuerier for the specified dataset and library.

-
-
Rückgabetyp:
-

DPQuerier

-
-
-
- -
- -
-
-

lomas_server.dataset_store.lru_dataset_store module

-
-
-class lomas_server.dataset_store.lru_dataset_store.LRUDatasetStore(admin_database: AdminDatabase, max_memory_usage: int = 1024)[Quellcode]
-

Bases: DatasetStore, PrivateDatasetObserver

-

Implementation of the DatasetStore interface, with an LRU cache.

-

Subscribes to the PrivateDatasets to get notified if their memory usage -changes and then clears the cache accordingly in order stay below -the maximum memory usage.

-
-
-dataset_cache: OrderedDict[str, PrivateDataset]
-
- -
-
-get_querier(dataset_name: str, library: str) DPQuerier[Quellcode]
-

Returns the querier for the given dataset and library

-
-
Parameter:
-
    -
  • dataset_name (str) – The dataset name.

  • -
  • library (str) – The type of DP library. -One of constants.DPLibraries

  • -
-
-
Rückgabe:
-

The DPQuerier for the specified dataset and library.

-
-
Rückgabetyp:
-

DPQuerier

-
-
-
- -
-
-update_memory_usage() None[Quellcode]
-

Remove least recently used datasets until the cache -is back to below or equal to its maximum size.

-
- -
- -
-
-

lomas_server.dataset_store.private_dataset_observer module

-
-
-class lomas_server.dataset_store.private_dataset_observer.PrivateDatasetObserver[Quellcode]
-

Bases: ABC

-

We use this abstract class to „subscribe“ to object instances -(PrivateDataset) so that they can notify instances of this -abstract class (LRUDatasetStore or other DatasetStore implementing -caching) when their memory usage changes.

-
-
-abstract update_memory_usage() None[Quellcode]
-

Abstract method to update total memory used by datasets

-
- -
- -
-
-

lomas_server.dataset_store.utils module

-
-
-lomas_server.dataset_store.utils.dataset_store_factory(config: DatasetStoreConfig, admin_database: AdminDatabase) DatasetStore[Quellcode]
-

Instantiates and returns the correct DatasetStore based on the config.

-
-
Parameter:
-
-
-
Verursacht:
-

InternalServerException – If the dataset store type from the config - does not exist.

-
-
Rückgabe:
-

The correct DatasetStore instance.

-
-
Rückgabetyp:
-

DatasetStore

-
-
-
- -
-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_server.dp_queries.dp_libraries.html b/html/de/lomas_server.dp_queries.dp_libraries.html deleted file mode 100644 index 6c03d65b..00000000 --- a/html/de/lomas_server.dp_queries.dp_libraries.html +++ /dev/null @@ -1,410 +0,0 @@ - - - - - - - lomas_server.dp_queries.dp_libraries package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server.dp_queries.dp_libraries package

-
-

Submodules

-
-
-

lomas_server.dp_queries.dp_libraries.opendp module

-
-
-class lomas_server.dp_queries.dp_libraries.opendp.OpenDPQuerier(private_dataset: PrivateDataset)[Quellcode]
-

Bases: DPQuerier

-

Concrete implementation of the DPQuerier ABC for the OpenDP library.

-
-
-cost(query_json: OpenDPInp) tuple[float, float][Quellcode]
-

Estimate cost of query

-
-
Parameter:
-

query_json (BaseModel) – The JSON request object for the query.

-
-
Verursacht:
-
-
-
Rückgabe:
-

-
The tuple of costs, the first value

is the epsilon cost, the second value is the delta value.

-
-
-

-
-
Rückgabetyp:
-

tuple[float, float]

-
-
-
- -
-
-query(query_json: OpenDPInp) str[Quellcode]
-

Perform the query and return the response.

-
-
Parameter:
-

query_json (BaseModel) – The JSON request object for the query.

-
-
Verursacht:
-

ExternalLibraryException – For exceptions from libraries - external to this package.

-
-
Rückgabe:
-

TODO Check this, probably float or int. -str: The JSON encoded string representation of the query result.

-
-
-
- -
- -
-
-lomas_server.dp_queries.dp_libraries.opendp.get_output_measure(opendp_pipe: Measurement) str[Quellcode]
-

Get output measure type.

-
-
Parameter:
-

opendp_pipe (dp.Measurement) – Pipeline to get measure type.

-
-
Verursacht:
-

InternalServerException – If the measure type is unknown.

-
-
Rückgabe:
-

One of OpenDPMeasurement.

-
-
Rückgabetyp:
-

str

-
-
-
- -
-
-lomas_server.dp_queries.dp_libraries.opendp.is_measurement(value: Measurement) bool[Quellcode]
-

Check if the value is a measurement.

-
-
Parameter:
-

value (dp.Measurement) – The measurement to check.

-
-
Rückgabe:
-

True if the value is a measurement, False otherwise.

-
-
Rückgabetyp:
-

bool

-
-
-
- -
-
-lomas_server.dp_queries.dp_libraries.opendp.reconstruct_measurement_pipeline(pipeline: str) Measurement[Quellcode]
-

Reconstruct OpenDP pipeline from json representation.

-
-
Parameter:
-

pipeline (str) – The JSON string encoding of the pipeline.

-
-
Verursacht:
-

InvalidQueryException – If the pipeline is not a measurement.

-
-
Rückgabe:
-

The reconstructed pipeline.

-
-
Rückgabetyp:
-

dp.Measurement

-
-
-
- -
-
-

lomas_server.dp_queries.dp_libraries.smartnoise_sql module

-
-
-class lomas_server.dp_queries.dp_libraries.smartnoise_sql.SmartnoiseSQLQuerier(private_dataset: PrivateDataset)[Quellcode]
-

Bases: DPQuerier

-

Concrete implementation of the DPQuerier ABC for the SmartNoiseSQL library.

-
-
-cost(query_json: SNSQLInpCost) tuple[float, float][Quellcode]
-

Estimate cost of query

-
-
Parameter:
-

query_json (BaseModel) – The JSON request object for the query.

-
-
Verursacht:
-

ExternalLibraryException – For exceptions from libraries - external to this package.

-
-
Rückgabe:
-

-
The tuple of costs, the first value

is the epsilon cost, the second value is the delta value.

-
-
-

-
-
Rückgabetyp:
-

tuple[float, float]

-
-
-
- -
-
-query(query_json: SNSQLInp, nb_iter: int = 0) dict[Quellcode]
-

Perform the query and return the response.

-
-
Parameter:
-
    -
  • query_json (BaseModel) – The JSON request object for the query.

  • -
  • nb_iter (int, optional) – Number of trials if output is Nan. -Defaults to 0.

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

The dictionary encoding of the resulting pd.DataFrame.

-
-
Rückgabetyp:
-

dict

-
-
-
- -
- -
-
-lomas_server.dp_queries.dp_libraries.smartnoise_sql.set_mechanisms(privacy: Privacy, mechanisms: dict[str, str]) Privacy[Quellcode]
-

Set privacy mechanisms on the Privacy object.

-

For more information see: -https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms

-
-
Parameter:
-
    -
  • privacy (Privacy) – Privacy object.

  • -
  • mechanisms (dict[str, str]) – Mechanisms to set.

  • -
-
-
Rückgabe:
-

The updated Privacy object.

-
-
Rückgabetyp:
-

Privacy

-
-
-
- -
-
-

lomas_server.dp_queries.dp_libraries.utils module

-
-
-lomas_server.dp_queries.dp_libraries.utils.querier_factory(lib: str, private_dataset: PrivateDataset) DPQuerier[Quellcode]
-

Builds the correct DPQuerier instance.

-
-
Parameter:
-
    -
  • lib (str) – The library to build the querier for. -One of DPLibraries.

  • -
  • private_dataset (PrivateDataset) – The dataset to query.

  • -
-
-
Verursacht:
-

InternalServerException – If the library is unknown.

-
-
Rückgabe:
-

The built DPQuerier.

-
-
Rückgabetyp:
-

DPQuerier

-
-
-
- -
-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_server.dp_queries.html b/html/de/lomas_server.dp_queries.html deleted file mode 100644 index f1a8705b..00000000 --- a/html/de/lomas_server.dp_queries.html +++ /dev/null @@ -1,405 +0,0 @@ - - - - - - - lomas_server.dp_queries package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server.dp_queries package

-
-

Subpackages

- -
-
-

Submodules

-
-
-

lomas_server.dp_queries.dp_logic module

-
-
-class lomas_server.dp_queries.dp_logic.QueryHandler(admin_database: AdminDatabase, dataset_store: DatasetStore)[Quellcode]
-

Bases: object

-

Query handler for the server.

-

Holds a reference to the admin database and the DatasetStore.

-
-
-admin_database: AdminDatabase
-
- -
-
-dataset_store: DatasetStore
-
- -
-
-estimate_cost(query_type: str, query_json: BaseModel) dict[str, float][Quellcode]
-

Estimate query cost.

-
-
Parameter:
-
    -
  • query_type (str) – The type of DP library, -one of :py:class:DPLibraries`

  • -
  • query_json (BasicModel) – The JSON request object for the query.

  • -
-
-
Rückgabe:
-

-
Dictionary containing:
    -
  • epsilon_cost (float): The estimated epsilon cost.

  • -
  • delta_cost (float): The estimated delta cost.

  • -
-
-
-

-
-
Rückgabetyp:
-

dict[str, float]

-
-
-
- -
-
-handle_query(query_type: str, query_json: BaseModel, user_name: str) dict[Quellcode]
-

Handle DP query.

-
-
Parameter:
-
    -
  • query_type (str) – The type of DP library, -one of :py:class:DPLibraries`

  • -
  • query_json (BasicModel) – The JSON request object for the query.

  • -
  • user_name (str, optional) – User name.

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
A dictionary containing:
    -
  • requested_by (str): The user name.

  • -
  • query_response (pd.DataFrame): A DataFrame containing -the query response.

  • -
  • spent_epsilon (float): The amount of epsilon budget spent

  • -
-

for the query. -- spent_delta (float): The amount of delta budget spent

-
-

for the query.

-
-
-
-

-
-
Rückgabetyp:
-

dict

-
-
-
- -
- -
-
-

lomas_server.dp_queries.dp_querier module

-
-
-class lomas_server.dp_queries.dp_querier.DPQuerier(private_dataset: PrivateDataset)[Quellcode]
-

Bases: ABC

-

Abstract Base Class for Queriers to external DP library.

-

A querier type is specific to a DP library and -a querier instance is specific to a PrivateDataset instance.

-
-
-abstract cost(query_json: BaseModel) tuple[float, float][Quellcode]
-

Estimate cost of query.

-
-
Parameter:
-

query_json (BaseModel) – The JSON request object for the query.

-
-
Rückgabe:
-

-
The tuple of costs, the first value is

the epsilon cost, the second value is the delta value.

-
-
-

-
-
Rückgabetyp:
-

tuple[float, float]

-
-
-
- -
-
-abstract query(query_json: BaseModel) str[Quellcode]
-

Perform the query and return the response.

-
-
Parameter:
-

query_json (BaseModel) – The JSON request object for the query.

-
-
Rückgabe:
-

TODO check this. -str: The JSON encoded string representation of the query result.

-
-
-
- -
- -
-
-

lomas_server.dp_queries.dummy_dataset module

-
-
-lomas_server.dp_queries.dummy_dataset.get_dummy_dataset_for_query(admin_database: AdminDatabase, query_json: GetDummyDataset) InMemoryDataset[Quellcode]
-

Get a dummy dataset for a given query.

-
-
Parameter:
-
    -
  • admin_database (AdminDatabase) – An initialized instance -of AdminDatabase.

  • -
  • query_json (GetDummyDataset) – The JSON request object for the query.

  • -
-
-
Rückgabe:
-

An in memory dummy dataset instance.

-
-
Rückgabetyp:
-

InMemoryDataset

-
-
-
- -
-
-lomas_server.dp_queries.dummy_dataset.make_dummy_dataset(metadata: dict, nb_rows: int = 100, seed: int = 42) DataFrame[Quellcode]
-

Create a dummy dataset based on a metadata dictionnary

-
-
Parameter:
-
    -
  • metadata (dict) – dictionnary of the metadata of the real dataset

  • -
  • nb_rows (int, optional) – _description_. Defaults to DUMMY_NB_ROWS.

  • -
  • seed (int, optional) – _description_. Defaults to DUMMY_SEED.

  • -
-
-
Verursacht:
-

InternalServerException – If any unknown column type occurs.

-
-
Rückgabe:
-

dummy dataframe based on metadata

-
-
Rückgabetyp:
-

pd.DataFrame

-
-
-
- -
-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_server.html b/html/de/lomas_server.html deleted file mode 100644 index eab4069f..00000000 --- a/html/de/lomas_server.html +++ /dev/null @@ -1,2018 +0,0 @@ - - - - - - - lomas_server package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server package

-
-

Subpackages

-
- -
-
-
-

Submodules

-
-
-

lomas_server.app module

-
-
-lomas_server.app.dummy_opendp_query_handler(_request: Request, query_json: DummyOpenDPInp = Body({'dataset_name': 'PENGUIN', 'opendp_json': '{"version": "0.8.0", "ast": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "constructor", "func": "make_chain_tt", "module": "combinators", "args": [{"_type": "constructor", "func": "make_select_column", "module": "transformations", "kwargs": {"key": "bill_length_mm", "TOA": "String"}}, {"_type": "constructor", "func": "make_split_dataframe", "module": "transformations", "kwargs": {"separator": ",", "col_names": {"_type": "list", "_items": ["species", "island", "bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g", "sex"]}}}]}, "rhs": {"_type": "constructor", "func": "then_cast_default", "module": "transformations", "kwargs": {"TOA": "f64"}}}, "rhs": {"_type": "constructor", "func": "then_clamp", "module": "transformations", "kwargs": {"bounds": [30.0, 65.0]}}}, "rhs": {"_type": "constructor", "func": "then_resize", "module": "transformations", "kwargs": {"size": 346, "constant": 43.61}}}, "rhs": {"_type": "constructor", "func": "then_variance", "module": "transformations"}}, "rhs": {"_type": "constructor", "func": "then_laplace", "module": "measurements", "kwargs": {"scale": 5.0}}}}', 'fixed_delta': 1e-06, 'dummy_nb_rows': 100, 'dummy_seed': 42})) JSONResponse[Quellcode]
-

Handles queries on dummy datasets for the OpenDP library.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object.

  • -
  • query_json (DummyOpenDPInp, optional) –

    A JSON object containing the following: -- opendp_pipeline: The OpenDP pipeline for the query. -- fixed_delta: If the pipeline measurement is of type „ZeroConcentratedDivergence“ (e.g. with „make_gaussian“) then

    -
    -

    it is converted to „SmoothedMaxDivergence“ with -„make_zCDP_to_approxDP“ (see opendp measurements documentation at -https://docs.opendp.org/en/stable/api/python/opendp.combinators.html#opendp.combinators.make_zCDP_to_approxDP). # noqa # pylint: disable=C0301 -In that case a „fixed_delta“ must be provided by the user.

    -
    -
      -
    • dummy (bool, optional): Whether to use a dummy dataset -(default: False).

    • -
    • nb_rows (int, optional): The number of rows -in the dummy dataset (default: 100).

    • -
    • seed (int, optional): The random seed for generating -the dummy dataset (default: 42).

    • -
    -

    Defaults to Body(example_dummy_opendp).

    -

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
A JSON object containing:
    -
  • query_response (pd.DataFrame): a DataFrame containing -the query response.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.dummy_smartnoise_sql_handler(_request: Request, query_json: DummySNSQLInp = Body({'query_str': 'SELECT COUNT(*) AS NB_ROW FROM df', 'dataset_name': 'PENGUIN', 'epsilon': 100.0, 'delta': 0.99, 'mechanisms': {'count': 'gaussian'}, 'postprocess': False, 'dummy_nb_rows': 100, 'dummy_seed': 42})) JSONResponse[Quellcode]
-

Handles queries on dummy datasets for the SmartNoiseSQL library.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (DummySNSQLInp, optional) –

    A JSON object containing: -- query: The SQL query to execute. NOTE: the table name is „df“,

    -
    -

    the query must end with „FROM df“.

    -
    -
      -
    • epsilon (float): Privacy parameter (e.g., 0.1).

    • -
    • delta (float): Privacy parameter (e.g., 1e-5).

    • -
    • mechanisms (dict, optional): Dictionary of mechanisms for the -query (default: {}). See Smartnoise-SQL mechanisms documentation -https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms.

    • -
    • postprocess (bool, optional): Whether to postprocess the query -results (default: True). -See Smartnoise-SQL postprocessing documentation -https://docs.smartnoise.org/sql/advanced.html#postprocess.

    • -
    • dummy (bool, optional): Whether to use a dummy dataset -(default: False).

    • -
    • nb_rows (int, optional): The number of rows in the dummy dataset -(default: 100).

    • -
    • seed (int, optional): The random seed for generating -the dummy dataset (default: 42).

    • -
    -

    Defaults to Body(example_dummy_smartnoise_sql).

    -

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
A JSON object containing:
    -
  • query_response (pd.DataFrame): a DataFrame containing -the query response.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.estimate_opendp_cost(_request: Request, query_json: OpenDPInp = Body({'dataset_name': 'PENGUIN', 'opendp_json': '{"version": "0.8.0", "ast": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "constructor", "func": "make_chain_tt", "module": "combinators", "args": [{"_type": "constructor", "func": "make_select_column", "module": "transformations", "kwargs": {"key": "bill_length_mm", "TOA": "String"}}, {"_type": "constructor", "func": "make_split_dataframe", "module": "transformations", "kwargs": {"separator": ",", "col_names": {"_type": "list", "_items": ["species", "island", "bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g", "sex"]}}}]}, "rhs": {"_type": "constructor", "func": "then_cast_default", "module": "transformations", "kwargs": {"TOA": "f64"}}}, "rhs": {"_type": "constructor", "func": "then_clamp", "module": "transformations", "kwargs": {"bounds": [30.0, 65.0]}}}, "rhs": {"_type": "constructor", "func": "then_resize", "module": "transformations", "kwargs": {"size": 346, "constant": 43.61}}}, "rhs": {"_type": "constructor", "func": "then_variance", "module": "transformations"}}, "rhs": {"_type": "constructor", "func": "then_laplace", "module": "measurements", "kwargs": {"scale": 5.0}}}}', 'fixed_delta': 1e-06})) JSONResponse[Quellcode]
-

Estimates the privacy loss budget cost of an OpenDP query.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (OpenDPInp, optional) –

    A JSON object containing the following: -- „opendp_pipeline“: The OpenDP pipeline for the query.

    -

    Defaults to Body(example_opendp).

    -

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
A JSON object containing:
    -
  • epsilon_cost (float): The estimated epsilon cost.

  • -
  • delta_cost (float): The estimated delta cost.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.estimate_smartnoise_cost(_request: Request, query_json: SNSQLInpCost = Body({'query_str': 'SELECT COUNT(*) AS NB_ROW FROM df', 'dataset_name': 'PENGUIN', 'epsilon': 0.1, 'delta': 1e-05, 'mechanisms': {'count': 'gaussian'}})) JSONResponse[Quellcode]
-

Estimates the privacy loss budget cost of a SmartNoiseSQL query.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (SNSQLInpCost, optional) –

    A JSON object containing the following: -- query: The SQL query to estimate the cost for.

    -
    -

    NOTE: the table name is „df“, the query must end with „FROM df“.

    -
    - -

    Defaults to Body(example_smartnoise_sql_cost).

    -

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
A JSON object containing:
    -
  • epsilon_cost (float): The estimated epsilon cost.

  • -
  • delta_cost (float): The estimated delta cost.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.get_dataset_metadata(_request: Request, query_json: GetDbData = Body({'dataset_name': 'PENGUIN'})) JSONResponse[Quellcode]
-

Retrieves metadata for a given dataset.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (GetDbData, optional) – A JSON object containing -the dataset_name key for indicating the dataset. -Defaults to Body(example_get_admin_db_data).

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
The metadata dictionary for the specified

dataset_name.

-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.get_dummy_dataset(_request: Request, query_json: GetDummyDataset = Body({'dataset_name': 'PENGUIN', 'dummy_nb_rows': 100, 'dummy_seed': 42})) StreamingResponse[Quellcode]
-

Generates and returns a dummy dataset.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (GetDummyDataset, optional) –

    -
    A JSON object containing the following:
      -
    • nb_rows (int, optional): The number of rows in the -dummy dataset (default: 100).

    • -
    • seed (int, optional): The random seed for generating -the dummy dataset (default: 42).

    • -
    -
    -
    -

    Defaults to Body(example_get_dummy_dataset).

    -

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

a pd.DataFrame representing the dummy dataset.

-
-
Rückgabetyp:
-

StreamingResponse

-
-
-
- -
-
-lomas_server.app.get_initial_budget(_request: Request, query_json: GetDbData = Body({'dataset_name': 'PENGUIN'}), user_name: str = Header(None)) JSONResponse[Quellcode]
-

Returns the initial budget for a user and dataset.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (GetDbData, optional) –

    A JSON object containing: -- dataset_name (str): The name of the dataset.

    -

    Defaults to Body(example_get_admin_db_data).

    -

  • -
  • user_name (str, optional) – The user name. -Defaults to Header(None).

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
a JSON object with:
    -
  • initial_epsilon (float): initial epsilon budget.

  • -
  • initial_delta (float): initial delta budget.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-async lomas_server.app.get_memory_usage() JSONResponse[Quellcode]
-

Return the dataset store object memory usage -:param user_name: The user name. Defaults to Header(None). -:type user_name: str, optional

-
-
Rückgabe:
-

with DatasetStore object memory usage

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.get_remaining_budget(_request: Request, query_json: GetDbData = Body({'dataset_name': 'PENGUIN'}), user_name: str = Header(None)) JSONResponse[Quellcode]
-

Returns the remaining budget for a user and dataset.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (GetDbData, optional) –

    A JSON object containing: -- dataset_name (str): The name of the dataset.

    -

    Defaults to Body(example_get_admin_db_data).

    -

  • -
  • user_name (str, optional) – The user name. -Defaults to Header(None).

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
a JSON object with:
    -
  • remaining_epsilon (float): remaining epsilon budget.

  • -
  • remaining_delta (float): remaining delta budget.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-async lomas_server.app.get_state(user_name: str = Header(None)) JSONResponse[Quellcode]
-

Returns the current state dict of this server instance.

-
-
Parameter:
-

user_name (str, optional) – The user name. Defaults to Header(None).

-
-
Rückgabe:
-

The state of the server instance.

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.get_total_spent_budget(_request: Request, query_json: GetDbData = Body({'dataset_name': 'PENGUIN'}), user_name: str = Header(None)) JSONResponse[Quellcode]
-

Returns the spent budget for a user and dataset.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (GetDbData, optional) –

    A JSON object containing: -- dataset_name (str): The name of the dataset.

    -

    Defaults to Body(example_get_admin_db_data).

    -

  • -
  • user_name (str, optional) – The user name. -Defaults to Header(None).

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
a JSON object with:
    -
  • total_spent_epsilon (float): total spent epsilon budget.

  • -
  • total_spent_delta (float): total spent delta budget.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.get_user_previous_queries(_request: Request, query_json: GetDbData = Body({'dataset_name': 'PENGUIN'}), user_name: str = Header(None)) JSONResponse[Quellcode]
-

Returns the query history of a user on a specific dataset.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (GetDbData, optional) –

    A JSON object containing: -- dataset_name (str): The name of the dataset.

    -

    Defaults to Body(example_get_admin_db_data).

    -

  • -
  • user_name (str, optional) – The user name. -Defaults to Header(None).

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
A JSON object containing:
    -
  • previous_queries (list[dict]): a list of dictionaries -containing the previous queries.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.lifespan(app: FastAPI) AsyncGenerator[Quellcode]
-

Lifespan function for the server.

-

This function is executed once on server startup, yields and -finishes running at server shutdown.

-

Server initialization is performed (config loading, etc.) and -the server state is updated accordingly. This can have potential -side effects on the return values of the „depends“ -functions, which check the server state.

-
- -
-
-async lomas_server.app.middleware(request: Request, call_next: Callable[[Request], Response]) Response[Quellcode]
-

Adds delays to requests response to protect against timing attack

-
- -
-
-lomas_server.app.opendp_query_handler(_request: Request, query_json: OpenDPInp = Body({'dataset_name': 'PENGUIN', 'opendp_json': '{"version": "0.8.0", "ast": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "partial_chain", "lhs": {"_type": "constructor", "func": "make_chain_tt", "module": "combinators", "args": [{"_type": "constructor", "func": "make_select_column", "module": "transformations", "kwargs": {"key": "bill_length_mm", "TOA": "String"}}, {"_type": "constructor", "func": "make_split_dataframe", "module": "transformations", "kwargs": {"separator": ",", "col_names": {"_type": "list", "_items": ["species", "island", "bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g", "sex"]}}}]}, "rhs": {"_type": "constructor", "func": "then_cast_default", "module": "transformations", "kwargs": {"TOA": "f64"}}}, "rhs": {"_type": "constructor", "func": "then_clamp", "module": "transformations", "kwargs": {"bounds": [30.0, 65.0]}}}, "rhs": {"_type": "constructor", "func": "then_resize", "module": "transformations", "kwargs": {"size": 346, "constant": 43.61}}}, "rhs": {"_type": "constructor", "func": "then_variance", "module": "transformations"}}, "rhs": {"_type": "constructor", "func": "then_laplace", "module": "measurements", "kwargs": {"scale": 5.0}}}}', 'fixed_delta': 1e-06}), user_name: str = Header(None)) JSONResponse[Quellcode]
-

Handles queries for the OpenDP Library.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object.

  • -
  • query_json (OpenDPInp, optional) –

    A JSON object containing the following: -- opendp_pipeline: The OpenDP pipeline for the query. -- fixed_delta: If the pipeline measurement is of type

    -
    -

    “ZeroConcentratedDivergence“ (e.g. with „make_gaussian“) then it is -converted to „SmoothedMaxDivergence“ with „make_zCDP_to_approxDP“ -(see „opendp measurements documentation at -https://docs.opendp.org/en/stable/api/python/opendp.combinators.html#opendp.combinators.make_zCDP_to_approxDP). # noqa # pylint: disable=C0301 -In that case a „fixed_delta“ must be provided by the user.

    -
    -

    Defaults to Body(example_opendp).

    -

  • -
  • user_name (str, optional) – The user name. -Defaults to Header(None).

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
A JSON object containing the following:
    -
  • requested_by (str): The user name.

  • -
  • query_response (pd.DataFrame): A DataFrame containing -the query response.

  • -
  • spent_epsilon (float): The amount of epsilon budget spent -for the query.

  • -
  • spent_delta (float): The amount of delta budget spent -for the query.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-lomas_server.app.smartnoise_sql_handler(_request: Request, query_json: SNSQLInp = Body({'query_str': 'SELECT COUNT(*) AS NB_ROW FROM df', 'dataset_name': 'PENGUIN', 'epsilon': 0.1, 'delta': 1e-05, 'mechanisms': {'count': 'gaussian'}, 'postprocess': True}), user_name: str = Header(None)) JSONResponse[Quellcode]
-

Handles queries for the SmartNoiseSQL library.

-
-
Parameter:
-
    -
  • request (Request) – Raw request object

  • -
  • query_json (SNSQLInp) –

    A JSON object containing: -- query: The SQL query to execute. NOTE: the table name is „df“,

    -
    -

    the query must end with „FROM df“.

    -
    - -

    Defaults to Body(example_smartnoise_sql).

    -

  • -
  • user_name (str, optional) – The user name. -Defaults to Header(None).

  • -
-
-
Verursacht:
-
-
-
Rückgabe:
-

-
A JSON object containing the following:
    -
  • requested_by (str): The user name.

  • -
  • query_response (pd.DataFrame): A DataFrame containing -the query response.

  • -
  • spent_epsilon (float): The amount of epsilon budget spent -for the query.

  • -
  • spent_delta (float): The amount of delta budget spent -for the query.

  • -
-
-
-

-
-
Rückgabetyp:
-

JSONResponse

-
-
-
- -
-
-

lomas_server.constants module

-
-
-class lomas_server.constants.AdminDBType(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[Quellcode]
-

Bases: StrEnum

-

Types of administration databases

-
-
-MONGODB: str = 'mongodb'
-
- -
-
-YAML: str = 'yaml'
-
- -
- -
-
-class lomas_server.constants.ConfigKeys(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[Quellcode]
-

Bases: StrEnum

-

Keys of the configuration file

-
-
-DATASET_STORE: str = 'dataset_store'
-
- -
-
-DATASET_STORE_TYPE: str = 'ds_store_type'
-
- -
-
-DB: str = 'admin_database'
-
- -
-
-DB_TYPE: str = 'db_type'
-
- -
-
-DB_TYPE_MONGODB: str = 'mongodb'
-
- -
-
-DEVELOP_MODE: str = 'develop_mode'
-
- -
-
-LRU_DATASET_STORE_MAX_SIZE: str = 'max_memory_usage'
-
- -
-
-MONGODB_ADDR: str = 'address'
-
- -
-
-MONGODB_PORT: str = 'port'
-
- -
-
-RUNTIME_ARGS: str = 'runtime_args'
-
- -
-
-SERVER: str = 'server'
-
- -
-
-SETTINGS: str = 'settings'
-
- -
-
-SUBMIT_LIMIT: str = 'submit_limit'
-
- -
-
-TIME_ATTACK: str = 'time_attack'
-
- -
- -
-
-class lomas_server.constants.DPLibraries(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[Quellcode]
-

Bases: StrEnum

-

Name of DP Library used in the query

-
-
-OPENDP = 'opendp'
-
- -
-
-SMARTNOISE_SQL = 'smartnoise_sql'
-
- -
- -
-
-class lomas_server.constants.DatasetStoreType(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[Quellcode]
-

Bases: StrEnum

-

Types of classes to handle datasets in memory

-
-
-BASIC: str = 'basic'
-
- -
-
-LRU: str = 'LRU_cache'
-
- -
- -
-
-class lomas_server.constants.OpenDPMeasurement(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[Quellcode]
-

Bases: StrEnum

-

Type of divergence for opendp measurement

-
-
-FIXED_SMOOTHED_MAX_DIVERGENCE = 'fixed_smoothed_max_divergence'
-
- -
-
-MAX_DIVERGENCE = 'max_divergence'
-
- -
-
-SMOOTHED_MAX_DIVERGENCE = 'smoothed_max_divergence'
-
- -
-
-ZERO_CONCENTRATED_DIVERGENCE = 'zero_concentrated_divergence'
-
- -
- -
-
-class lomas_server.constants.PrivateDatabaseType(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[Quellcode]
-

Bases: StrEnum

-

Type of Private Database for the private data

-
-
-PATH = 'PATH_DB'
-
- -
-
-S3 = 'S3_DB'
-
- -
- -
-
-class lomas_server.constants.TimeAttackMethod(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[Quellcode]
-

Bases: StrEnum

-

Possible methods against timing attacks

-
-
-JITTER = 'jitter'
-
- -
-
-STALL = 'stall'
-
- -
- -
-
-

lomas_server.mongodb_admin module

-
-
-lomas_server.mongodb_admin.add_dataset(db: Database, dataset_name: str, database_type: str, metadata_database_type: str, dataset_path: str | None = '', metadata_path: str | None = '', s3_bucket: str | None = '', s3_key: str | None = '', endpoint_url: str | None = '', aws_access_key_id: str | None = '', aws_secret_access_key: str | None = '', metadata_s3_bucket: str | None = '', metadata_s3_key: str | None = '', metadata_endpoint_url: str | None = '', metadata_aws_access_key_id: str | None = '', metadata_aws_secret_access_key: str | None = '') None[Quellcode]
-

Set a database type to a dataset in dataset collection.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • dataset_name (str) – Dataset name

  • -
  • database_type (str) – Type of the database

  • -
  • metadata_database_type (str) – Metadata database type

  • -
  • dataset_path (str) – Path to the dataset (for local db type)

  • -
  • metadata_path (str) – Path to metadata (for local db type)

  • -
  • s3_bucket (str) – S3 bucket name

  • -
  • s3_key (str) – S3 key

  • -
  • endpoint_url (str) – S3 endpoint URL

  • -
  • aws_access_key_id (str) – AWS access key ID

  • -
  • aws_secret_access_key (str) – AWS secret access key

  • -
  • metadata_s3_bucket (str) – Metadata S3 bucket name

  • -
  • metadata_s3_key (str) – Metadata S3 key

  • -
  • metadata_endpoint_url (str) – Metadata S3 endpoint URL

  • -
  • metadata_aws_access_key_id (str) – Metadata AWS access key ID

  • -
  • metadata_aws_secret_access_key (str) – Metadata AWS secret access key

  • -
-
-
Verursacht:
-

ValueError – If the dataset already exists - or if the database type is unknown.

-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.add_dataset_to_user(db: Database, user: str, dataset: str, epsilon: float, delta: float) None[Quellcode]
-

Add dataset with initialized budget values to list of datasets -that the user has access to. -Will not add if already added (no error will be raised in that case).

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username of the user to check

  • -
  • dataset (str) – name of the dataset to add to user

  • -
  • epsilon (float) – epsilon value for initial budget of user

  • -
  • delta (float) – delta value for initial budget of user

  • -
-
-
Verursacht:
-

ValueError – _description_

-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.add_datasets_via_yaml(db: Database, yaml_file: str | Dict, clean: bool, overwrite_datasets: bool, overwrite_metadata: bool) None[Quellcode]
-

Set all database types to datasets in dataset collection based -on yaml file.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • yaml_file (Union[str, Dict]) – if str: a path to the YAML file location -if Dict: a dictionnary containing the collection data

  • -
  • clean (bool) – Whether to clean the collection before adding.

  • -
  • overwrite_datasets (bool) – Whether to overwrite existing datasets.

  • -
  • overwrite_metadata (bool) – Whether to overwrite existing metadata.

  • -
-
-
Verursacht:
-

ValueError – If there are errors in the YAML file format.

-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.add_user(db: Database, user: str) None[Quellcode]
-

Add new user in users collection with initial values for all fields -set by default.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username to be added

  • -
-
-
Verursacht:
-
    -
  • ValueError – If the user already exists.

  • -
  • WriteConcernError – If the result is not acknowledged.

  • -
-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.add_user_with_budget(db: Database, user: str, dataset: str, epsilon: float, delta: float) None[Quellcode]
-

Add new user in users collection with initial values -for all fields set by default.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username to be added

  • -
  • dataset (str) – name of the dataset to add to user

  • -
  • epsilon (float) – epsilon value for initial budget of user

  • -
  • delta (float) – delta value for initial budget of user

  • -
-
-
Verursacht:
-

ValueError – _description_

-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.add_users_via_yaml(db: Database, yaml_file: str | Dict, clean: bool, overwrite: bool) None[Quellcode]
-

Add all users from yaml file to the user collection

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • yaml_file (Union[str, Dict]) – if str: a path to the YAML file location -if Dict: a dictionnary containing the collection data

  • -
  • clean (bool) – boolean flag -True if drop current user collection -False if keep current user collection

  • -
  • overwrite (bool) – boolean flag -True if overwrite already existing users -False errors if new values for already existing users

  • -
-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.check_dataset_and_metadata_exist(enforce_true: bool) Callable[Quellcode]
-

Creates a wrapper function that raises a ValueError -if the supplied user does not already exist in the user collection.

-
- -
-
-lomas_server.mongodb_admin.check_user_exists(enforce_true: bool) Callable[Quellcode]
-

Creates a wrapper function that raises a ValueError if the supplied -user does (not) exist in the user collection depending on the -enforce_true parameter.

-
-
Parameter:
-

enforce_true (bool) – If set to True, the wrapper will enforce -the user is already in the database. If set to False, it -will enforce the user is NOT in the database.

-
-
Rückgabe:
-

-
The wrapper function that enforces user presence

(or absence) before calling the suplied function.

-
-
-

-
-
Rückgabetyp:
-

Callable

-
-
-
- -
-
-lomas_server.mongodb_admin.check_user_has_dataset(enforce_true: bool) Callable[Quellcode]
-

Creates a wrapper function that raises a ValueError if the supplied -user has access (or not) to the supplied dataset depending on the -enforce_true parameter.

-
-
Parameter:
-

enforce_true (bool) – If set to true, the wrapper function will -enforce the user has access to the dataset. If set to False, -the wrapper function will enforce the user has NOT access -to the specified dataset.

-
-
Rückgabe:
-

-
The wrapper function that asserts user access (or not)

to the provided dataset.

-
-
-

-
-
Rückgabetyp:
-

Callable

-
-
-
- -
-
-lomas_server.mongodb_admin.del_dataset(db: Database, dataset: str) None[Quellcode]
-

Delete dataset from dataset collection.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • dataset (str) – Dataset name to be deleted

  • -
-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.del_dataset_to_user(db: Database, user: str, dataset: str) None[Quellcode]
-

Remove if exists the dataset (and all related budget info) -from list of datasets that user has access to.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username of the user to which to delete a dataset

  • -
  • dataset (str) – name of the dataset to remove from user

  • -
-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.del_user(db: Database, user: str) None[Quellcode]
-

Delete all related information for user from the users collection.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username to be deleted

  • -
-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.drop_collection(db: Database, collection: str) None[Quellcode]
-

Delete collection.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • collection (str) – Collection name to be deleted.

  • -
-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.get_list_of_datasets(db: Database) list[Quellcode]
-

Get the list of all dataset is ‚datasets‘ collection

-
-
Parameter:
-

db (Database) – mongo database object

-
-
Rückgabe:
-

list of names of all datasets

-
-
Rückgabetyp:
-

dataset_names (list)

-
-
-
- -
-
-lomas_server.mongodb_admin.get_list_of_datasets_from_user(db: Database, user: str) list[Quellcode]
-

Get the list of all datasets from the user

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username of the user to show archives

  • -
-
-
Rückgabe:
-

list of names of all users

-
-
Rückgabetyp:
-

user_datasets (list)

-
-
-
- -
-
-lomas_server.mongodb_admin.get_list_of_users(db: Database) list[Quellcode]
-

Get the list of all users is ‚users‘ collection

-
-
Parameter:
-

db (Database) – mongo database object

-
-
Rückgabe:
-

list of names of all users

-
-
Rückgabetyp:
-

user_names (list)

-
-
-
- -
-
-lomas_server.mongodb_admin.set_budget_field(db: Database, user: str, dataset: str, field: str, value: float) None[Quellcode]
-

Set (for some reason) a budget field to a given value -if given user exists and has access to given dataset.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username of the user to set budget to

  • -
  • dataset (str) – name of the dataset to set budget to

  • -
  • field (str) – one of ‚epsilon‘ or ‚delta‘

  • -
  • value (float) – value to set as epsilon or delta

  • -
-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.set_may_query(db: Database, user: str, value: bool) None[Quellcode]
-

Set (for some reason) the ‚may query‘ field to a given value -if given user exists.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username of the user to enable/disable

  • -
  • value (bool) – may query value (True or False)

  • -
-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.show_archives_of_user(db: Database, user: str) List[dict][Quellcode]
-

Show all previous queries from a user

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username of the user to show archives

  • -
-
-
Rückgabe:
-

list of previous queries from the user

-
-
Rückgabetyp:
-

archives (List)

-
-
-
- -
-
-lomas_server.mongodb_admin.show_collection(db: Database, collection: str) list[Quellcode]
-

Show a collection

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • collection (str) – Collection name to be shown.

  • -
-
-
Rückgabe:
-

None

-
-
-
- -
-
-lomas_server.mongodb_admin.show_dataset(db: Database, dataset: str) dict[Quellcode]
-

Show a dataset from dataset collection.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • dataset (str) – name of the dataset to show

  • -
-
-
Rückgabe:
-

informations about the dataset

-
-
Rückgabetyp:
-

dataset_info (dict)

-
-
-
- -
-
-lomas_server.mongodb_admin.show_metadata_of_dataset(db: Database, dataset: str) dict[Quellcode]
-

Show a metadata from metadata collection.

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • dataset (str) – name of the dataset of the metadata to show

  • -
-
-
Rückgabe:
-

informations about the metadata

-
-
Rückgabetyp:
-

metadata (dict)

-
-
-
- -
-
-lomas_server.mongodb_admin.show_user(db: Database, user: str) dict[Quellcode]
-

Show a user

-
-
Parameter:
-
    -
  • db (Database) – mongo database object

  • -
  • user (str) – username of the user to show

  • -
-
-
Rückgabe:
-

all information of user from ‚users‘ collection

-
-
Rückgabetyp:
-

user (dict)

-
-
-
- -
-
-

lomas_server.mongodb_admin_cli module

-
-
-

lomas_server.uvicorn_serve module

-
-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_server.private_dataset.html b/html/de/lomas_server.private_dataset.html deleted file mode 100644 index efeda467..00000000 --- a/html/de/lomas_server.private_dataset.html +++ /dev/null @@ -1,358 +0,0 @@ - - - - - - - lomas_server.private_dataset package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server.private_dataset package

-
-

Submodules

-
-
-

lomas_server.private_dataset.in_memory_dataset module

-
-
-class lomas_server.private_dataset.in_memory_dataset.InMemoryDataset(metadata: Dict[str, int | bool | Dict[str, str | int]], dataset_df: DataFrame)[Quellcode]
-

Bases: PrivateDataset

-

PrivateDataset for a dataset created from an in-memory pandas DataFrame.

-
-
-get_pandas_df() DataFrame[Quellcode]
-

Get the data in pandas dataframe format

-
-
Rückgabe:
-

pandas dataframe of dataset (a copy)

-
-
Rückgabetyp:
-

pd.DataFrame

-
-
-
- -
- -
-
-

lomas_server.private_dataset.path_dataset module

-
-
-class lomas_server.private_dataset.path_dataset.PathDataset(metadata: Dict[str, int | bool | Dict[str, str | int]], dataset_path: str)[Quellcode]
-

Bases: PrivateDataset

-

PrivateDataset for dataset located at constant path.

-

Path can be local or remote (http).

-
-
-get_pandas_df() DataFrame[Quellcode]
-

Get the data in pandas dataframe format

-
-
Verursacht:
-

InternalServerException – If the file format is not supported.

-
-
Rückgabe:
-

pandas dataframe of dataset

-
-
Rückgabetyp:
-

pd.DataFrame

-
-
-
- -
- -
-
-

lomas_server.private_dataset.private_dataset module

-
-
-class lomas_server.private_dataset.private_dataset.PrivateDataset(metadata: dict)[Quellcode]
-

Bases: ABC

-

Overall access to sensitive data

-
-
-df: DataFrame | None = None
-
- -
-
-get_memory_usage() int[Quellcode]
-

Returns the memory usage of this dataset, in MiB.

-

The number returned only takes into account the memory usage -of the pandas DataFrame „cached“ in the instance.

-
-
Rückgabe:
-

The memory usage, in MiB.

-
-
Rückgabetyp:
-

int

-
-
-
- -
-
-get_metadata() dict[Quellcode]
-

Get the metadata for this dataset

-
-
Rückgabe:
-

The metadata dictionary.

-
-
Rückgabetyp:
-

dict

-
-
-
- -
-
-abstract get_pandas_df(dataset_name: str) DataFrame[Quellcode]
-

Get the data in pandas dataframe format

-
-
Parameter:
-

dataset_name (str) – name of the private dataset

-
-
Rückgabe:
-

The pandas dataframe for this dataset.

-
-
Rückgabetyp:
-

pd.DataFrame

-
-
-
- -
-
-subscribe_for_memory_usage_updates(dataset_observer: PrivateDatasetObserver) None[Quellcode]
-

Add the PrivateDatasetObserver to the list of dataset_observers.

-
-
Parameter:
-

dataset_observer (PrivateDatasetObserver) – The observer of this dataset.

-
-
-
- -
- -
-
-

lomas_server.private_dataset.s3_dataset module

-
-
-class lomas_server.private_dataset.s3_dataset.S3Dataset(metadata: dict, s3_parameters: dict)[Quellcode]
-

Bases: PrivateDataset

-

PrivateDataset for dataset in S3 storage.

-
-
-get_pandas_df() DataFrame[Quellcode]
-

Get the data in pandas dataframe format

-
-
Verursacht:
-

InternalServerException – If the dataset cannot be read.

-
-
Rückgabe:
-

pandas dataframe of dataset

-
-
Rückgabetyp:
-

pd.DataFrame

-
-
-
- -
- -
-
-

lomas_server.private_dataset.utils module

-
-
-lomas_server.private_dataset.utils.private_dataset_factory(dataset_name: str, admin_database: AdminDatabase) PrivateDataset[Quellcode]
-

Returns the appropriate dataset class based on dataset storage location

-
-
Parameter:
-
    -
  • dataset_name (str) – The dataset name.

  • -
  • admin_database (AdminDatabase) – An initialized instance -of AdminDatabase.

  • -
-
-
Verursacht:
-

InternalServerException – If the dataset type does not exist.

-
-
Rückgabe:
-

The PrivateDataset instance for this dataset.

-
-
Rückgabetyp:
-

PrivateDataset

-
-
-
- -
-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_server.tests.html b/html/de/lomas_server.tests.html deleted file mode 100644 index ea29cd17..00000000 --- a/html/de/lomas_server.tests.html +++ /dev/null @@ -1,460 +0,0 @@ - - - - - - - lomas_server.tests package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server.tests package

-
-

Submodules

-
-
-

lomas_server.tests.constants module

-
-
-

lomas_server.tests.test_api module

-
-
-class lomas_server.tests.test_api.TestRootAPIEndpoint(methodName='runTest')[Quellcode]
-

Bases: TestCase

-

End-to-end tests of the api endpoints.

-

This test can be both executed as an integration test -(enabled by setting LOMAS_TEST_MONGO_INTEGRATION to True), -or a standard test. The first requires a mongodb to be started -before running while the latter will use a local YamlDatabase.

-
-
-setUp() None[Quellcode]
-

_summary_

-
- -
-
-classmethod setUpClass() None[Quellcode]
-

Hook method for setting up class fixture before running tests in the class.

-
- -
-
-tearDown() None[Quellcode]
-

Hook method for deconstructing the test fixture after testing it.

-
- -
-
-classmethod tearDownClass() None[Quellcode]
-

Hook method for deconstructing the class fixture after running all tests in the class.

-
- -
-
-test_budget_over_limit() None[Quellcode]
-

_summary_

-
- -
-
-test_dummy_opendp_query() None[Quellcode]
-

_summary_

-
- -
-
-test_dummy_smartnoise_query() None[Quellcode]
-

_summary_

-
- -
-
-test_get_dataset_metadata() None[Quellcode]
-

_summary_

-
- -
-
-test_get_dummy_dataset() None[Quellcode]
-

_summary_

-
- -
-
-test_get_initial_budget() None[Quellcode]
-

_summary_

-
- -
-
-test_get_previous_queries() None[Quellcode]
-

_summary_

-
- -
-
-test_get_remaining_budget() None[Quellcode]
-

_summary_

-
- -
-
-test_get_total_spent_budget() None[Quellcode]
-

_summary_

-
- -
-
-test_opendp_cost() None[Quellcode]
-

_summary_

-
- -
-
-test_opendp_query() None[Quellcode]
-

_summary_

-
- -
-
-test_smartnoise_cost() None[Quellcode]
-

_summary_

-
- -
-
-test_smartnoise_query() None[Quellcode]
-

_summary_

-
- -
-
-test_state() None[Quellcode]
-

_summary_

-
- -
-
-test_subsequent_budget_limit_logic() None[Quellcode]
-

_summary_

-
- -
- -
-
-

lomas_server.tests.test_dummy_generation module

-
-
-class lomas_server.tests.test_dummy_generation.TestMakeDummyDataset(methodName='runTest')[Quellcode]
-

Bases: TestCase

-

Tests for the generation of dummy datasets.

-
-
-test_boolean_column() None[Quellcode]
-

_summary_

-
- -
-
-test_cardinality_column() None[Quellcode]
-

_summary_

-
- -
-
-test_datetime_column() None[Quellcode]
-

_summary_

-
- -
-
-test_float_column() None[Quellcode]
-

_summary_

-
- -
-
-test_int_column() None[Quellcode]
-

_summary_

-
- -
-
-test_nullable_column() None[Quellcode]
-

_summary_

-
- -
-
-test_seed() None[Quellcode]
-

_summary_

-
- -
- -
-
-

lomas_server.tests.test_mongodb_admin module

-
-
-class lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin(methodName='runTest')[Quellcode]
-

Bases: TestCase

-

Tests for the functions in mongodb_admin.py.

-

This is an integration test and requires a mongodb database -to be started before being executed.

-

The test is only executed if the LOMAS_TEST_MONGO_INTEGRATION -environment variable is set to True.

-
-
-classmethod setUpClass() None[Quellcode]
-

Connection to database

-
- -
-
-tearDown() None[Quellcode]
-

Drop all data from database

-
- -
-
-test_add_dataset_to_user() None[Quellcode]
-

Test add dataset to a user

-
- -
-
-test_add_datasets_via_yaml() None[Quellcode]
-

Test add datasets via a YAML file

-
- -
-
-test_add_local_dataset() None[Quellcode]
-

Test adding a local dataset

-
- -
-
-test_add_user() None[Quellcode]
-

Test adding a user

-
- -
-
-test_add_user_wb() None[Quellcode]
-

Test adding a user with a dataset

-
- -
-
-test_add_users_via_yaml() None[Quellcode]
-

Test create user collection via YAML file

-
- -
-
-test_del_dataset() None[Quellcode]
-

Test dataset deletion

-
- -
-
-test_del_dataset_to_user() None[Quellcode]
-

Test delete dataset from user

-
- -
-
-test_del_user() None[Quellcode]
-

Test deleting a user

-
- -
-
-test_drop_collection() None[Quellcode]
-

Test drop collection from db

-
- -
-
-test_set_budget_field() None[Quellcode]
-

Test setting a budget field

-
- -
-
-test_set_may_query() None[Quellcode]
-

Test set may query

-
- -
- -
-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/lomas_server.utils.html b/html/de/lomas_server.utils.html deleted file mode 100644 index daf943a1..00000000 --- a/html/de/lomas_server.utils.html +++ /dev/null @@ -1,1496 +0,0 @@ - - - - - - - lomas_server.utils package — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server.utils package

-
-

Submodules

-
-
-

lomas_server.utils.anti_timing_att module

-
-
-async lomas_server.utils.anti_timing_att.anti_timing_att(request: Request, call_next: Callable, config: Config) Response[Quellcode]
-

Anti-timing attack mechanism.

-

Changes the response time to either a minimum or by adding -random noïse in order to avoid timing attacks.

-
-
Parameter:
-
    -
  • request (Request) – The FastApi request.

  • -
  • call_next (Callable) – The FastApi endpoint to call.

  • -
  • config (Config) – The server config.

  • -
-
-
Rückgabe:
-

The reponse from call_next.

-
-
Rückgabetyp:
-

Response

-
-
-
- -
-
-

lomas_server.utils.collections_models module

-
-
-class lomas_server.utils.collections_models.Dataset(*, dataset_name: str, database_type: PrivateDatabaseType, metadata: MetadataOfPathDB | MetadataOfS3DB)[Quellcode]
-

Bases: BaseModel

-

BaseModel for a dataset

-
-
-database_type: PrivateDatabaseType
-
- -
-
-dataset_name: str
-
- -
-
-metadata: MetadataOfPathDB | MetadataOfS3DB
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'database_type': FieldInfo(annotation=PrivateDatabaseType, required=True), 'dataset_name': FieldInfo(annotation=str, required=True), 'metadata': FieldInfo(annotation=Union[MetadataOfPathDB, MetadataOfS3DB], required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.collections_models.DatasetOfPathDB(*, dataset_name: str, database_type: PrivateDatabaseType, metadata: MetadataOfPathDB | MetadataOfS3DB, dataset_path: str)[Quellcode]
-

Bases: Dataset

-

BaseModel for a local dataset

-
-
-dataset_path: str
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'database_type': FieldInfo(annotation=PrivateDatabaseType, required=True), 'dataset_name': FieldInfo(annotation=str, required=True), 'dataset_path': FieldInfo(annotation=str, required=True), 'metadata': FieldInfo(annotation=Union[MetadataOfPathDB, MetadataOfS3DB], required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.collections_models.DatasetOfS3DB(*, dataset_name: str, database_type: PrivateDatabaseType, metadata: MetadataOfPathDB | MetadataOfS3DB, s3_bucket: str, s3_key: str, endpoint_url: str, aws_access_key_id: str, aws_secret_access_key: str)[Quellcode]
-

Bases: Dataset

-

BaseModel for a dataset on S3

-
-
-aws_access_key_id: str
-
- -
-
-aws_secret_access_key: str
-
- -
-
-endpoint_url: str
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'aws_access_key_id': FieldInfo(annotation=str, required=True), 'aws_secret_access_key': FieldInfo(annotation=str, required=True), 'database_type': FieldInfo(annotation=PrivateDatabaseType, required=True), 'dataset_name': FieldInfo(annotation=str, required=True), 'endpoint_url': FieldInfo(annotation=str, required=True), 'metadata': FieldInfo(annotation=Union[MetadataOfPathDB, MetadataOfS3DB], required=True), 's3_bucket': FieldInfo(annotation=str, required=True), 's3_key': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-s3_bucket: str
-
- -
-
-s3_key: str
-
- -
- -
-
-class lomas_server.utils.collections_models.DatasetOfUser(*, dataset_name: str, initial_epsilon: float, initial_delta: float, total_spent_epsilon: float, total_spent_delta: float)[Quellcode]
-

Bases: BaseModel

-

BaseModel for informations of a user on a dataset

-
-
-dataset_name: str
-
- -
-
-initial_delta: float
-
- -
-
-initial_epsilon: float
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'dataset_name': FieldInfo(annotation=str, required=True), 'initial_delta': FieldInfo(annotation=float, required=True), 'initial_epsilon': FieldInfo(annotation=float, required=True), 'total_spent_delta': FieldInfo(annotation=float, required=True), 'total_spent_epsilon': FieldInfo(annotation=float, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-total_spent_delta: float
-
- -
-
-total_spent_epsilon: float
-
- -
- -
-
-class lomas_server.utils.collections_models.DatasetsCollection(*, datasets: List[DatasetOfPathDB | DatasetOfS3DB])[Quellcode]
-

Bases: BaseModel

-

BaseModel for datasets collection

-
-
-datasets: List[DatasetOfPathDB | DatasetOfS3DB]
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'datasets': FieldInfo(annotation=List[Union[DatasetOfPathDB, DatasetOfS3DB]], required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.collections_models.Metadata(*, max_ids: int, row_privacy: bool, columns: Dict[str, Dict[str, int | float | str | List[str]]])[Quellcode]
-

Bases: BaseModel

-

BaseModel for a metadata format

-
-
-columns: Dict[str, Dict[str, int | float | str | List[str]]]
-
- -
-
-max_ids: int
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'columns': FieldInfo(annotation=Dict[str, Dict[str, Union[int, float, str, List[str]]]], required=True), 'max_ids': FieldInfo(annotation=int, required=True), 'row_privacy': FieldInfo(annotation=bool, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-row_privacy: bool
-
- -
- -
-
-class lomas_server.utils.collections_models.MetadataOfDataset(*, database_type: PrivateDatabaseType)[Quellcode]
-

Bases: BaseModel

-

BaseModel for metadata of a dataset

-
-
-database_type: PrivateDatabaseType
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'database_type': FieldInfo(annotation=PrivateDatabaseType, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.collections_models.MetadataOfPathDB(*, database_type: PrivateDatabaseType, metadata_path: str)[Quellcode]
-

Bases: MetadataOfDataset

-

BaseModel for metadata of a dataset with PATH_DB

-
-
-metadata_path: str
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'database_type': FieldInfo(annotation=PrivateDatabaseType, required=True), 'metadata_path': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.collections_models.MetadataOfS3DB(*, database_type: PrivateDatabaseType, s3_bucket: str, s3_key: str, endpoint_url: str, aws_access_key_id: str, aws_secret_access_key: str)[Quellcode]
-

Bases: MetadataOfDataset

-

BaseModel for metadata of a dataset with S3_DB

-
-
-aws_access_key_id: str
-
- -
-
-aws_secret_access_key: str
-
- -
-
-endpoint_url: str
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'aws_access_key_id': FieldInfo(annotation=str, required=True), 'aws_secret_access_key': FieldInfo(annotation=str, required=True), 'database_type': FieldInfo(annotation=PrivateDatabaseType, required=True), 'endpoint_url': FieldInfo(annotation=str, required=True), 's3_bucket': FieldInfo(annotation=str, required=True), 's3_key': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-s3_bucket: str
-
- -
-
-s3_key: str
-
- -
- -
-
-class lomas_server.utils.collections_models.User(*, user_name: str, may_query: bool, datasets_list: List[DatasetOfUser])[Quellcode]
-

Bases: BaseModel

-

BaseModel for a user in a user collection

-
-
-datasets_list: List[DatasetOfUser]
-
- -
-
-may_query: bool
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'datasets_list': FieldInfo(annotation=List[DatasetOfUser], required=True), 'may_query': FieldInfo(annotation=bool, required=True), 'user_name': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-user_name: str
-
- -
- -
-
-class lomas_server.utils.collections_models.UserCollection(*, users: List[User])[Quellcode]
-

Bases: BaseModel

-

BaseModel for users collection

-
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'users': FieldInfo(annotation=List[User], required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-users: List[User]
-
- -
- -
-
-

lomas_server.utils.config module

-
-
-class lomas_server.utils.config.Config(*, develop_mode: bool, server: Server, submit_limit: float, admin_database: DBConfig, dataset_store: DatasetStoreConfig)[Quellcode]
-

Bases: BaseModel

-

Server runtime config.

-
-
-admin_database: DBConfig
-
- -
-
-dataset_store: DatasetStoreConfig
-
- -
-
-develop_mode: bool
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'admin_database': FieldInfo(annotation=DBConfig, required=True), 'dataset_store': FieldInfo(annotation=DatasetStoreConfig, required=True), 'develop_mode': FieldInfo(annotation=bool, required=True), 'server': FieldInfo(annotation=Server, required=True), 'submit_limit': FieldInfo(annotation=float, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-server: Server
-
- -
-
-submit_limit: float
-
- -
- -
-
-class lomas_server.utils.config.ConfigLoader[Quellcode]
-

Bases: object

-

Singleton object that holds the config for the server.

-

Initialises the config by calling load_config() with its -default arguments.

-

The config can be reloaded by calling load_config with -other arguments.

-
-
-get_config() Config[Quellcode]
-

Get the config.

-
-
Rückgabe:
-

The config.

-
-
Rückgabetyp:
-

Config

-
-
-
- -
-
-load_config(config_path: str = '/usr/lomas_server/runtime.yaml', secrets_path: str = '/usr/lomas_server/secrets.yaml') None[Quellcode]
-

Loads the config and the secret data from disk, -merges them and returns the config object.

-
-
Parameter:
-
    -
  • config_path (str, optional) – The config filepath. Defaults to CONFIG_PATH.

  • -
  • secrets_path (str, optional) – The secrets filepath. Defaults to SECRETS_PATH.

  • -
-
-
Verursacht:
-

InternalServerException – If the config cannot be - correctly interpreted.

-
-
-
- -
-
-set_config(config: Config) None[Quellcode]
-

Set the singleton’s config to config.

-
-
Parameter:
-

config (Config) – The new config.

-
-
-
- -
- -
-
-class lomas_server.utils.config.DBConfig(*, db_type: str = <enum 'AdminDBType'>)[Quellcode]
-

Bases: BaseModel

-

BaseModel for database type config

-
-
-db_type: str
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'db_type': FieldInfo(annotation=str, required=False, default=<enum 'AdminDBType'>)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.config.DatasetStoreConfig(*, ds_store_type: DatasetStoreType)[Quellcode]
-

Bases: BaseModel

-

BaseModel for dataset store configs

-
-
-ds_store_type: DatasetStoreType
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'ds_store_type': FieldInfo(annotation=DatasetStoreType, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.config.LRUDatasetStoreConfig(*, ds_store_type: DatasetStoreType, max_memory_usage: int)[Quellcode]
-

Bases: DatasetStoreConfig

-

BaseModel for dataset store configs in case of a LRU dataset store

-
-
-max_memory_usage: int
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'ds_store_type': FieldInfo(annotation=DatasetStoreType, required=True), 'max_memory_usage': FieldInfo(annotation=int, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.config.MongoDBConfig(*, db_type: str = <enum 'AdminDBType'>, address: str, port: int, username: str, password: str, db_name: str)[Quellcode]
-

Bases: DBConfig

-

BaseModel for dataset store configs in case of a MongoDB database

-
-
-address: str
-
- -
-
-db_name: str
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'address': FieldInfo(annotation=str, required=True), 'db_name': FieldInfo(annotation=str, required=True), 'db_type': FieldInfo(annotation=str, required=False, default=<enum 'AdminDBType'>), 'password': FieldInfo(annotation=str, required=True), 'port': FieldInfo(annotation=int, required=True), 'username': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-password: str
-
- -
-
-port: int
-
- -
-
-username: str
-
- -
- -
-
-class lomas_server.utils.config.Server(*, time_attack: TimeAttack, host_ip: str, host_port: int, log_level: str, reload: bool, workers: int)[Quellcode]
-

Bases: BaseModel

-

BaseModel for uvicorn server configs

-
-
-host_ip: str
-
- -
-
-host_port: int
-
- -
-
-log_level: str
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'host_ip': FieldInfo(annotation=str, required=True), 'host_port': FieldInfo(annotation=int, required=True), 'log_level': FieldInfo(annotation=str, required=True), 'reload': FieldInfo(annotation=bool, required=True), 'time_attack': FieldInfo(annotation=TimeAttack, required=True), 'workers': FieldInfo(annotation=int, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-reload: bool
-
- -
-
-time_attack: TimeAttack
-
- -
-
-workers: int
-
- -
- -
-
-class lomas_server.utils.config.TimeAttack(*, method: TimeAttackMethod, magnitude: float)[Quellcode]
-

Bases: BaseModel

-

BaseModel for configs to prevent timing attacks

-
-
-magnitude: float
-
- -
-
-method: TimeAttackMethod
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'magnitude': FieldInfo(annotation=float, required=True), 'method': FieldInfo(annotation=TimeAttackMethod, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.config.YamlDBConfig(*, db_type: str = <enum 'AdminDBType'>, db_file: str)[Quellcode]
-

Bases: DBConfig

-

BaseModel for dataset store configs in case of a Yaml database

-
-
-db_file: str
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'db_file': FieldInfo(annotation=str, required=True), 'db_type': FieldInfo(annotation=str, required=False, default=<enum 'AdminDBType'>)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-lomas_server.utils.config.get_config() Config[Quellcode]
-

Get the config from the ConfigLoader Singleton instance.

-
-
Rückgabe:
-

The config.

-
-
Rückgabetyp:
-

Config

-
-
-
- -
-
-

lomas_server.utils.error_handler module

-
-
-exception lomas_server.utils.error_handler.ExternalLibraryException(library: str, error_message: str)[Quellcode]
-

Bases: Exception

-

Custom exception for issues within external libraries

-

This exception will occur when the processes fail within the -external libraries (smartnoise-sql, opendp, diffprivlib)

-
- -
-
-exception lomas_server.utils.error_handler.InternalServerException(error_message: str)[Quellcode]
-

Bases: Exception

-

Custom exception for issues within server internal functionalities -like unexpected match cases

-
- -
-
-exception lomas_server.utils.error_handler.InvalidQueryException(error_message: str)[Quellcode]
-

Bases: Exception

-

Custom exception for invalid queries

-
-
For example, this exception will occur when the query:
    -
  • is not an opendp measurement

  • -
  • cannot be reconstructed properly (for opendp and diffprivlib)

  • -
-
-
-
- -
-
-exception lomas_server.utils.error_handler.UnauthorizedAccessException(error_message: str)[Quellcode]
-

Bases: Exception

-

Custom exception for unauthorized access: -(unknown user, no access to dataset, etc)

-
- -
-
-lomas_server.utils.error_handler.add_exception_handlers(app: FastAPI) None[Quellcode]
-

Translates custom exceptions to JSONResponses. -:param app: A fastapi App. -:type app: FastAPI

-
- -
-
-

lomas_server.utils.example_inputs module

-
-
-

lomas_server.utils.input_models module

-
-
-class lomas_server.utils.input_models.DummyOpenDPInp(*, dataset_name: str, opendp_json: str, dummy_nb_rows: int, dummy_seed: int, fixed_delta: float | None = None)[Quellcode]
-

Bases: BaseModel

-

Model input for a dummy opendp query

-
-
-dataset_name: str
-
- -
-
-dummy_nb_rows: int
-
- -
-
-dummy_seed: int
-
- -
-
-fixed_delta: float | None
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'dataset_name': FieldInfo(annotation=str, required=True), 'dummy_nb_rows': FieldInfo(annotation=int, required=True), 'dummy_seed': FieldInfo(annotation=int, required=True), 'fixed_delta': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'opendp_json': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-opendp_json: str
-
- -
- -
-
-class lomas_server.utils.input_models.DummySNSQLInp(*, query_str: str, dataset_name: str, dummy_nb_rows: int, dummy_seed: int, epsilon: float, delta: float, mechanisms: dict, postprocess: bool)[Quellcode]
-

Bases: BaseModel

-

Model input for a smarnoise-sql dummy query

-
-
-dataset_name: str
-
- -
-
-delta: float
-
- -
-
-dummy_nb_rows: int
-
- -
-
-dummy_seed: int
-
- -
-
-epsilon: float
-
- -
-
-mechanisms: dict
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'dataset_name': FieldInfo(annotation=str, required=True), 'delta': FieldInfo(annotation=float, required=True), 'dummy_nb_rows': FieldInfo(annotation=int, required=True), 'dummy_seed': FieldInfo(annotation=int, required=True), 'epsilon': FieldInfo(annotation=float, required=True), 'mechanisms': FieldInfo(annotation=dict, required=True), 'postprocess': FieldInfo(annotation=bool, required=True), 'query_str': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-postprocess: bool
-
- -
-
-query_str: str
-
- -
- -
-
-class lomas_server.utils.input_models.GetDbData(*, dataset_name: str)[Quellcode]
-

Bases: BaseModel

-

Model input to get information about a dataset

-
-
-dataset_name: str
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'dataset_name': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.input_models.GetDummyDataset(*, dataset_name: str, dummy_nb_rows: int, dummy_seed: int)[Quellcode]
-

Bases: BaseModel

-

Model input to get a dummy dataset

-
-
-dataset_name: str
-
- -
-
-dummy_nb_rows: int
-
- -
-
-dummy_seed: int
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'dataset_name': FieldInfo(annotation=str, required=True), 'dummy_nb_rows': FieldInfo(annotation=int, required=True), 'dummy_seed': FieldInfo(annotation=int, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
- -
-
-class lomas_server.utils.input_models.OpenDPInp(*, dataset_name: str, opendp_json: str, fixed_delta: float | None = None)[Quellcode]
-

Bases: BaseModel

-

Model input for an opendp query

-
-
-dataset_name: str
-
- -
-
-fixed_delta: float | None
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'dataset_name': FieldInfo(annotation=str, required=True), 'fixed_delta': FieldInfo(annotation=Union[float, NoneType], required=False, default=None), 'opendp_json': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-opendp_json: str
-
- -
- -
-
-class lomas_server.utils.input_models.SNSQLInp(*, query_str: str, dataset_name: str, epsilon: float, delta: float, mechanisms: dict, postprocess: bool)[Quellcode]
-

Bases: BaseModel

-

Model input for a smarnoise-sql query

-
-
-dataset_name: str
-
- -
-
-delta: float
-
- -
-
-epsilon: float
-
- -
-
-mechanisms: dict
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'dataset_name': FieldInfo(annotation=str, required=True), 'delta': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0), Le(le=0.0004)]), 'epsilon': FieldInfo(annotation=float, required=True, metadata=[Gt(gt=0), Le(le=5.0)]), 'mechanisms': FieldInfo(annotation=dict, required=True), 'postprocess': FieldInfo(annotation=bool, required=True), 'query_str': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-postprocess: bool
-
- -
-
-query_str: str
-
- -
- -
-
-class lomas_server.utils.input_models.SNSQLInpCost(*, query_str: str, dataset_name: str, epsilon: float, delta: float, mechanisms: dict)[Quellcode]
-

Bases: BaseModel

-

Model input for a smarnoise-sql cost query

-
-
-dataset_name: str
-
- -
-
-delta: float
-
- -
-
-epsilon: float
-
- -
-
-mechanisms: dict
-
- -
-
-model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
-

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

-
- -
-
-model_config: ClassVar[ConfigDict] = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-model_fields: ClassVar[dict[str, FieldInfo]] = {'dataset_name': FieldInfo(annotation=str, required=True), 'delta': FieldInfo(annotation=float, required=True), 'epsilon': FieldInfo(annotation=float, required=True), 'mechanisms': FieldInfo(annotation=dict, required=True), 'query_str': FieldInfo(annotation=str, required=True)}
-

Metadata about the fields defined on the model, -mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

-

This replaces Model.__fields__ from Pydantic V1.

-
- -
-
-query_str: str
-
- -
- -
-
-

lomas_server.utils.loggr module

-
-
-

lomas_server.utils.utils module

-
-
-lomas_server.utils.utils.add_demo_data_to_admindb() None[Quellcode]
-

Adds the demo data to the mongodb admindb.

-

Uses hardcoded paths to user and dataset collections. -Meant to be used in the develop mode of the service.

-
- -
-
-async lomas_server.utils.utils.server_live(request: Request) AsyncGenerator[Quellcode]
-

Checks the server is live and throws an exception otherwise.

-
-
Parameter:
-

request (Request) – Raw request

-
-
Verursacht:
-

InternalServerException – If the server is not live.

-
-
Rückgabe:
-

AsyncGenerator

-
-
-
- -
-
-lomas_server.utils.utils.stream_dataframe(df: DataFrame) StreamingResponse[Quellcode]
-

Creates a streaming response for a given pandas dataframe.

-
-
Parameter:
-

df (pd.DataFrame) – The dataframe to stream.

-
-
Rückgabe:
-

The resulting streaming response.

-
-
Rückgabetyp:
-

StreamingResponse

-
-
-
- -
-
-

Module contents

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/Demo_Client_Notebook.html b/html/de/notebooks/Demo_Client_Notebook.html deleted file mode 100644 index 61e45697..00000000 --- a/html/de/notebooks/Demo_Client_Notebook.html +++ /dev/null @@ -1,1122 +0,0 @@ - - - - - - - Lomas: Client demo — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Lomas: Client demo

-

This notebook showcases how researcher could use the Lomas platform. It explains the different functionnalities provided by the lomas-client library to interact with the secure server.

-

The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.

-

Each user has access to one or multiple projects and for each dataset has a limited budget with \(\epsilon\) and \(\delta\) values.

-
-
[1]:
-
-
-
from IPython.display import Image
-Image(filename="images/image_demo_client.png", width=800)
-
-
-
-
-
[1]:
-
-
-
-../_images/notebooks_Demo_Client_Notebook_2_0.png -
-
-

🐧🐧🐧 In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.

-

Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.

-

This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library lomas-client. 🐧🐧🐧

-
-

Step 1: Install the library

-

To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library lomas-client on her local developping environment.

-

It can be installed via the pip command:

-
-
[2]:
-
-
-
#!pip install lomas-client
-
-
-
-
-
[1]:
-
-
-
from lomas_client import Client
-import numpy as np
-
-
-
-
-
-

Step 2: Initialise the client

-

Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server.

-

To create the client, Dr. Antartica needs to give it a few parameters: - a url: the root application endpoint to the remote secure server. - user_name: her name as registered in the database (Dr. Alice Antartica) - dataset_name: the name of the dataset that she wants to query (PENGUIN)

-

She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit (as is done in the Admin Notebook for Users and Datasets management).

-
-
[2]:
-
-
-
APP_URL = "http://localhost:80"
-USER_NAME = "Dr. Antartica"
-DATASET_NAME = "PENGUIN"
-client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)
-
-
-
-

And that’s it for the preparation. She is now ready to use the various functionnalities offered by lomas_client.

-
-
-

Step 3: Understand the functionnalities of the library

-
-

Getting dataset metadata

-

Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the get_dataset_metadata() function of the client. As this is public information, this does not cost any budget.

-

This function returns metadata information in a format based on SmartnoiseSQL dictionary format, where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). Any metadata is required for Smartnoise-SQL is also required here and additional information such that the different categories in a string type column column can be added.

-
-
[3]:
-
-
-
penguin_metadata = client.get_dataset_metadata()
-penguin_metadata
-
-
-
-
-
[3]:
-
-
-
-
-{'max_ids': 1,
- 'row_privacy': True,
- 'censor_dims': False,
- 'columns': {'species': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Adelie', 'Chinstrap', 'Gentoo']},
-  'island': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Torgersen', 'Biscoe', 'Dream']},
-  'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},
-  'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},
-  'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},
-  'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},
-  'sex': {'type': 'string',
-   'cardinality': 2,
-   'categories': ['MALE', 'FEMALE']}}}
-
-
-

Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) with their associated categories (i.e. the species column has 3 possibilities: ‚Adelie‘, ‚Chinstrap‘, ‚Gentoo‘) and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds (i.e. the body mass of penguin ranges from 2000 to 7000 gramms). She also knows based on the field max_ids: 1 that each penguin can only be once in the dataset and on the field -row_privacy: True that each row represents a single penguin.

-
-
-

Get a dummy dataset

-

Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset.

-

Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets. Getting a dummy dataset does not affect the budget as there is no differential privacy here. It is not a synthetic dataset and all that could be learn here is already present in the public metadata (it is created randomly on the fly based on the -metadata).

-

Dr. Antartica first create a dummy dataset with 200 rows and chooses a seed of 0.

-
-
[4]:
-
-
-
NB_ROWS = 200
-SEED = 0
-
-
-
-
-
[5]:
-
-
-
df_dummy = client.get_dummy_dataset(
-    nb_rows = NB_ROWS,
-    seed = SEED
-)
-
-print(df_dummy.shape)
-df_dummy.head()
-
-
-
-
-
-
-
-
-(200, 7)
-
-
-
-
[5]:
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0GentooBiscoe49.20847316.117959190.1259502873.291927FEMALE
1GentooTorgersen55.03162819.963435242.9291423639.940005FEMALE
2ChinstrapTorgersen51.09671816.777518159.9614935401.743330MALE
3AdelieBiscoe49.07091114.796037244.5301532316.038092MALE
4ChinstrapBiscoe44.82791813.246787236.9488535036.246870FEMALE
-
-
-
-
-

Query on dummy dataset

-

Now that she has an idea of what the data looks like, she wants to start querying the real dataset for her research.

-

However, her budget is limited and it would be a waste to spend it by mistake on a coding error. Therefore the client/server pipeline has functionnal testing capabilities for the users. It is possible to test a query on a dummy dataset to ensure that everything is working properly. Dr. Antartica will not be able to use the results of a dummy query for her analysis (as the data is random) but if the query on the dummy dataset works, she can be confident that her query will also work on the -real dataset. This functionnal testing on the dummy does not have any impact on the budget as it is on random data only.

-

To test on the dummy data instead of the real data, the function call is exactly the same with the only exception of the flag dummy=True. In the following cell, she will test with smartnoise_query but it is the same flag for opendp_query. She can optionnaly give two additional parameters to set the seed and the number of rows of the dummy dataset.

-

Another more advanced possibility for functionnal tests with the dummy is to compare results of queries on a local dummy and the remote dummy with a very high budget: - create a local dummy on the notebook with a specific seed and number of rows - compute locally the wanted query on this local dummy with python functions like numpy - query the server on the same remote dummy with (dummy=True, same seed and same number of row) and a very big buget to limit noise as much as possible (don’t -worry this won’t cost any real budget) - compare and verify that the local and remote dummy have similar results.

-
-

Average and number of rows with smartnoise-sql library on remote dummy

-

Dr. Antartica will now try a query to get the number of penguin and their average bill length (in mm) on the dummy dataset. She does not forget to - set the dummy flag to True - set very high budget values to be able to compare results with a similar local dummy (with the same seed and number of rows) if she wants to verify that the function do what is expected. Here she will just check that the number of rows is close to what she sets as parameter.

-
-
[6]:
-
-
-
# Number of penguin and average bill length in mm
-QUERY = "SELECT COUNT(*) AS nb_penguins, \
-        AVG(bill_length_mm) AS avg_bill_length_mm \
-        FROM df"
-
-
-
-
-
[7]:
-
-
-
# On the remote server dummy dataframe
-dummy_res = client.smartnoise_query(
-    query = QUERY,
-    epsilon = 100.0,
-    delta = 0.99,
-    dummy = True,
-    nb_rows = NB_ROWS,
-    seed = SEED
-)
-
-
-
-
-
[8]:
-
-
-
print(f"Average bill length in remote dummy: {np.round(dummy_res['query_response']['avg_bill_length_mm'][0], 2)}mm.")
-print(f"Number of rows in remote dummy: {np.round(dummy_res['query_response']['nb_penguins'][0], 2)}.")
-
-
-
-
-
-
-
-
-Average bill length in remote dummy: 47.52mm.
-Number of rows in remote dummy: 199.
-
-
-

No functionnal errors happened and the estimated number of rows is very close (if not equal) to the number of rows that she set for the dummy dataframe. She is now even more confident in using her query on the server.

-
-
-
-

Get current budget

-

It is the first time that Dr. Antartica connects to the server and she wants to know how much buget the queen assigned her. Therefore, she calls the fonction get_initial_budget.

-
-
[9]:
-
-
-
client.get_initial_budget()
-
-
-
-
-
[9]:
-
-
-
-
-{'initial_epsilon': 10.0, 'initial_delta': 0.005}
-
-
-

She sees that she has 10.0 epsilon and 0.005 epsilon at her disposal.

-

Then she checks her total spent budget get_total_spent_budget. As she only did queries on metadata on dummy dataframes, this should still be 0.

-
-
[10]:
-
-
-
client.get_total_spent_budget()
-
-
-
-
-
[10]:
-
-
-
-
-{'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}
-
-
-

It will also be useful to know what the remaining budget is. Therefore, she calls the function get_remaining_budget. It just substarcts the total spent budget from the initial budget.

-
-
[11]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[11]:
-
-
-
-
-{'remaining_epsilon': 10.0, 'remaining_delta': 0.005}
-
-
-

As expected, for now the remaining budget is equal to the inital budget.

-
-
-

Estimate cost of a query

-

Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The estimate cost function returns the estimated real cost of any query.

-

Again, of course, this will not impact the user’s budget.

-

Dr. Antartica checks the budget that computing the average bill length will really cost her if she asks the query with an epsilon and a delta.

-
-
[12]:
-
-
-
EPSILON = 0.5
-DELTA = 1e-4
-
-
-
-
-
[13]:
-
-
-
client.estimate_smartnoise_cost(
-    query = QUERY,
-    epsilon = EPSILON,
-    delta = DELTA
-)
-
-
-
-
-
[13]:
-
-
-
-
-{'epsilon_cost': 1.5, 'delta_cost': 0.00014999500000001387}
-
-
-

This query would actually cost her 1.5 epsilon and delta 1.4999e-4. She decides that it is good enough.

-
-
-

Query on real private dataset with smartnoise-sql

-

Now that all the safeguard functions were tested, Dr. Antartica is ready to query on the real dataset and get a differentially private response of the number of penguins and average bill length. By default, the flag dummy is False so setting it is optional. She uses the values of epsilon and delta that she selected just before.

-

Careful: This command DOES spend the budget of the user and the remaining budget is updated for every query.

-
-
[14]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[14]:
-
-
-
-
-{'remaining_epsilon': 10.0, 'remaining_delta': 0.005}
-
-
-
-
[15]:
-
-
-
response = client.smartnoise_query(
-    query = QUERY,
-    epsilon = EPSILON,
-    delta = DELTA,
-    dummy = False # Optionnal
-)
-
-
-
-
-
[16]:
-
-
-
nb_penguins = response['query_response']['nb_penguins'].iloc[0]
-print(f"Number of penguins in real data: {nb_penguins}.")
-
-avg_bill_length = np.round(response['query_response']['avg_bill_length_mm'].iloc[0], 2)
-print(f"Average bill length of penguins in real data: {avg_bill_length}mm.")
-
-
-
-
-
-
-
-
-Number of penguins in real data: 342.
-Average bill length of penguins in real data: 42.93mm.
-
-
-

After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:

-
-
[17]:
-
-
-
response
-
-
-
-
-
[17]:
-
-
-
-
-{'requested_by': 'Dr. Antartica',
- 'query_response':    nb_penguins  avg_bill_length_mm
- 0          342           42.930651,
- 'spent_epsilon': 1.5,
- 'spent_delta': 0.00014999500000001387}
-
-
-
-
[18]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[18]:
-
-
-
-
-{'remaining_epsilon': 8.5, 'remaining_delta': 0.004850004999999986}
-
-
-

As can be seen in get_total_spent_budget(), it is the budget estimated with estimate_smartnoise_cost() that was spent.

-
-
[19]:
-
-
-
client.get_total_spent_budget()
-
-
-
-
-
[19]:
-
-
-
-
-{'total_spent_epsilon': 1.5, 'total_spent_delta': 0.00014999500000001387}
-
-
-

Dr. Antartica has now a differentially private estimation of the number of penguins in the dataset and is confident to use the library for the rest of her analyses.

-
-
-
-

Step 4: Penguin statistics with opendp

-
-
[20]:
-
-
-
import opendp as dp
-import opendp.transformations as trans
-import opendp.measurements as meas
-
-
-
-
-

Confidence intervals for bill length over the whole population

-

She is first interested to have a better idea of the distribution of bill length of all species. She already has the count and average from the previous step, so she only needs the variance values.

-

She first checks the metadata again to use the relevant values in the pipeline.

-
-
[21]:
-
-
-
penguin_metadata
-
-
-
-
-
[21]:
-
-
-
-
-{'max_ids': 1,
- 'row_privacy': True,
- 'censor_dims': False,
- 'columns': {'species': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Adelie', 'Chinstrap', 'Gentoo']},
-  'island': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Torgersen', 'Biscoe', 'Dream']},
-  'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},
-  'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},
-  'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},
-  'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},
-  'sex': {'type': 'string',
-   'cardinality': 2,
-   'categories': ['MALE', 'FEMALE']}}}
-
-
-

She can define the columns names and the bounds of the relevant column.

-
-
[22]:
-
-
-
columns = ["species", "island", "bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g", "sex"]
-
-
-
-
-
[23]:
-
-
-
bill_length_min = penguin_metadata['columns']['bill_length_mm']['lower']
-bill_length_max = penguin_metadata['columns']['bill_length_mm']['upper']
-bill_length_min, bill_length_max
-
-
-
-
-
[23]:
-
-
-
-
-(30.0, 65.0)
-
-
-

She can now define the pipeline of the transformation to have the variance that she wants on the data:

-
-
[24]:
-
-
-
bill_length_transformation_pipeline = (
-    trans.make_split_dataframe(separator=",", col_names=columns) >>
-    trans.make_select_column(key="bill_length_mm", TOA=str) >>
-    trans.then_cast_default(TOA=float) >>
-    trans.then_clamp(bounds=(bill_length_min, bill_length_max)) >>
-    trans.then_resize(size=nb_penguins.tolist(), constant=avg_bill_length) >>
-    trans.then_variance()
-)
-
-
-
-

However, when she tries to execute it on the server, she has an error (see below).

-
-
[25]:
-
-
-
# Expect to fail !!!
-client.opendp_query(
-    opendp_pipeline = bill_length_transformation_pipeline,
-    dummy=True
-)
-
-
-
-
-
-
-
-
-Server error status 400: {"InvalidQueryException":"The pipeline provided is not a measurement. It cannot be processed in this server."}
-
-
-

This is because the server will only allow measurement pipeline with differentially private results. She adds Laplacian noise to the pipeline and should be able to instantiate the pipeline.

-
-
[26]:
-
-
-
var_bill_length_measurement_pipeline = (
-    bill_length_transformation_pipeline >>
-    meas.then_laplace(scale=5.0)
-)
-
-
-
-

Now that there is a measurement, she is able to apply the pipeline on the dummy dataset of the server.

-
-
[27]:
-
-
-
dummy_var_res = client.opendp_query(
-    opendp_pipeline = var_bill_length_measurement_pipeline,
-    dummy=True
-)
-print(f"Dummy result for variance: {np.round(dummy_var_res['query_response'], 2)}")
-
-
-
-
-
-
-
-
-Dummy result for variance: 27.36
-
-
-

With opendp, the function estimate_opendp_cost is particularly useful to estimate the used epsilon and delta based on the scale value.

-
-
[28]:
-
-
-
cost_res = client.estimate_opendp_cost(
-    opendp_pipeline = var_bill_length_measurement_pipeline
-)
-cost_res
-
-
-
-
-
[28]:
-
-
-
-
-{'epsilon_cost': 0.7163742690067888, 'delta_cost': 0}
-
-
-

She can now execute the query on the real dataset.

-
-
[29]:
-
-
-
var_res = client.opendp_query(
-    opendp_pipeline = var_bill_length_measurement_pipeline,
-)
-
-
-
-
-
[30]:
-
-
-
print(f"Number of penguins: {nb_penguins} (from previous smartnoise-sql query).")
-
-print(f"Average bill length: {np.round(avg_bill_length, 2)} (from previous smartnoise-sql query).")
-
-var_bill_length = var_res['query_response']
-print(f"Variance of bill length: {np.round(var_bill_length, 3)} (from opendp query).")
-
-
-
-
-
-
-
-
-Number of penguins: 342 (from previous smartnoise-sql query).
-Average bill length: 42.93 (from previous smartnoise-sql query).
-Variance of bill length: 27.686 (from opendp query).
-
-
-

She can now do all the postprocessing that she wants with the returned data without adding any privacy risk.

-
-
[31]:
-
-
-
# Get standard error
-standard_error = np.sqrt(var_bill_length/nb_penguins)
-print(f"Standard error of bill length: {np.round(standard_error, 2)}.")
-
-
-
-
-
-
-
-
-Standard error of bill length: 0.28.
-
-
-
-
[32]:
-
-
-
 # Compute the 95% confidence interval
-ZSCORE = 1.96
-lower_bound = np.round(avg_bill_length - ZSCORE*standard_error, 2)
-upper_bound = np.round(avg_bill_length + ZSCORE*standard_error, 2)
-print(f"The 95% confidence interval of the bill length of all penguins is [{lower_bound}, {upper_bound}].")
-
-
-
-
-
-
-
-
-The 95% confidence interval of the bill length of all penguins is [42.37, 43.49].
-
-
-
-
-

Count per species

-

She can also creates an histogram of the number of penguin per species.

-

She first extract the categories from the metadata:

-
-
[33]:
-
-
-
categories = penguin_metadata['columns']['species']['categories']
-categories
-
-
-
-
-
[33]:
-
-
-
-
-['Adelie', 'Chinstrap', 'Gentoo']
-
-
-

Then, writes the pipeline:

-
-
[34]:
-
-
-
species_count_pipeline = (
-    trans.make_split_dataframe(separator=",", col_names=columns) >>
-    trans.make_select_column(key="species", TOA=str) >>
-    trans.then_count_by_categories(categories=categories) >>
-    meas.then_laplace(scale=0.5)
-)
-
-
-
-

Verify it works on the dummy:

-
-
[35]:
-
-
-
dummy_res = client.opendp_query(
-    opendp_pipeline = species_count_pipeline,
-    dummy=True
-)
-print(f"Dummy result for histogram: {dummy_res['query_response']}")
-
-
-
-
-
-
-
-
-Dummy result for histogram: [38, 33, 28, 0]
-
-
-

Checks the required cost:

-
-
[36]:
-
-
-
cost_res = client.estimate_opendp_cost(
-    opendp_pipeline = species_count_pipeline
-)
-cost_res
-
-
-
-
-
[36]:
-
-
-
-
-{'epsilon_cost': 2.0, 'delta_cost': 0}
-
-
-

And finally apply the pipeline on the real dataset:

-
-
[37]:
-
-
-
species_counts_res = client.opendp_query(
-    opendp_pipeline = species_count_pipeline,
-)
-
-
-
-
-
[38]:
-
-
-
species_counts_res
-
-
-
-
-
[38]:
-
-
-
-
-{'requested_by': 'Dr. Antartica',
- 'query_response': [152, 68, 124, 0],
- 'spent_epsilon': 2.0,
- 'spent_delta': 0}
-
-
-
-
[39]:
-
-
-
for i, count in enumerate(species_counts_res['query_response']):
-    if i == len(categories):
-        print(f"Species Unknown has {count} penguins.")
-    else:
-        print(f"Species {categories[i]} has {count} penguins.")
-
-
-
-
-
-
-
-
-Species Adelie has 152 penguins.
-Species Chinstrap has 68 penguins.
-Species Gentoo has 124 penguins.
-Species Unknown has 0 penguins.
-
-
-
-
-
-

Step 5: See archives of queries

-

She now wants to verify all the queries that she did on the real data. It is possible because an archive of all queries is kept in a secure database. With a function call she can see her queries, budget and associated responses.

-
-
[40]:
-
-
-
previous_queries = client.get_previous_queries()
-previous_queries
-
-
-
-
-
[40]:
-
-
-
-
-[{'user_name': 'Dr. Antartica',
-  'dataset_name': 'PENGUIN',
-  'client_input': {'query_str': 'SELECT COUNT(*) AS nb_penguins,         AVG(bill_length_mm) AS avg_bill_length_mm         FROM df',
-   'dataset_name': 'PENGUIN',
-   'epsilon': 0.5,
-   'delta': 0.0001,
-   'mechanisms': {},
-   'postprocess': True},
-  'response': {'requested_by': 'Dr. Antartica',
-   'query_response': {'index': [0],
-    'columns': ['nb_penguins', 'avg_bill_length_mm'],
-    'data': [[342, 42.93065072561188]],
-    'index_names': [None],
-    'column_names': [None]},
-   'spent_epsilon': 1.5,
-   'spent_delta': 0.00014999500000001387},
-  'timestamp': 1717666971.0667355,
-  'dp_librairy': 'smartnoise_sql'},
- {'user_name': 'Dr. Antartica',
-  'dataset_name': 'PENGUIN',
-  'client_input': {'dataset_name': 'PENGUIN',
-   'opendp_json': <opendp.mod.Measurement at 0x7a92cbffc5f0>,
-   'fixed_delta': None},
-  'response': {'requested_by': 'Dr. Antartica',
-   'query_response': 27.68648927291686,
-   'spent_epsilon': 0.7163742690067888,
-   'spent_delta': 0},
-  'timestamp': 1717666974.674265,
-  'dp_librairy': 'opendp'},
- {'user_name': 'Dr. Antartica',
-  'dataset_name': 'PENGUIN',
-  'client_input': {'dataset_name': 'PENGUIN',
-   'opendp_json': <opendp.mod.Measurement at 0x7a92cbffc9e0>,
-   'fixed_delta': None},
-  'response': {'requested_by': 'Dr. Antartica',
-   'query_response': [152, 68, 124, 0],
-   'spent_epsilon': 2.0,
-   'spent_delta': 0},
-  'timestamp': 1717666977.146055,
-  'dp_librairy': 'opendp'}]
-
-
-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/Demo_Client_Notebook.ipynb b/html/de/notebooks/Demo_Client_Notebook.ipynb deleted file mode 100644 index 2ace8fd4..00000000 --- a/html/de/notebooks/Demo_Client_Notebook.ipynb +++ /dev/null @@ -1,1412 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3f18d338", - "metadata": {}, - "source": [ - "# Lomas: Client demo" - ] - }, - { - "cell_type": "markdown", - "id": "1582a2ae", - "metadata": {}, - "source": [ - "This notebook showcases how researcher could use the Lomas platform. It explains the different functionnalities provided by the `lomas-client` library to interact with the secure server.\n", - "\n", - "The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.\n", - "\n", - "Each user has access to one or multiple projects and for each dataset has a limited budget with $\\epsilon$ and $\\delta$ values." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "23bb4f13-7800-41b2-b429-68c2d02243d0", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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P/i1WfzOMjsFhZBuOCe6juo8qnMqAABR0XeGX/z8D3YMQDEioEOr+fPfsEOJXnlloR1CsXhr4VLnJj5fIZ1+nYsHc012CoBC+MtfnkqozpoOBUAP/flJO4Z8Uxiabjjn9ecHn8j7DysAACSj7/b8YAQULwI6OJ5/boFzkxdEJTlU/bWcqAixqlO6f1FUb82WbuLvueePdgxAOXh70bt2KE5V2z/xiQ8lnK++dPvN5ripE+0z4nSephRdYSx6K3EfqcrQNde833z7O//Suo++9n8/by686Ez7jDhdY59/foEdAwAge/qe4P2O4P3TdWnOB893mlrwWvT2UjsEoNjQBl0BlEIbdKoyqVJZLt38ecd1o3HVVZfYsY4rlrbfikW620PPW7QouNRGPtosKNR+og264kIbdOEKdR73fyb05TqsDdB//ep3EkrbJTsX6DM9b97LZtmy1a0/xtTXD4md6yeZc889PWU7o25nCMuXrUp4T81j3PjR5uyzZwV2hJBuGyupzgXex3U++sAHzzN/e3xe63VKy3HBBbPN6WfMdMZFpdWeeur52HOWtC6zblLGjx9pZs8+qcPnTf86KUD1vq+XvypssmuptvGjj8519pFbelnhn5b34ovPCu1owr88Ombuv/9hszC2bbSv3XmoiQSXttfXv/FFO5ZIP9j98pdtVaD0feALX7jRjrXp6DHVkX2ZD7RBB5Q+2qBL7zu6/1qU7DUdvRaJ+9pknVulao8122u3e21qbNyUUBPIXYewtrz921XX62FDB5u//W1e63LoenjBhbMT2vDL5fej0aNHtFt3zWf27Fl522733/+IefyxZ+1YfDlWrFhj5se+M2j7aR5TY+t91VWXtl7Xw7axlrW+oa7g7aVnqtjboCOgK4BiD+j0gf7SF79px+InH32QvScpndS+852v2LHk9OV+wSsLW79suydEfbBXrVqT9KISdHLUF/1UJyv3pOSeTETzPiP2BT/oS346Fzf/TYQed09I3hsSd3t15ESUznJ4qYTMt+/8mR2LO2nW8YEdOLjbZMXy1QkXANH7BPXm5F+eIP5lDLsYuxeFZBfjoJtyraNu1txt7F4YZp95UtJGbTuyvl7F8KUiagR04Qp1Hle7j94vOzq/XHHFhVn1AvrjH/82IZzx0zF67TWXhQYiqgqj6pnuOS+MSpH5PyP5COh0/hfv51S889Z5RG3zJVtmbdvrr78yZTjp59+eWh6VUMjm861trGYFkgkL9/zbWNcE//7Wa73nJVFJzKBzqn/9gvZrNsdUpvsyXwjogNJHQJf63kHSDeiyuRal81oJu2+RbK/dqa5NrqDrmn+76n2893uia5u388Rcfj/S94gFL7/Z7nroytd28wd0Qd8hvMdLNtu4WBR7QNf5azF2uCgdOHDADpWu9c1H7FBxevLJ582SJSvsmDGXXnKOOfnkE8w8T7VX/W8YXmeGDYt/mQ6jD+3DDz9tNm/aZqfEX6uTjeY3ZMjAhPcaMKDWCdFcGzZsjp2c2tq8q+7ZwzzyyDNm0VtLYxfhPXaqLsh7zKJFS82GjZvNmDEjnfbYXn/t7YQT0/bYTe7rry82+2PH0LHHTrBT4/zv418Oeeihtjad9PjKlWvNH+5/1Fm3Q4faLtYa17yadjabadOOsVPTk85yeA0aNMBZn+XLV9spxmzbvtNccsnZdixOJ+pvfevHZsk7K5zt4Kdp2g9vv/1ubJmPNdWxC474lyeIdxl1YfrhD/7H2b/e/SPaRto27n468cSp9pE23m0sPXtWm5/+5HcJ21j/Nf8XX3jNeXzs2BHOdK+Orq8r2XromEq1HuJdBu883O2g7bpy1RozceLYdu9fLOr70OpBmEKdx/UjhvfLmY6duXPnO22B7t691xw+ctg5D6QrnS9SOkZ1rgw6x7tfuN3PYzL6jOj8MGJkW5ioH2y8n8mTTjo+8DriPxd8YM55dijO+7g+X/7PqYL0G2640hlO54uqaNuuWrU26Tk3yI7tu5x1dWlZ9PnWNW7jxi3m8OFDKa+VXune1Oi8H3Q9829j7/Hjuu6jc0zvXr0Slrtr126B16xf/vKPrftbNyPXXTcn4ZyV7TGVyb7Mp3I833Xv3t0OAZWhEPeJxXYfl8m9g358/tvfnjN/f+oFOyVOJZ395/9srkUKt7wlr5PRNSroWpbttVtt+P79qRftWHK6Fo6fOCrh+5R/u+p9/N99ps84NuEeKJffj3Sf4r8eemm7Bd0HZbvd3n57WcJ9ZdB3CJVq1/sqzHs29p00HVrndLKDKOTq+p+vay4BXQEUe0D305/e1/qh1pfxT336OmdYNxqr32t0hqWqqio0nBD9OvPiC6/asfZ0AvOGc+K/qASdHJOdcHQSWfDKW2bz5rZA0E8nnROmTzZ9+/axU9K7uHlvInQC9W6LIHrcf7JPJZOLrKuqU5UTVrm0Xb3vq1Jc3/ver8zu5t3OeDK6ECh0OuWU6c64f3mCuMuoC4LCtHRoP2m5J04cY6fE+W/KvTePQYIucNmsrxTDl4piQUAXrlDn8cGDBziBnJ+OXZ0/9dn/2xPPmbVr18eOt/0JnwU/HdsP/vkJOxYvrfSx6y83n/rUdeaM2SeZnr2qE87Jy5eviX0JO8OOxT9bP/rRbxK+fJ551snm1luvN1dfc5lz3ln93rqEL5TvrV6fMI98BHQu/dp8+5c/6TzX+54//9nvY5+1rXYs/mv/LbF1DlpmLVtY8B9Gz/X+gOXSuK4DOodqeRXI727ea/r16xsayvu3sa7BH/rQxea2L97orJfOd+8sWdn6eND1zL+NRb+Af+G2m5x11nz0fC3HE7Fjx7Vr1+6E7Saa14IFbdcA782IZHtMSSb7Mp8I6IDSR0AXv47pvBr0p+8T3vDF9fGPX5FwHcn2WuTv4Eqlp274+JXONUjXhk6dOyUsR2Psufou7r02Znvt9t7Pive6oqDorbfeTfg+06dPTcL3+aB7IG2HT95yjXON03zc++B8fD8SlXL7dOw+/GPXX+FsX5Ww885j//797e4hst1u/oBOdG3/4pducpZD6+0+Xz/gudtYP6appPytn7vBeY6Wd1ts/rrXcTXt2FWU9zzFHtBxN1bhFCx4q1OpKqFr5kmJ1UvUno1OSEH0C42/fTSdZNRQtqoqqSqNW5UlUzo5qrqL5qP56YTg5daPd5+j/xr3euml4B5qM6WTr7tOuvj438ffeHg+BFVJatnbdkF69ZW3Evaptru7zGqbSDduXt62BlWlyN2GXirarOn6c4s4z3v2Zee/S/N1G0nX+/n3t7/x+zCaj7u8Wg7/fP78YOLNXTbrK96bUdF+dddD/3Wh89KXHf/nQD1oer8U6DXuPPzHvi62+tUNCDJiRL1zDCajY00lmBQsq0qsvhgHUXV8r5v/6erWaoOq4vD+95+bcHzrc6QvjC51aOA9rnVOv/76y1urR+hcpHnqnKzPmZb785+/3nks3/R+Wn4/XdP81T1VFce7zAquvOfuefPS+zXY68abPtTu/O+nc41+Xf/qV7/j/IAVdP30b+MPzjk/oUqIqojeeOOH7Fic2rpJRvtD1WD81Vg0rn3o0v7W9vJa9HbieXqK78eIbI+pIGH7EgCQe7pW67uGV7bXoh2+69uME49rvQbpv67DupfQNUjB2Wdv/WjCNSoX1259/9C89R56L+91RdepWbOm2bH0aTsENbeQj+9Heq7ae3X3jf6r5JrX2saNdiguX995tOz+Y0S891s9q7snNGOh56sKrXedP3LdB+yjyAQBXYXzhyxq1NGlD7U3WNCJSGFIEP8Ng06M3pOMe4LQhzZTuiC4J0fNb1bsQ+/nfY7+n3XWLGfY5f+FoiN0stHJ110nXbj8J3u1fxYFNebp0nKp0XKFidreuli5yxy/SF7qDGdL89HJV9tF7+NtPFTv57+opEMXON1Yusurfem/sPhv+LJd32L4UgF46ZhWsKvjLhV9HlSiSdUO/bxhtOblfi68/CXHVqxca4fah+pqR9FP81T7pPrcarmD3iMf/MGRy/8jifea5tJnc3hDnR2Ll4wN+/EpjD7XaodGn/dUdO3UD1hqisH/Pv5trG3o5785UEPUyUydOskOtTfTVwp+wcuJ13T9EOfS+dTfhly2x1SQsH0JAMgdndP1fTnoOpPra9E3v3m388OUSl279AO/7g0VnOka6pWLa7euR5q33iOofb3q6sxLO+meIEg+vh8F3d/6r8HeUFDy8Z1H9/6pllU0H3VWph+J3R/79F5RfCcsNwR0FUwfTv+Xcf/Jd+ZJiScLtYMUZP26xEQ/qDirPrRBJ59kgpZJPep4KfhI9Rx/CNMRQSe9sWOG26HiohO6wkSdJP3bRvshFzQfnXx1Etb7+Oer9gMzpZ6R/OLHTWIQun7DZjsUl8v1jeJLBeCn40zH3Zduv9kJn/0lSf1Uos5bMtN7/IpCZDXO7//zdwrjPZf7f6n1f7ai1H9g8I89/h9JtH5B6+0N1UVt/2VKn2ud/1RiV+F9qkBVn3v1rurlX46gZdWfl+aTTL9+bdWW/HSu9P5YsHDhEjsUr27j/fLvD/pycUwFCduXAIDs6fuDvkvoO7I/8HFley1SR2xe+vFQP0y512AFOWq/zF9q25Wva7feT9+N9CNmUPMhyehaGXYPkY/vR/4ft9KRj+3Wf0D4NVmFFbx0DOhHYnVgqPf57nd/4QR2qlmHjiOgq2AqDZfsy7j4GxDVBzzoQ+f/4E8O+UU805PPwICThD/48QYfro6EQ6kEnXzz8T75oDBIN1e6OOoimS86NnSTp4Dr3nv/aqemzx9+ufxBaKqSipmsb7F+qQBEnwmFz1//xhedIEilVvUFyRuyuB7/2zw7lBv+X2qLSdi5Igr6hVjhvQJVVW1XCQW3ZLGfgtR8f3FNFeZ6f/DQ+c49t6n3dS9/Mxf5Ukz7EgBKjX4cUpMqbrMqQSGKmmLJ5w/DugYmu/ZoGR5/7FknyFHvpfm6DmodFRDpPfTdW++npiZ07c30O03Q/aWrmL8fZau+Pny9r7jiwsDvny6Vsldg97X/873Qpj2QGgFdBfOXhgv6Mq4bD3+pgHTac8u01BKy4y8xKAqo9IuR2qj60he/6QRHujimKn2RKf0ypV9MPv3pf3NOyGoXSwGXbvxyJZ0gtKPrWyxfKoBUdD5WqVWV5vzRj/7dKbXlpc9ctl+G/EFypfA2E5ANXftUQsEtWRzUJuqa1evtUMf5S7Nlwn+tVzVXHTfe6qs6J+YqOKvUYwoACk3nbX1H8De/oPPwTwKawsiW91qkHxL1vSToxykvLcu3vvWTnIQ33mu3fmxSm68KiNzrjq5lqoXgNoWDuI5+59H3UDXvoW2ZLKgT3Qvm45irBAR0FUohg/9Ls1s81f/nf978+W/aIUQhKCDyB1gKqhRQ6Rcj3bTrJOq2oaZf13JBF1aVLtMvU7qx069JuijrS4FK+eivULJd36i/VACioNt77k3VmUhQuBxWMtP7C3uqP5f/y1cujvtCczsPSvWndiPToV/mvftI555kVPI6VRV9r6BlC/rLpjqNbuC8x42quS72dQ7hb94iSEeOKQBA/ulHIv/3A32HVY2QdASdx4P+/NcifS/Rj1P67q3v1WFNP+ieQR0thMn02q3vJz/8wa9bS7bp+4vmoe/3qoWgH8460gZdmGL9fpTr7zxB9EOktql+KNb7JWuGRcdcWA0khCOgq1DZ9GqqAMT/673/REUpo/wJ2nfeC6QuvgqqXArM9GtHWBtqHaVfRbyl03Qh1kXZbRi0/4DMS1GGXeC8vdRKD89FNlfrG9WXCsA1duxIOxSnKqvJvvTpPOsvITpoUH/n/+jRic0JbN2WeYlWf/UOf4jTEf7PsmRTGsxvmK9qxvZtuf3SPG584j7SuSfVl09vO2/Sr7bt3Oj/Uluoa6c3gNM1/W++6tH+5i0kF8cUAKAwrvlI+x/KVSMk6JqV62uRvnvre7WaftB3Xn239n+v9na0kO2129+rqjoLzOaHrFTy8f2oI/L9nScVbWO3GRbtZ5VU9Bd28LfVjdQI6CpUtqXgFixIrB7rP1GFVeFZtIgUPRu6Wfc3cupva8L/uAKzXFc51sXdW7JSF11diLMVdoHz9wLobR8h1+tb6C8VgMsfiig4UaclKknn/bKsYU1TaU4vfSkaYXvM0mfA+4Vb80pVIs/P3z7jc57ek71UklbVvxWWt//xJvEX66AePXN5XfC3V5nrdvl0fvB/+dSv9v52KnWuVnuc2jba9l4TJ422Qwr82obl0Ufn2qH88h9r3qBX1xT3OPLKxTEFACgMXa8uvOhMO9ZG7dH5ZXst0vVPJcxVEyCo7Wcti78DQe/3g2yv3fv27bdDcS0tieOSyxpgufh+lAv5/s7jp3XQumidgmoQqKSiv037Hj1yV3KxUhDQVSDdNHhvGPSF2y3VE/anEkVe6v3VW7IjnROVnj9/fsdL7lUy94ZcbSt4fyESf9fe/sf9JXBycUPlLwWzd2+LHWrjD3HT4S/FIUHHzZTj2o63bNc36i8VgEuhiL/dGJ2rVY1c7Tu61So1rGn+Y9/fS/bs2bPsUNyfH3wi4fOgYz/Zl8fTT0/s8VOhvL6QuWGh/mtc4Y4e0y/zqmruDar8jQ3rs6xrkOizqvdVOyW5oi+H3gBN20+fbXeZ9Z76vKutSi27tof7WLouvCCxt2ntB7edSncfqR1MtcfpL+GosN8bfp19duI+Uok8bRP3POZuY+0nNbisbec/x3WElsH/445ryrHB0yXbYwoAUDiqreEvHafrtf+7cTbXIj2m65/aflOTN7ru6brr/S6ga4H/O773WpPra7e+a7jXHy2Hrknee18JCvHSlYvvR7lQiO88Lr1W66B10Tq5x4h3fnqO/54tqEQ+kuv8tRg7XJQOHDhgh0rX+uYjdqg4PPrY3ISbhksvfV/K3lUHDRpgnnjiOTtmzKFDh8yAAf1aX6fH58VO1pou22MniJWr1jgXhb59+zgnyV/99x9iJ47EG4sBA2oTwo8NGzabBS+3BTv+xyUXz0lnHg89lPgL0wfmnGeH2qQzn2T8r9d20/v6/1RKbNGipa3b16UbrMsvv9COxen5Xk07d5kxY0Y6F6Inn3zePPzw0+3mc8bsk0y150LjX66jsb+ZM6c681i8OH5x8T7e3LzHVHWqMhMnjnFO1A8++JR58YVX7aNxavvhnHNOtWNx/mXVfLzHjS5kP/vp7xKOGz324Q9faseyW18t6x3f/JFZsmSF2bxpW+v7962tcY5p0bH7SGweesz1vnNOaz32R4ysd459NyzRf+866OKo5fjpT+9z2gfbsX2X6dq1s/NYsanvw282YQp1Htex+/bb7yYcb+nQ8XbrrdfbsTgdo/q86rwi+hzoPOKeV1584TXnffT48uWrnc90w/A6M2xY/Mu8PiPdunZ1XuPStUPnI71e//0BlALGiy46y44ZU1PTK6GUq5bh9dcXO6/XNUXvG8R/vvV/zoPOx66BA2sTzk/6bLvLrPfU512fUy271k3/Mzlva7tu2Li53bqnoi/zn/70RxI++xreH/ue490OGtZyerex9tPq9xqdbdepcydzrKen9PiNUtuNx0knHd+6D5PZ3bw3Yd+KlvG66+YkXA+8sj2mJJN9mU/leL7r3p2SCqgshbhPLLb7uEzvPeqGDnLOzV7LV6w2p5wyvfVcn821SK9t2tnsTHfpuqv3dK8NWl7v9xp9Z7np5qvtWFw21+6g7xqal16r5fBeI13dunZJ2G6ZbNdcfD9K99qt+Xn5r5nZfud5++1lCft93PhRCd8xXLr+q4NJ737U69z30p/m773nUvt0Z56ZGP4Wg1xd//N1zeVurMIoLPC21yUzTkxdR19VW/y/tnt7gdXjF16YWKpAv6K4pT6UuPtPVMiOLm7XX3+lHWvjL4Gj/a3SHPrTr1tukOTlb1je39aQfpFx5/HE355z2hzwV/PSvN3SPUElYtLd/97jRr/I+V/nb1Mjm/UdMaK+Xa9Oen9vSRj/savtrjb2vK699jI7FOddB3c5tA21bCr59NvfPGSfCSTSufQLt90U2gZiEJ2b9Zogn7zlI2nNS8GMOnbxt9miY13Tvb8Uh9FyXHVVW3gu+oyl6jBGn+FM1jcVrUO6y6z31TbKlKrSZ9IjnM4bn731owml51wq4ZDuvPS8XLVfqX3r30ZTY/tQx2Ay2R5TAIDCCarqqu/Gf/3r3+1YXDbXIrVDlu5rdT0M+s6SzbU7ne8aWj7vtWtt40Y71DHZfj/KlUJ853Fl8v1U3+10XCBzlKArgGL65UUleZSku3TCOOecU+xYcv5f25X6nzB9cmtpAJWe8v+C4qcTiHceHSnZlovnpDOPVL9YSCa/tgTxvz5d2m+3fOq6wBupVCVwFKwpTPUeB6qGpv3n0i9DYSVE3JJww0fUmddeW5zwS4mXTuDHTZ2UcDyMnziqtWSaeLexnq8G2MOCPF14PvShi83JJ59gp8Rlu74qep3quHW5Xyr8pUv0i1fPntXOL5Jh28PlXhzDSqhEiRJ04Qp5HtexofOISh5VVVU5x5b/11/38/Whqy4y77/s3NDjyTsvtdGya9ee1mNUn6nRoxvM7DNPMjfccKWZ4muqwKVfTY87boI5aqrMoYMH2/0KfszkcU4zCCrNG7Qcer37/vplV/Tex0weay695Bxz+RUXtvslOZsSdKL3VOkA/cLfErsRCVvmqz58aYc/izp36Bqo7aJf4vfG3sf7+Xff54ILZpsbPn5lwrnPz52X5rG3ZX/CDwvuvv74x68I/CW6oyXoZO3a9QnnXG2TVK/N9pjKdF/mCyXogNJHCbr07j1UIspf+knfe/3fy7O5Fvmvid7rknvNVw0UlZwLu+5mc+0O+64xfcaxzmtUek3bzi0tpuuWvl/pddKR7arXdvT7Ua5K0Ek22y3dEnTivf7r+6l430v3XPqhT++Vi7bJ86XYS9BVHY2xw0WpubnZDpWuBeuS37AXktoO8H4ZV2DmLw0URqXvVBrIS7/I+H/NV5XAefNedkoRiU6O48ePdNpKU8qvUkUunezVGL9Lr1WJJZf/ccnFc9KZh3c5RW3x+aUzn2T8r09G81aHBDNPOs75NSwZ7aunnlI7AG86JbdEJ2j13Kd2ExTseddPJ1T1YOoVNo/6hjqn9IioiqgaklWbhO5FXOGh2pXQceVfP/2a4r5Wgo4FtV+w6O2lCcePTvYXX3xWYOkTycX6al2eeWa+Wb9uo9O2gss9ft11SsZdjrcWLkn4nLnbTdWEi7k0ycz6LnYIfsV0HgeQvXI839XU1NghoDIU4j6R6z9QXnJ1/c/XNZeArgA4sQMoBQR04TiPA+WFgA4ofQR0ADJV7AEd9ZkAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARKjqaIwdLkrNzc12qHQtWHfIDgFA8ZpZ38UOwa9xc+lfiwC0aRhcY4fKR01N+a0TkEwh7hNr7rzLDgEoB82332aHspOvay4BXQFwYwegFJTjDWuucB4HygsBHVD6COgAZKrYAzqquAIAAAAAAAARIqADAAAAAAAAIkRABwAAAAAAAESIgA4AAAAAAACIEAEdAAAAAAAAECECOgAAAAAAACBCBHQAAAAAAABAhAjoAAAAAAAAgAgR0AEAAAAAAAARIqADAAAAAAAAIkRABwAAAAAAAESIgA4AAAAAAACIEAEdAAAAAAAAECECOgAAAAAAACBCBHQAAAAAAABAhAjoAAAAAAAAgAgR0AEAAAAAAAARIqADAAAAAAAAIkRABwAAAAAAAESIgA4AAAAAAACIEAEdAAAAAAAAECECOgAAAAAAACBCBHQAAAAAAABAhAjoAAAAAAAAgAgR0AEAAAAAAAARIqADAAAAAAAAIkRABwAAAAAAAESIgA4AAAAAAACIEAEdAAAAAAAAEKGqozF2uCg1NzfbodLVuLn01wFA+WsYXGOH4Md5HCh+r8+fa4cAoHCufWeZHQJQ7Jpvv80OZaemJj/3TZSgAwAAAAAAACJEQAcAAICS1ryryQ4BAACUJgI6AAAAlLCjZuumdXYYAACgNBHQAQAAoGRt37rJNG3fYscAAABKEwEdAAAAStKRI4fNjm2b7RgAAEDpIqADAABASVI4t6tpux0DAAAoXQR0AAAAKDmHDx+i9BwAACgbBHQAAAAoOWp7rnnnDjsGAABQ2gjoAAAAUFIOHNhP6TkAAFBWCOgAAABQUhTO7WneaccAAABKHwEdAAAASsb+/S2mafsWOwYAAFAeCOgAAABQMpq2bTZ7d++yYwAAAOWBgA4AAAAlYV/LHtqeAwAAZYmADgAAACVB4VzL3j12DAAAoHwQ0AEAAKDotezdTek5AABQtgjoAAAAUPQUzu3f12LHAAAAygsBHQAAAIrant27KD0HAADKGgEdAAAAiprCuQP799kxAACA8lN1NMYOF6Xm5mY7VLoaN5f+OgAofw2Da+wQ/DiPA9HZ3bzTrFr2tjl08ICdglIxePBgM27cOFNbW2uqq6tN9+7d7SPGHDp0yLS0tJg9e/aYdevWmaVLl9pHgNJy7TvL7BCAYtd8+212KDs1Nfm5b6IEHQAAAIrW9q0bCedKjIK5888/35x77rlm1KhRTkDnDeekS5cuzg1OXV2dmTFjhrniiivM1KlT7aMAAFQeAjoAAAAUpead203T9i12DKXgxBNPNGeddZYZOHCgnZIeBXhTpkxxgr1evXrZqQAAVA4COgAAABQltT13+NAhO4Zip3BuwoQJTum4jlKwd95559kxAAAqBwEdAAAAis7OHVvpubWETJw40QnngqhN6UWLFpmHHnrI3Hvvvc7fq6++ajZu3Gifkahnz57mnHPOsWMAAFQGAjoAAAAUHYVzR44csWModqqeGuTdd981f/3rX83ChQudDiFc6hTi6aefNi+88ILZu3evndpGbdMp9AMAoFLQi2sB0PsfgFJAL67hOI8DhaVwbvWKd0yRf02F5VZt9VM498orr9ixcOpUQu3W+avGNjU1mUcffdSOxSm0U6cSXo2NjWbevHl2LNFll13Wrrc9ld5L1mus3mPs2LEJPc/u37/f6XV28+bNKdfJ/566n1FIeeqppzrrqhKCovXT/PzbTj3c3n///XYsmEoYKsT0UilFBaEoLHpxBUoHvbgCAAAAaVKpOXUMQThXOhoaGuxQG4VS6YRzopAqqLqren8tZIcRei+FawoA/T3PaljTFKbNmTPHjBw50j6SntmzZzs92rrhnGh+Y8aMaVcgQUFlqh5t+/XrZ4fiFOoRzgFAaSOgAwAAQNFQOEfPraVDoZY3dHKtXr3aDqVHpdqCHHPMMXYov7Qe6pwinVIRWt9Zs2alHdJ169YtMMQUlaLbsGGDHWvjLx3npff1hoei+QAAShsBHQAAAIrC4cOH6BiixIQFT5mW5lL7dEFN2wSFf/mgKqOZvJdKualqbzol/PxhmpdKDr7zzjt2rI1K14VR9Vu/ZFV2AQClgYAOAAAARUHh3K6mbXYMpWDAgAF2qI3aa+sIbycSrr59+9qh/FHQ5i85pyqjakPP7XlW7bv5O7NQ8OZvDy8TbrVUrffWrVvt1Lhk1Vz91Vu1vTMtsQgAKD4EdACAtB0+fNi8/fbbTkPZpUrLrhuigwcP2ikAisWWjY12CKXC37GDHDhwwA5lRoFVFNRxg9+bb77ptKHnhoa6bjz55JPtlnHQoEF2KDUFfE899ZQT+OnP2xFEUBt8QdVcg6q3btlClXAAKAcEdACAtGzatMk888wzZuXKlXZK6VJJg7lz55r169fbKQCKQU2fxJJBQCH4q5MqSAuqMhpU0k1hWVDA56dgTwGfOsQIogDQX/IwqJprUPXWJUuW2CEAQCkjoAMAJKWGp3XjsGDBgtaScx0tHVFM9u3b5zRK/vrrr5tt26hSBxSD2v6DTNeu3ewYkH8TJ060Q2127dplh9rzB3SSrEMHl66lQVV4vXbs2GGH4oKqufbp08cOxandvrDQDwBQWgjoAACB9Gv/qlWrnGo+KnF29OhR+0h5aWxsdNZx+fLlZRE8AqWsd59aUzsgdWkkFI+gaqnqtbQjevfubYfapAq18kGB27XXXhv4N2XKFPusNsk6dHDt3r3bDoVTO3d+3vBPJfX8HVkE9QALAChNBHQAgHbUno1CK90sJCtJUC50A6he9LTOqsoLIDr9Bgw23br3sGModkElkFXtM53eTf2qq6vtUBuVdi4H6ZTUVkk4f0cU3vBv3LhxdqhNUA+wAIDSREAHAGilKqxqd0dBVSW2z6ZGut944w3nhsd/kwSgMHr17uNUdUVpUCnkIEFtpYl6TL3sssvaVS1V6TB/5weybt06O1QZ/NVVVc3V3Vb+tu5U3TaKEoYAgPwgoAMAOHQTpHDq3XffLeleWrOlaq6q7qqQstJuDIFiQSm60qGAKOgHDfU2GkTTa2pqzIwZM5ygzn3etGnTnP9e6jRBTSyk0qNHbo8VhY5uT6vp/M2bN8++Mnu69vjV19cHVm8N6vkVAFC6COgAoMK17N1jGlfHA6mgxq8rlbaFtomq+e7d02ynAiiEnr1qTP8BQ+wYil1QKTqFcCot56Vxbyk5Pee0004z559/vhk4cKCd2iadcE6CSt6JqtnqPZIJWvb+/fvbocJT4KkOJbz69evXrnqr2v5TB04AgPJBQAcAFUqdPmzbvMGsWbXUbNnYaA4fPmwfgUvbRB1lrFkZ20ab1pkjR47YRwDkW+2AQaZHdWKJIRSnV155xSnt5jdhwoSEkE7NBygQ83csERTOqVSe5psOtV0X1OadSumlElQCUCXVgnp3LRR/aKgAsqGhwY7F8YMaAJQfAjoAqEC7m5vMmpVLnHBu7+7y7wQiWy17d5vG95aZtbHttXtXYskGAPlR3bO3U9UVpSGoB1JRSKeqrFOnTnXGVR107ty5Sdv5VID3+uuv27FEQSXe1E7bmWee2dpGm/6fc8457UKtMEHzPP74482pp57aGvzpv8LGq666ylkfzV/r5G8XLhdUMs4fYmodvVasWGGHAADlouqoilAUsebm0q9W1LiZqlEAisPBgwecUnPbt240+/d1vJ252bNnm759+9qx0qL29Z566ik7lrnu3atN/0F1ZsCgoaZrt252KoB82Neyx7y3/B0nJEfxU4ClQC5bCqfUvlpY225z5sxp1x5bJl599VWnQySvjs5TJQcff/zxhM4aFOD5q9YGvWcyCgDr6ursWCK95wMPPGDHELVr31lmhwAUu+bbb7ND2UnVfEJHUYIOACrErqZtTlXNDY2rsgrnKt3+/S3ONlyzaolp2r7FTgWQDz2qe9GjawlRlVR1NOQv/ZUplRZT6bcrrrgioRSba+XKlXYotXR/7FeJvUyXW8/XOuejJ9VkJeQ2bNhghwAA5YSADgDKnMK49WtXOuGcQjrkxq6m7U4V4XVrVjilfADkhwI6VXdFaVBgpSqsuWgjTW2vjRo1ql1bcqoCGlQt1U+l8BQYpkMdUsyfPz9p1VsvlWLT89PtyCJTmm9Qu36insYBAOWHgA4Aytj2rZucYG7T+jVO9Vbk1uFDh8zmDWudbayqwwByTx1FqMMIlI7NmzebJ554wmlO4L333nN6JfUHXyp9ptJtCtFeeOEF57lBpd30PFUP9VP1V01XEOgt+ab3cef59NNP26npUSj24IMPOu3pab7+gEzz1nSFfqpimq9wzhVUUk7bSNsXAFB+aIOuAGiDDkChHTp00OnUYMe2/HyJnz59ulOyoRTphuu1116zY7mlkj4NI8eZrt1Kc9sAxUpVy99bttjs3cN3qnKnKq2q3up2iqCScmFt0ZU7bQuVIPRSOJhu77YoDNqgA0pHsbdBR0BXAAR0AArlyJHDTkmubVs2mJa9VLuMgtrMcjuR8Pe6B6DjVFpVVcpR/tTmnKq1Dho0qF0HDJVC2+CSSy5pdx156KGHKnJ7FDMCOqB00EkEAKAgmnfucKpaNq5eTjgXIbVHt37NCrN21dLYPtlupwLIlkqo9urdx46hnCmAUqk5VSOtlDDK2xHGyJEjzXnnndcunFNVYcI5AChflKArAErQAcgntS3nlpo7sH+fnYpi0K1bdzNg8FCnNB3VXoHsUYoO5eqyyy5LWSJDbe4tXbrUjqFYUIIOKB2UoAMA5E3T9i1mzYolZkPjKsK5InTgwP7YvnnPrF65JG/tAQKVpN/AIaZXTV87BpSPw4cP26FgKj1HOAcA5Y2ADgBKUMve3Wbd6uVOldZdVKMselQ/BnKja9dupt+AwXYMKB+7d++2Q+2p99hnn33WjgEAyhUBHQCUELVKsHXzeifs2byx0Rw+fMg+gmKnDjy2xPbZmlVLnSrJsb0ZfwBARhTQ1fSptWNAeVAIp17GXYcOHXKa+lGvrQ8++CBtzwFABaANugKgDToAubCneafTzty2LRvtFJSy/gOHOO3T9a4haAAypZBbYTcARI026IDSQRt0AICsHD50yGkY3Sl5RThXNrZv3eSUhNy0fo05dOignQogHerRtU/f/nYMAACg9BHQAUAR27ljm9PBgHot3Ney105Fudi/r8WsX7vSCep2Nm2zUwGk0rlLF1M7YJAdAwAAKH0EdABQhPbvV3Czyik1t3PHVjsV5Ur7WL3xKqxTaAcgNacUXS2l6AAAQHkgoAOAIrNj2yYnrNm0frU5dPCAnYpyp2ququ6q0nTbt1KVGUilc+cuprY/PboCAIDyQEAHAEVi755m0/jeMieg2d28005Fpdnd3OQcAyo9uWf3LjsVQBD16Nq330A7BgAAULoI6AAgYkeOHDFbNq1zQhn91zgqmzpYd3up3LKx0Rw5fNg+AsCrU6dOTlVXAACAUkdABwARat65w6xdtdQpOdeyd7edCsTt27vHNK5ebta+967ZvavJTgXg1W/AIEI6AABQ8gjoACACBw8cMBvXrXZKSG3fuslOBYLpGFFvvjpmDtIuIZCgqqqTGTik3o4BAACUJgI6ACiwpu1bnLBlQ+Mqc2D/PjsVSE7Hio4ZVYXWMQSgTU2fWjsEAABQmgjoAKBA9u/ba9avWeGUmmveud1OBTKzq2mbcwzpWNIxBQAAAKD0VR1VS9RFrLm52Q6VrsbNpb8OALKzbcsGp9F/euVELvXq3ccMGDzUDBg01E4BkI6GwTV2qHzU1JTfOgHJFOI+sebOu+wQgHLQfPttdig7+brmUoIOAAqkqqrKDgEAAAAA0IaADgAKQCWcRoyZaIYMHW46d+lipwIdo2NocF2Dc0xReg4AAAAofQR0AFAg3Xv0NMNGjDXDR00wfWr726lAZvrUDjAjRk809SPHmR7VvexUAAAAAKWMgA4ACqzfgMFmxOhJZmjDaNOtew87FUhOx4qOGZWaq+0/yE4FAAAAUA4I6AAgAl27dTN19SOdklD9Bw6xU4FgOkZGjpnkHDNdu3azUwEAAACUCwI6AIhQTd9+ZvjoiaZh1HhT3bO3nQrEVffqbYbHjg0Fub371NqpAAAAAMoNAR0ARKxTp05m0JB6M3LsJOc/vb1Cx8CAwUOd9goH6piIHSMAAAAAyhff+AGgSKgEnUrSqY2x3jV97VRUmt41tc4xoFJzvXr3sVMBAAAAlDMCOgAoMv0H1pkRYyeZIcNGmi60N1YxunTpGtvnI5xwTscAAAAAgMpBQAcARah792ozbPhopxRV334D7VSUK+1jhbLDho8x3XtU26kAAAAAKgUBHQAUsb79Bji9d9aPGGt6VPe0U1EuFMYplFOpub61A+xUAAAAAJWGgA4AilznLl3M4KHDndJ06jgA5aH/wCFOMKdqrareCgAAAKByEdABQInoVdPX6dVz+OgJpmevGjsVpaZn7z5O2Kpee9UhBAAAAAAQ0AFACamqqjIDBw9zSl4NrmswnTt3sY+g2HXq1NkMiu2ztpKQVfEHAAAAAFQ8AjoAKEHVPXub+pHjnKCuT9/+diqKVZ/a/s6+aojts+qevexUAAAAAIgjoAOAElbbf5AT/AxtGG26de9hp6JYdOvWPbZvRjml5voNGGynAgAAAEAiAjoAKHFdu3U3dfUjnRBIgR2KQ99+A532AuvqRzn7CAAAAADCENABQJmo6dvP6XiAapTR6lHdy6l+rH3Rp3aAnQoAAAAA4aqOxtjhotTc3GyHSlfj5tJfBwCl5dChg6bxvWVmx7bNdkpuTZ8+3XTvXpqlwvbv329ee+01O5ZbKsGogJQSc0Bxaxhcfj1h19TQuzcqSyHuE2vuvMsOASgHzbffZoeyk69rLgFdARDQAYjK9q2bzLbNG8zu5iY7JTdmz55t+vbta8dKS0tLi3nqqafsWG706t3HDBg01PbOCqDYEdABpY+ADkCmCOiyREAHANnZv6/FbN+60QnqDh48YKdmh4AurnOXLvFgLvbXo7qnnQqg2HU0oOu29Ek7BKAcHJh4nh0KRkAHlJdiD+hogw4Aylz3HtVOL6/qsIA20XKnT21/p2OO+hFjCecAAAAAZIWADgAqhHoVHTFmohk2fIwT2qFjunePB54jRk+i11wAAAAAOUFABwAVpGvXbmbIsBFOUNd/4BA7FenSNtO2q6sfabp262anAgAAAEB2COgAoAL1rqk1I8ZMcqpo9uzdx05FmOqevU3DqPFmeGx79e5Ta6cCAAAAQG7QSUQB0EkEgGLWsneP2bZlg9ke+zt8+LCdmlyldBLRuXNnM2LECNOtV3/Tsxc9JALlgk4iAAidRKTn+bVrzBn3/NKOJXfB2HFmTG0/c+2U48zpw0fYqcjEyh07zLdfetE8sXKFWbFjuzPtxKHDzIcmH2tunHaCGUDbxx1GJxEAgKJW3bOXaRg5zhx//PFm4MCBdiq0LbRNpkyZQjgHAACQhr+tWG7+69UFTqB39Z/+aLa17LWPIB2PLl9mTvzFT51t6IZz8sqG9ebLf3/SzPrlz82bmzbaqSg3BHQAAEd9fb2ZNm2amTBhgqmurtxOJLp3727GjYsHltomAAAAyNzvFy8yF977G0K6NKnk3HUP/sns2Ndip7Sn0O6KP97PNi1TBHQAgFYK5iZOnOiEU8OGDbNTK0ddXZ2z7sccc4zp2ZPqAwAAANlQya/PPPaoHUMyqtaaLJxzKaT74zuL7RjKCW3QFQBt0AEoBf42mQ4dOmTWrl1r1qxZY3bt2mWnxpVbG3S9evVy2ppz2psL6J2V8zhQXiq5DbqJtz5mh5IbP7S3Gdqv2px+zCBz/Vkj7dRg/nku/cFFdqjNxqZ95n/mrjbzFm82yzbstlONmT6mn/n4OaPM+cfXOeOLG3eZnz+10ry6ssls3BG/Ue3do4vzvC9cNsFMbqjsjo3c7dire2fzmYvG2alxT7y50dz689ftWHzb/u4LJ9ux3EhnX5cS2qBLj78NOrUz9/g119mxNnreY8uXmTteeM5OafPc9Z+gTboUqr7xNTsU9+OLLzWfnH6iU7Luw3/6gxN2usL2AZKjDToAQEnq0qWLGT16tFOibOTI5DdnpWz48OHOOqpaa1A4BwCVSCHavMVbzB0PLDaX3vGc+ce72+wjHXPTjxaYX/x9ZUI4J6+t3GE27NjvDCuc++j35ptHXt3QGs7J7n2HnGWpZArm7nxwqbnkm88523HP/vQ6dQIKSQHcN89+n3nj5lvslDb3LnrLDrWnAOrTjz1ixt39fSek0p+GNU2PhXGfq78Lf/cbZ9p9by9yhr3zufMfLyRUCdVzZv7ip63P0bCekw69Vm3r9f/2fya8h5ZVAWVH+V/7qRkznXBOxvTrZ757/oXOcCruMrl/KC0EdACApGpra83UqVPNSSed1No2XTkEWT169DAzZsxw2t0bMGCAnQoA8FOo9tmfvdbhkE4lu/zBnNeEYb2d/w+/ssEJ44KoFF0ll55TqTkFc2HbBygmxw+pM//5vsTSiQq2gmj62Lu/165TBA1rmh5LJzzbtnevE8xd8+c/Oh1VuDQfda7gtoWnIE3P8ZZGcztgcEO+IOqYQUGeXqu29bxVUd1lVSlDzb8j7cPVxL5ba5t9ePIUM7Zff3Pp+An2kbhd++M/ZLjUUy7KDwEdACAtQ4YMMWeffbYZM2aMnVK6VCLwrLPOqsh29gCgIxQM/e/fvOWU5PJTNUfvn98bq3baoThVvXz262c7z/3zl08zp0yI/0jy+qrEkjIfPm1E6zx//flZdirCqJqwu730l+vqrUAmLhgz1g7FKdDy9z6qcE6BVyoKz1KFdArZvMGcnx5XD6gK0sLo9UFBopb77F/fkxDqhdH8FQZmSqHm7aecZu67/Eqz/DOfMxePG28fiffsqs4jXP16VJsvnXyqHUM5IaADAKStc+fO5thjjy3pXl617CoR2LVrVzsFACqXN9Bx/35160nmcxePd0qteanaqUpyZeuE0f1MXW0PZzhZqbjTj2kr3Vzpbc8BpUaBk1/zgQN2yLSWZnMpdPrdB680R7/6Nefvkas/4kxzKaRLVt3VpXbb9PqtX7zdKY3m5ZbQc5+z4jOfNycOTfyx9lcL37BDbb789FMJJeb0Gi2f+z5abu+yKshLt8psMgrmVE31kvt+2/r+ep/fzLncqfaK8kMnEQVA4+IASkFHG02vBJzHgfJCJxFtFMiFUWm5D9/1UkJ7cHX9qs2z/36WHYsLm6faTFO1zGRufN8Yp+Sc2qJL5gc3ndDakYTrnrmrzeOvb0h4rUrnXXjC0NCOLfzLpPmqhN8jr8XbvVMoeeaxg8ztcya1hogS1HmFtsWMMbXmpnPHhAaI13z3pYTl07YJmpc65fjQqSPaLXe62/D2ORPT7iTCff931zcnVD12O+NI1jEInURUpnQ7ifDzt4GmKpwqJSY/ee0Vc8ujDzvDopDr6mMTAzX/+37ltDOcNu5cyeYv/tdLqueoeqlKsLkUkikgc+nx+Z+4yQyoTuztX6Xspv3sx3YsTuGd/3mZUMinYNLLv/zIDJ1EAAAAACg5Cqj+5YpJdixOgVK2HUZkS8GhOq5QBxb+YE/jmXRs8fjrG50AzA3KVJV3w459CeGcgsAP/ucL7Tqv0LCm6TEFaenQvII6wlBQ5i53PnnXxd8uoNsZh5bjtl+1L0UE5NKfly6xQ3H+cE78vb4+sXKFHQp25aTJdijumIED7VAbf9Vb/3O87eDJ/yx80w7FfeOscwJDN5UYVMcOXn98Z7Ed6pid+9o3KaDATm3h+asLozwQ0AEAAAAIpFJr/qqur61IXc0sXxTOqUfYZJ1OiB5XxxZBbeZ5KajyUwk8lwItBVapKORLJ6TTvJJ19KDlvvux8Ha0sqHAMp11EW2XfC0HIP724txeR/1/XqnagPNX+wwL0rxSlXDzh4InDau3Q+3NHpFY8vTNTZvsUMdcNfnY1iq/qpbr0na44o/3d6gzChQ3AjoAAAAAoSYMS6zKs2f/YTuUnKpdqgqkqmB6aVzT9afnqBqmhlW90kvVT93nudVb1QaeN5ybPXmQ09GEnqP/l8xoC9cUhN35YGIpnSCqXurOQ39u9U6Fe99/5F1nWBRUfuWKya3P+4+PHJcQXiqkU/XRVLSeaudP89A66v29nl+y1Q6lvw3T8YcX19qhOG0rb2cdyZYDyIUpgwbbodLhbXtOkrX91tAnsar7yqbsfszwhomfnH6iU8XXpZJ+2ZbQQ/EhoAMAAACQNn9vq4X0+xfW2KF4G3A/+9SJre2/6f9dN0xLCPqcqqQpStHd+bHjA9uQe+AfjQml3T53yYSEttkuP7nB/Md1x9mxuPueTwzB/LTMCiTdnmsVPH7inNHOsCtVe3wdpXb1FDAqmNNyeNvZ0/r7lwPIRlCHDn26d7dDHac24yrVRZ6eXeWZ996zQygXBHQAAAAAip46QfAGZpdMbyst56VeYr1eTFISTKXGwjp48JcgC+o4wd9xxWsrE9uv8gta5lMntW8nKx8UxmkdFGKqow9vO3vSuzqxKjOQjT8uaV+6y9+mnJdblTPVX7J55IM6hfBK1pNs467EErRjasNL2/nd9/Yic+HvfuP8qWpvOkFk0/7kPz6g9BDQAQAAACh66m3VS1VK1auo/8/f6+nyjXvsUHtD+1Xbofb8JdmC3kt/Xqnaxps2uq8dauMPygpJVXL/9FKj+bf73jbffCB1dWAgHWob7VsvPG/H4j48ObETiBOHDrNDccmCryj5l/Pl9evsUHvz1qy2Q3H+NumS+dXCN5x2+dy2+R5bvsz573XvorfsUFxt9+jOHcgPAjoAAAAAaRs/NLFNulJWTuuSLnV8cfN/vWJm/K8nnR5d//m3bzlVh729ygIdoZBNJcHG3/2Ddm23fXbmSXYo7nxfb6rffulFO1Rc5kxM7Mn6q3OfDuycQb2q/terC+yYMf16VJvzxiS2HZnMB33vc8cLz5mfvPaKM6z3u/MfLyTMX84eNcoOoVwQ0AEAAAAItX5HYjUqf6+uxa7QbeapKm4xUlt8l97xnNOT67zFW5zqwmqLTm3SqW06/QHpUCkvf2+r+ht79/fMNX/+Y7twTqXn/FVTb5w23Q7FKXxSCOWGXwr7Pv3YI2bmL35q/uWZv5tHly+LpNfSq4+dklDNVZ0zXHjvb5zlES2TQsmzf32PM+7636ednrKHWK8rj5nshHpetzz6sLNdB37nTvPlvz9pp8bpuXqNn3+foLQQ0AEAAAAIpLDJX7IqqJpmFLw9mSb7U6cMuRA076A/f7t0xeIL//1GQhXcz1083mmLTm3SqW26of2yb8Af8FMV0bsvutiOtVFvqP/5vvPsWJxCKIVRCpYU9im0e2XDeqc02SX3/db84o3X7TML64Err0oIz7RMWh4tp5bXH0p+asZMc/spp9mx9CjM+9FFl9ix1H4z5/KMAkCUBgI6AAAAAIH+++nEXgJVei6qAGpcXS87FLexKb9VMtWBhJfaaytV/3h3W0Kbeurp9jMXjbNjQH6o5Nzj114XGiQpxPrKaWfYseT0vExDr1w5fkideeaj17drjy6IljOToM1LpfV+98Er7VgwBYV6zsW+Hl1RHgjoAAAAALRSVUh1HKDqkP6OEj58WmF7UPTy93b6yKsb8hqaTR+T2Hvjz59K7HyilDS3HLRDcf5xefz14qyai9JywdhxTkj1xs23mPsuvzJlKa9vnv0+s+Izn3dKnfl7THXnpcf1vCgppFtw4z854ZiCR++yaljLr3XOdjkV0gVtD4WD2havxJZBz0F5qjoaY4eLUnNzsx0qXY2bS38dAJS/hsGV11B2ujiPA+Wlo+e7bksT2wAqRf5eRzOh9sp+f9vJ7Xod9c9T1Ty97nxwaULPqqqaevuciXaszTXffSkhEPzBTSe0K61326/ecII5l0q5fe6S8a3PU7D4vUeXmwmx6dNG1ZrpY/uZUyYMcB6TdJdFFP6pEwUvPf9jZ410toEeV2j37vpmJ8ybNqqvEyJ6t0866ySZbsPZkweZn33qRGcZdu496KyjqiPf+vO2KoAqJedW7/U/JqriqlJ0msd9z691Oorw0rZ9+CuJpZtSLWepOTAxsYqlX82dd9khAOWg+fbb7FB2amryc99ECToAAAAASalq67euO65dOFdot8+ZlNBJhdpUU/Ck4Eh/6pFUbeapE4TvP7rMfPZnrzklAjtickMfJ5DzUkh25r8+47yXwjuFhVoGhVt67/+Zu9o+M7f81Xu1fu4yPLd4q50aTqGgAlYvbR93Hv5wTrzt1QEA8o+ADgAAAEAolaT64c3TE0qiRUUBoZbFHzYF0XOc52YRKqp0XbrVevW8sNJ42br85IbQdVZvrOlQwJqsB16VuPOvq9quAwAUBgEdAAAAgAQK5S6ZMdT8x0eOc6o5FkM459KyqPfRr1wx2QmVvBRiqfqnHtNzcrHc/371sebPXz7N2R7+kMwNtfS4npdP//VP051l8IZsev8hfdPrfVXb4tefn9VuHu72UnXY049J3F5/eHGtHQIA5Btt0BUAbRcBKAW0QReO8zhQXiq5DToAbWiDDqgstEEHAAAAAAAAIBQBHQAAAAAAABAhqrgCAJACVVyB8kIVVwBCFVegslDFFQAAAAAAAEAoAjoAAAAAAAAgQgR0AAAAAAAAQIQI6AAAAAAAAIAIEdABAAAAAAAAESKgAwAAAAAAACJUdTTGDhel5uZmOwQAQDQaN3MtAspJw+AaO5SZbkuftEMAysGBiefZoWA1d95lh0rD82vXmDPu+aUdS21sv/5mXP/+5oMTJ5krj5lsBlT3tI8gSnf+4wXz5b+3XW/+833nmdtPOc2OIRvNt99mh7JTU9Ox7xGpUIIOAAAAAIAKs2LHdvO3FcvNLY8+bGb98ufmzU0b7SMAokBABwAAAABABVNYd8Uf77djAKJAQAcAAAAAQIVTSHff24vsGIBCow06AABSoA06oLzQBl3+TLz1MTuUXO8eXcyEYTXm9EkDzRWnNJi62h72kY554s2N5tafv27HjJk+pp/53RdOtmNAsHJvg+6CsePM49dcZ8cSbWvZa/74zmLzz0//3ezY12KnGvPhyVPMfZdfaccQBdqgy59ib4OOgA4AgBQI6IDyQkCXP+kGdF4K635483RzyoQBdkrmCOjQEZUc0Ln+5Zm/mzteeM6OJX/Nyh07zLdfetE8sXKFU9pO1NHE+WPGmi+dfKoZ06+fMy2IAsFfvPG6efq9VU67d65+ParNSfX1aXdUoRJ+v1r4RsI8tMw3TJ1mrj52ip0SzF3+BevXmVc2rLdT48ugdZgTW4aweVR942t2KP5+/3nOueY/Xnje/H5xvMThiUOHmf975tnm4nHjnXFxQ9A/L12SsLzuNrt2ynHm9OEj7NQ2YQGdf901n3+aPsPcOO2EpNvN3fZ/WPx263q76/zZmScFLoP4l+O56z9hXmxca3762qvO/tc8tL2+ftbZJdPBCJ1EAAAAAECI3fsOmc/+7DWzsWmfnQKg2CgcGnv398x/vbqgNZwTDWuaHlOgE0SdT4y/+wdO2OMNqkSl9zRNHVVceO9vnDApiKbP/MVPzTV//mO7eWhc0/V4WEcX3uX3hnOiZVDQpnl8+rFH7NRw2/buNTc9/JfWcE40zz7du9ux+Dqr4w2tl3953W2mMDWd99u5b5+5+k9/bLfumo+2abLt5t323vV211nLoHmHvd7r3kVvOfNx97/msbJpB73/5hABHQAAAIBIKaS788Eldixz5x9fZ5b+4KLWP0rPAcmpNJkCNW/pOZlRN9QOtVG4pXAoFYU3/pBOwc/Zv74noRptGAVIH3nwT3asjeahEMofrPnpcb2XP2xSSJXO8ouCs5+89oodC6b38S+LSrO5JdH0floOb5AZRu+nUozJaB95w0A/Lcu/zn3GjrVxlyPVtte8g7a7n5bVTyUXkTtUcQUAIAWquALlhSqu+eOv4qqwzO8f724zzy3ean7x95V2Spyqur76/5JXOQRyqdyruHbEis98PqGqqsIulcByQx5Va/zRRZe0VgV9dPkyc92Df0oIgbzz8Id7XzntDHPjtOkJj6sUmff1qkrprXbpr2qpdvLuOPt9zjzcaqve8OhTM2Y6y+jS/P2Pu9Uy9foP/+kPCYFbUDVfbxVX148vvtR8cvqJdqzNhb/7TUJJN83vRxdeErq84t1m/vUVVaH97vkXOttF+0SBnH8eR7+auIz+5VBVWbc6rMI7lQL0rvfvPnhlQhXfsOX4/eUfal3WUkMbdFkioAMARI2ADigvBHT5k05A57r7seXm+48us2Nx3uff+eDShBDvBzedYN5YtdM88toGs3FHixPonXnsIHP7nElm4eqm0Dbozvy3uc7zXc9+/ezATilUxfbMf20rhRIUGOo5/zN3tXl91Q7z2soddmqc3jOdTi80jwf+0WieX7I1YR51/arNjDG15sIT6pwSga57Yu93xwOL7Zgxl8wYau66IbjUyp9eajT//Nu37JgxsycPMj/7VPsAAXEEdIn8AY2oNJmqabqCnuN/X4Vw3zz7fc6wP+R55OqPJLTTJnqO2kf70ORjzakNw9u1idb/2//ZGuCppNryz3zOGfbyh1Fbv3h7a9VLBVpPrlxp5q1Z7bSfN/8TNyVUy1TIeMl9v7Vj6QV0/hDQpeBr2s9+bMfiy+t/P1F1XD129qhR5uT6BnP8kLbPfFAwFhScDvzOnXYs7o2bb2mdj385gpbXH74qfFtw4z85w5LOcpQaAroslVNAt2DdITsEAMVrZn0XOwQXAR1QXgjo8ieTgM7fsYMkC+gUTD3y6gY7FucGcck6ifi3+942v39hjTMsn7t4vPnMRePsWJtUQZhK/qmtPFXHTWb80N7m55+eGRjSaR7/+zdvJQSGQbzvnU5w6LrtV28kbKOvXDHZXH/WSDsGPwK6OAVF37/gonbBmfiDL38pLZc3wPIGPf6Qx+1Y4NLxE8ys+vp2wZWff53CejRNJwgM43+PdAK6sPn7l6MjPbD65xG0POLfN96Sh/55+EslupIFm/55+AO8UkQnEQAAAADgo9JwmfCHc3LhCe3by/K7+vThdijusdfbz0eef2eLHYpTSTaXQjIFa6nCOVm2Ybf519+1by/KDfhShXOidVWwKAr6VBLOpWVQSbkgz77dtg4K8gjnkIwCF5W6Uom0sDDLG96IgqqgP6+E6qJjxtqhOJXWUtVMlVhTCbBxd3/faYNNpdiCqNdQLwVGQe/vDZJk0ZbNdiiYqprqPfXeN/zlQTs1ffUhAc1rGxLPLyoRmK1zRo22Q+lTb7leCiCDtpt//76zdasdak9BLvKLgA4AAABAwSio8peOE1XxTEWl0/785dOcknb6SyeAmtzQx3mdSwHa4sZddixOAdy8xW3hlpbFW830b29sSgjWvMuhKrMq8eblnZfr+48sSwj4VMrPnYf+++ehUn/ucl7kCQtF1WP9FNp556/qv6hsKnmlEm/6UxCnttu8FKR9+emnnKqO+aIql6ryGkYdKagTBAV2CutUmi1f1N6deixVlVn16Kr31Hun05mDn7dKqlfT/vLtjXr60NQ/iCA7BHQAAAAA8kJVXv1/N/zg5XbhnFwyPfXN350fO94J3DJ1ka+k3TNvJZauUQDndaanxJooCPyPjxxnPnzaCCe8+9wl41uXQyXc1A5eMgol/e3Nfffj01rnof+q0qrgT6XlVA33V7ee1Pr45Sc3OCXiXK+ubLJDbfyhndrDA1wKlO67/EqnLTIvlaBKpwfPTHmDNrVHpyqhCgyTUVCmkl65COm8JcgUQKrNN3VWoR5LVYpPpcG0LdSmnv4Q5y+xiMIioAMAAAAQKQVTH0tRGk7P6Ug4J+q4wctfzdVfvdVfLVYUkv371ceaZ//9rITSdRLU3pyXeq31UhgZ9JqHv3KG06mD2sg7ZcIAOzXOWyJOpfkU+nn5q7dqeQE/dRSgqq1eCunU3lg63BJ5qf787Z2pCq3aUVMnAwrEVJpP7dEF+eGCl+1Qe2rTLej9/H/eNtsUQHqr3ar3VVXrdXujbejTsfNKkNruiZ/rXfv326FoqQ26oO3k/8u0vTzkFgEdAAAAgMioqmdYpwpeQ9OoAhtG8/a24+at5uqv3ppuEKjXqXMKVde99I7n7NRgyzYkdjY0bXRfO5Q+b5t48thrG+1Q++qtl8xIDGAAr59f+n471EZtuKnnTz9/mKe227KhHkAViqk03/YvfdkJ7PxVYFXKzTVl0GA7FPdeU/vSo8lonbztrKkU3yen569nY3810BfyWGU3mRl1icvRuCuxWj+KEwEdAAAAgIJSKKfATNVG1dtqqnBOxg/Nrtc8fztubjXXB/6R2OGCvzqslwI59ZR65r/NdXpWVc+xqq6rwC+ZdDqXSEWl9rzt9D3rCRX91Vsvmh7cPhYgqu6qkmh+ao/O73xfJw/ffulFO5QeBXpq++3Tjz3itDHnD/gU2N128il2LM5bsk49vXqpg4lMQsLmAwfsUNy2ve3b27t30Vt2KHv+TjH+69VXAtv4U+cUqnbrdpCR63YAT/OVYPzq3KftEIoZAR0AAACAvHA7c/D/KZRTVc5CVsP0t+PmVnP1V3f1V4d1KZhTIKceVlXFVPPythdXCN628bzVXL1t0inE81ePBfxUlTGoqqvCNK8bp023Q3EKyFQd1g2UFJYpfAsLm86/99dO2296ndqY+/Cf/pDQY6te/5nHHrVjcSph5xpQ3bNdu3mah7edOs1P4Z86gPjJa68ElgR0qaqrniN6by2zls0rKMRLl8JPb1t7au/uwnt/07pM2jZ6f3VOoWVxO8j44zuLncdzRVWKvb2uattr+7jhprsc2m7af9rvmQSfyA8COgAAAAAVwVv1U6XeFHB5S78pcAsqzadqrArmXOpx9ZF/CW8vzs8bDMrulo6VqPOXjFM1V62Dt4dZfwcXQJigqq4Ka7wBm0q4+UvbqTrswO/caaq+8TWnN1QFXN6w6RdvvG6facz3L7jIDsXpeXqOXuu+3lulVaXnvnTyqXYs7utnnZ1Qqk7zUGcS7jw0PwVQms8tjz5szv71Pa3roLbwvEGV6Dnue2uZ/TT/bPznOee2W95pP/ux857abnp/LwWlVx4z2Y7lzq/eP8cOxWn7aJ29y6Htpv2nEPXTjz9in4moENABAAAAqAj+gOubf0wstXL6McHh1u9fSGxHSj2uplMt1+WvnvvGezvtUGYUBKqNPJequXrbopOgDi6AIEFVXVXi666X/mHH4lTazt9OXBg9z9vRgEpyqVMIb2AVRs955qPXO6Ggl0rRabo/aAui5+i5eo3rgSuvSvr+KvHmX79kpfBS0XZNd3kVziko9S5vriicTHfbaxv8ds7ldgxRIaADAAAAUBEUcHnbcfOWnlMpt+tDepL1tyGnDiK87pm72g4FO2PyQDsU98ir69vNQ/7tvredDif0Xx0/BD3H20aeSs5pXq5serpFZQqq6qpSZf6A6ptnv8/p0EHVTf3Bkxtw6XE9z09VVpd95lan91Q91x8YaZoe03MUbgXRdPW8qsBJz/fS8qhXWD2m5/jnofFXbvwnZ9m97+2+Rj2+XjRuvJ0a95PXXrVDHaP3nP+Jm1rX2cu7vAtiyxW2zrngbnsFsf79rHFtE/Xwqm2Qj5AQmak6GmOHi1Jzc2KPR6VswbrsG4cFgHybWZ9YDQfGNG4un2sRAGMaBness4FuS5+0Qwgz8dbH7FCc2pvrKFUrVQcMrhvfN8bcPmeiHUukzhvUPpxLnVConbsgdz+23Hz/0bY2sFyqtqqScUH866Xn3j5nkjOsTiZ++fSqdiHes18/O6GU3TXffcm8trKtjSeFaf9y5WQnNFQQp/n4l0vt26kKrZd6n/3gf75gxxIFPR/hDkxs31GCV82dd9khAOWg+fbb7FB2amqy67QoDCXoAAAAAFSMs48bbIcSXejr5dVLgZyX2qNTL676U6gW1EvrwtVtHTfI5y4Zn9AWnUrv3fCDl53wz52PlwK8oA4rVEJOAWSQsHUDABQ/AjoAAAAAFUMBlzqD8FK11/OPDw/oVFrO2/abn16v0mtey9a3VZ8VlZT74c3TE6rYhnFL14W1c3ehp5qrS+tE9VYAKF0EdAAAAAAqir8ziEumtw+8vBSU/fzTM51qtt6ATUGagrnf33Zyu6ql9/+j0Q61UUin5+o1/lJwmq9Ctq9cMdk8/JUznOeGuWDaEDvUJqyDCwBAaaANugKiDToApYA26NqjDTqgvNAGHUqd2qxTtVgvf5t3SI026IDKQht0AAAAAICc+dsbm+xQnEreEc4BQGkjoAMAAACAIqYScy71WPv9R961Y3FUbwWA0kdABwAAAABFTD3CqrdX/d3689cTeo1V23XXnzXSjgEAShUBHQAAAAAUsbAeZnv36GK+dd1xdgwAUMroJKKAOtJJxKuvvmXu/uGv7Vhy48ePMv369zUzZ041M2YU5kK9fftO89RTz5sePbqb97//XDu1PD35xPNmxcrV5pZbPmKnFM5/3PEjs2zZe3YsXL/+tWbggFpz7JQJ5vTTZ5r+seOhENasWWcefXRuXo69++9/xDz+2LN2zJgLLzrTXHXVJXYM+UAnEe3RSQRQXugkAqXo0jueM8s27HaGFcydeewgc9O5Y8zkhj7ONGSOTiKAykInESgYBTgvz3/TCfS++91fOOFZvmjeCk6++tXvOOHJvn377SPlR8HcF794h/nd7/5iduRxm+bCju1NznHw4J+fcPbN888tsI/kh4K5H//4t+Zr/+d7zrEHAACA/Hj4K2eYpT+4yPl79f+dZ+66YRrhHACUEQK6MvXWwqXmu3f9PG8hnUrNOcFcS1uDteVIJRjjwVyTnVI6tG9++cs/OAFjvvz2Nw8RzAEAAAAAkCUCujK2bt0mc889f7RjqFQKGBe/s8yOAQAAAACAYkMbdAWUizbo1M7cP3/l03asjZ63aNEy8+zcl+yUNp/57EdpF6yD0t3++eZvgy5on6q66dIlq8y8efOdcNYrX8udznJlizboCo826NqjDTqgvHS0Dbpilq/2cIBiVYj7RNqgA8pLsbdBR0BXQPkM6FwqKfXtO39mx+JOmnV8YMcGbsP+6xo3JgQ6Pap7xN5npJly7ERz3vmn26lx/rAkiD9A6cj7eIW9Xh0iNDQMSfl6cTuzeGvhktZ5uO8/e/ZJ7UIl/3YP4t8XuVjOMJkGYWqDUNWcvb72fz9vRoyot2NttK7z5r1sGhs3JVTl1XJr+wR1/JBOpxX+ZVRV21deWWjWxraPt2p0ff0QM278aHP22bMCly8soNN07/7UfGaedHzKzko6sr5eYeuh42FYfV3oeni5y7Bs2erWeWj5j5s6yZx77ulJO/fI53HmIqBrj4AOKC8EdEDpI6ADkKliD+g6fy3GDhelAwcO2KHSt775iB1K34YNm82ClxfaMWMGDKg1Z5wx0461N2jQALM/ts2WL19tpxizbftOc8klZ9uxOIUMP/zB/zg3+M3Ne+zUuEOHDpnNm7aZRYuWmg0bN5sTT5xqHzHm7beXJcw7yLjxo8yxx05whjv6Pq5kr1ewker1otDyW9/6sVnyzoqEebjvr+27ctUaM3HiWFNd3cN5zL/dg3j3RS6WMxl19rDdEyaddNLxZtiwIXasPa3LvNhrtI6u2n59Y9PH2LE4dfCgDiW0fN6wSTSu9dF26Nmz2owdO8I+0n55grjLqHD0jm/ebV588TXnNd5lEm2v1e81mpfmv2lGj2lwjmEv/zFX3zDUPPDHR83rr72dsK01vGTJCic8mzbt2NZ96dXR9ZVU66FpydbD5V0G7zy0/FpP7be+fXqbESPbh3z5Ps5c9X1o/cBv157yuRYBMKZPr+52qHx0715+6wQkU4j7xO4v/MMOASgHB047xQ5lJ1/XXO7CytCU4+LhmEs37t42yDSsdsnSoQ4A/vKXp+xYZrJ9H5UyyuT1KlHlp2X44Q9+3S6M8VNps5/8+Ld2LDO5WM5cUwkslQbzWrEiMVjV9k63g4ds2rFTO4gKlFLRPtK+SkXVuJPNT4+pgxS/bNdXx0cm6xHUQYvCuVTLoNercw8dV17FeJwBAAAAAHKDEnQFVIgSdKKSOw899KQdizvhhGNbS1w98MDjCUGDqsB+4babzNXXXGZOmD7ZqbroLZ1z+PDh1vdUybgPzDmvXSk9VTv8yr98xnnMLT2XzfuIAhXv66+55v3mho9f6bz+jNknmU6dOyUsQ2PsuaecMj2h5NTPf/Z7s3nTVjsWX85bPnWdM4/xE0eZ1e+ta10GlYByS05pW2ldGobXJWx/VWP89ne+4jzmLmsuljOVTEvQydq1GxLet2+fmoTt+9Of3pcQXM754Pnm9i9/snW933rr3YQSXn1ir3f3reaj5y1evCxhuVSt9VOx7avHtHyqjnn/79uCIlUr/uQt17Q+p6pTlVPqzaX303t71y2o1Kbm87GPfdDc+rkbArex9qm/FFw266v1+NMDf3OGRcfBJ276kLnppqtbX78+tl/dY0nz0TK5rxcFbCo551KV1o9df7mzLbQOPXtVJ2yL5cvXmAsuOMOOFeY4c1GCrj1K0AHlhRJ0QOmjBB2ATFGCDkVhxYo1dsiYq6661Lm5V2Cmdqs07rZ5pbazLrhgtjOcrWzfZ4evBNKME49rfb3+qx0yBSXHTZ3ohC2fvfWjrY+LSkB520nTcug17nMmHzPeCQwV9rjUwUKmsl3OQvG3Gff5z1/vLI+WS8vnbbtNbbDNmjXNjnWc9vOXbr/ZCUYVSJ111qyE9t30npqeKW3D023Y6G5jvYeXf19ms75btmy3Q3Fqa07Hj0uv17x1nJ951snmE5/4kNOWnJfanPO6+Z+ubt0WWgctj3cd1D6egllXqRxnAAAAAIDMEdBVIN20qxF5dRzxne98pd1NfHXPzEvcBMn1+3zzm3ebe+75U0LVP3XS8IUv3OiEG97ARBa99a4dilPj/35apuENdXYsXj0yqGpiJjJdzqgoPNPyaLmCOh6prs7NrwJaX4VHX//GF53/firplgkFUEHb0B+IeUubSS7XV9Vs1RGH2oRT6TpR2Kbj/PrrL3fCQ//x7u20Q0GalsfP3+7dipVr7VB7pXKcAQAAAABSI6CDQyGDSuvohv/ee/9qp+ZeJu9z7JTEtvRUokjBiHpX/cTHbzf/+tXvOO1shbWNtsJXLdJ9nf/PX7Js1aq20obpyHY5i4mWUaGT2kqbOzfz0oTpUqikKpsKufzbP5X6+rZA1UuBmL80nje8CpLu+ip885a0FAVuahPua//ne+bTn/43Zx6aV1DA618OrbP/ONSfv+fg9es22qHyOs4AAAAAAIkI6CrEsKGD7VAbhQkKSBQuKGRQw/S64deNfy519H1UCihZ9UeVkHr8sWfNt+/8mfmPO37UWpKp0EplOYOWUWGSgjItl0IeLaNCJ3Uy4G2vLVtaZ4WyCpHcIErtsXlLleVCqtJ42azvtddcZofa02s1D83rq7F11HvkWqkcZwAAAACAzBHQlaGgG3NvdVKFFApKFCYoIFG4oLaz1Eab2ozTXy7k4n1ULdJt2ysZlUj61rd+Elh6KVPe9vrSFcVypuLtvEH84ZVKWilMUlDmlmJTAOS2oab/uaCAVsGsQlm32qmqeKq9NXUqoeFCyHZ9VW31S7ff7By/yeg413vkohdVdzldxXicAQAAAACyR0BXhl566Q071MZtjF5+8uPfJrTPpRt+tZ2ltuLUZlz/AblpWD5X76OSQ3qdwhHNIyzQUTDy/PNtjer7KQz65X/fmfIvqJ20dORqOXNBwcxCX+m0seNG2qH44z/8wa+dZRFV39T2UQDktqGWizboVLVTAa1LgdjX/u/nnbbStJ29x2W6Wlr226H29u5tsUNxbjCdq/VVu246fr/9nX9xQj23A5QgyarM6tgIOvaC/vyK6TgDAAAAAOQGAV2ZURDhDwbUqL5LpYi8pXJ0c68b/lzLx/soHNE8FO4ouFBA4Q8n3l7U1jGEetr02r6tMKWJMl3OfHjqqedbwyjXlOPa2jBTcON93N+7aq74ey5VoDQioHOETCxftsoOJdKx7w2Exe0oIdfrq/buFOq5HaAosPOXCNX7uW3PjR6d2PnD1m3ZVyMvhuMMAAAAAJAbBHRlQtVaVZVQVfj8wczs2SfZIWNa9iY+5i9xJAsWLLRDHZeL91HI53YkoKqyfgoozjhjph2L6+EpBTV2zHA7FPf43+bZodzKdjlzSYGQlkNtkXkpuHHDKtm3L7EUWlCptPnz37RDHbfPN1//caHj1l+NMxWFcOpoxE+hpJc3mM52fRX+uZ2baB/rs+alwE6lQv1txLkl+PwdWKj9Rf88Uimm4wwAAAAAkFudvxZjh4vSgQMH7FDpW998xA6lb8OGzWbBy21BltoVe+ihJ9v9qdTcokVLzaFDh+wz4xRSXH75hXas/fyam/eYqk5VZuLEMU5Y8uCDT5kXX3jVPhqnqn/nnHOqHYt7++1lZrmnl1QFEaecMt2Zx7r1m8yhg4eyeh89545v/sgsWbLCbN60zXn9ylVrTN/aGjNo0ADnOQqjHnn4aecx1/vOOc2MHRsvrTRiZL2Z91xbySn91zwUlPTt28cJXZ588nnz05/e5/TcumP7LtO1a2fnMZd/ex2N/c2cOdUJeBYvXuZs72yXMx0Kh7xtymmZgo4DTddy+H3ipg+1Lo/499+mTVtNXd1AM2zYECcI+vnPfm/WeXoQlfqGoWbatGPsWJx/udTDqvax5tES295qe9D7+ObN28y4cSOdbazX/uqeP7ULlI+bMtHZdy7/sso7S1aabl27OtvQ3Y8P//Vp+2jcpZec0zqfbNf3nnseMA/H9uHq9xqd/bh8xWrn/fv16xs7bns4y/DXv/7dvP7a287zRVVfr/vIHDtmzOFDR5zPqMudh3scaDm+/71fmfkvveEcdwoVtXySi89DJur78NuN36495XMtAmBMn17l9wNG9+78KIPKUoj7xO4v/MMOASgHB047xQ5lJ1/X3KqjMXa4KDU3N9uh0rdgXWJ4lg7ddKvHy45QEPWF225ySu94ffGLd2TcU6u/LSyFK+qNNYga/1f7Ytm+j0orqWOBdGl91aaYV6bbTyXNVGXQpeDlS1/8ph1L5D43F8uZinrlzLSkmUtVL1W6y0uBjzpuyIR/20iydVcbb6pW7G2DLh3u8eNSZwv+EoGpKJj+whdutGPZr69er04X/GFiMmqjTtVgvTLdj9qGblXcQhxnrpn1XewQXI2by+daBMCYhsE1dqh81NSU3zoByRTiPrHmzrvsEIBy0Hz7bXYoO/m65lJMokw5AUVAOCc33vQhp5H8MAom/D1aqnSPl4KHsMbx3eqD2b6PGu9PtydRN4z0U7ihgCrZcri0PJ+85SN2LE7bL6zXTrfabi6WMx+0zgqJ/OGcjBhRn7IXXa2TtolrbWNiCTM5++xZdqg9VWfVeyfr9VTL6F8Of0k2P7Vjl+q4uv76K+1YXLbrq9d/9taPpuw91aX38odzouPL+x5h3O3ibSevWI8zAAAAAED2qOJaQLmo4pqMbvyPmzrJfOiqi8z7LzvXqXoXRFXijjtugtnbss9s276ztVqsQj1Vibv+hivM4cOJVVQPHTpsTjxxqh2LO+aYMe3moWUYMXKYU80xF++jKoYnTJ9sjpoq061rl4Tqkgoxjpk81pnXTTdfHbq+quKn6redOndyql16qwAqyDhm8jgn9Lnqw5cGzmPMmJHOa7UObgkqva6+oa51WXOxnMn4q5KGUYA0fvxI572uu26OmThpjH2kPW2XhuF1TlVKt1qslnX6jGOd7XHRRWc5x59bNVT7r2fP6oQqk6qqGjSP0aMbzKRjxjpVNLWN9LodO5pat72Wc9asaeajH/2AmT59ilNd2F0/zUfb0q1q7K+e+r5zTzUXXHCGs6137dqdsE8uvfR9znEVtI2zXV8dz3pfTavqZMzBg4cTStTp/afPOM58/ONXOOsURMulduLc5di1a0/r58LdbrPPPMnccMOVZsqUtk49XPk+zlxUcW2PKq5AeaGKK1D6qOIKIFNUcc1SpVdxBYBCo4pre1RxBcoLVVyB0kcVVwCZooorAAAAAAAAgFAEdAAAAAAAAECEqOJaQFRxBVAKqOLaHlVcgeL3+vy5dggACuNaX0d6AIobVVwBAAAAACgze7vwoyaA3CGgAwAAAAAgQ28OHmgOdaqyYwCQHQI6AAAAAAAytKpvjVnWr9aOAUB2COgAAAAAAOiAZbV9zcZePe0YAHQcAR0AAAAAAB2wu1tXs6xfX7OvS2c7BQA6hoAOAAAAAIAOWlvT2ylJBwDZIKADAAAAACALy/vXmsaa3nYMADJHQAcAAAAAQBZaOnd2qrru7trVTgGAzBDQAQAAAACQpQ29ejohHQB0BAEdAAAAAAA5sLxfrVnVp8aOAUD6COgAAAAAAMiBg52qnPbomrp3s1MAID0EdAAAAAAA5MiW6h5OSAcAmSCgAwAAAAAgh97t29cspz06ABkgoAMAAAAAIJeqjFlW29ds61ltJwBAcgR0AAAAAADk2I4e3c27tX3MoaoqOwUAwhHQAQAAAACQB6v69qE9OgBpqToaY4eLUnNzsx0qfY2by2ddAJSvhsE1dgguzt9A8Xt9/lw7BADFpebAQTNz42ZTt2evnQIgCs2332aHslNTk5/7JUrQAQAAAACQJ83dujrt0e3v0tlOAYD2KEFXQJTAAFAKKEHXHudvoPhVcgm6a6+91g6lZ+/evebAgQOmqanJvPnmm2bPnj32kXCzZ882DQ0Ndiw9+h5/+PBhs3nzZvPKK6/YqR0zceJEM2PGDDuWvv379zvrqnVcsWKFWb16tX0EErRfGxsbzbx58+xYe7169XL2xaZNm8zSpUvtVKRj6pZtZsrW7XYMQKFRgg4AAABA0ejZs6epra01o0aNMpdccokT0uSDbmD0PhMmTDBz5swxI0eOtI8UTvfu3Z3lqKurM6eddpq5+OKLnYAJHTN16lRz4YUXZhzWIm5Zv1rTWMPxByAYAR0AAABQobp06eKELZdddllegyuFgrNmzYokpPNSYHjOOefYMaRr8ODBTrg5ZcoUJ/REx7R06WyW96s1e7p2tVMAoA0BHQAAAFDhVMrszDPPtGP5oTDwhBNOsGPR0bqeeOKJdgzpULiqcBPZW9+rp3m3to8dA4A2tEFXQLRhBKAU0AZde5y/geJHG3SJ7r33XjvURiXkVFquvr7eDBw40AnM/N59993A9uIyaatMpeQGDRpkxowZE/geixYtMgsXLrRj6Qlqg073CX/961/tWHta3+OPP95Zbv9yqG26Bx54wI5VrnT3q0pY+ttcevXVV2mDroO6HjliZm7YbEbt4vsFUEi0QQcAAAAgcuooQYHK008/bR555BGnswg/hWrZUkcMCvnmz59vDh06ZKe2UXtwhaD1ffHFF83KlSvtlDaqpqlqm0AUDnbqZJb3rzVN3bvZKQBACbqCogQGgFJACbr2OH8DxY8SdImCStD5KaA699xz7ViboFJ0HentU9Temz+QS1XyLUhHStB5BW2jVCXA9J5jx4411dXVre2uqeRdS0tLRj3TqkSh5qMSff5SF24vs9u2bTPLly935hvGvw7J1j/d/ZXqeen2ENyRfQpjJuzYaU7cGL7PAeQWJegAAAAAFB2FQVu3brVjbXJZsixo/sVOQZqqdCoQVLtr3k4RNJxJz7QKKNV7rELKoBs6zU/T1aOuwlLaxqssy2r7mhX9+toxAJWOgA4AAACoUBs3brRDbXr37m2HKo/CufPOOy+t0hGpeqY9//zzM67Oq+CPkK5yHK0y5t3avmZbdQ87BUAlI6ADAAAAKlRQQKcOFRRU5YI6o4jaqaeeaocSBVVvVYk3BW/p0rZSoObfXgrt/Ove1NRkXnjhBaf6sf6eeuoppzqpn9oBzNX2R/Hb0aO7WdavrzlcVWWnAKhUBHQAAABAhQpr88zfLllHKKQKKkGmzhsKQW3IqY01VR/1C6p6q6DNX3JOnVyoTb6HHnrICdXUA62/cw1VU/W3jxdUqu7RRx91OtBwadurrTcti+apsFTvNXfu3IJto1TcMDGoXXC14ec+Tvtz2VnZt48T0gGobAR0AAAAAHLGDcZU/TPIihUr7FB2FKapE4OwP4VmQUGjQrc33njDjrUJanvvzTffdDqDcAOzhQsXmieffLJd77SDBg2yQ+HCqsI+8cQT5sEHH3R619V7JesoAuVL7dFt6pV+6U0A5YeADgAAAEDGFH4lC8ZU/dNP1Ty9pcgKTcHa/PnzA0Mwdf7gpVJtQdVgFdb5S+CpFJ034PMHeKLOIi6++GKnpF4uO+JAeWju3s0sHNjfjgGoRAR0AAAAAPJOgdezzz5rxwpLgZlCtUceeSQwIFSpP79du3bZofaCqsh6q/Oq5F1QSOf2AKseW6+44gqnI4mgNuxQmYbsbbFDACoRAR0AICMtLS1mwYIFdqz0qOSE1gEAUDgKtFQ1tNBtqykkU7tu999/v1OVNJP3V+AWVEJQf1OmTLHPauMtgaf3WblypR0LplJ36khCgd0HPvABp3RdUFCIyjC8eY+ZsGOnHQNQiQjoAABpe++995zSD0G9/pUKVWt65plnnDaQjh49aqcCQGUKq2q5Y8cOO9QxKi2njgV0vVDPpZmGY+nQ/N1OCtSJQ1AHDqpmq15RVUqt0NSenNZd1XrToYBP1YOjWFZEq+bAQTN+R5PpEVDqEkDlIKADAKSkUEs3Gm+99ZY5ePCgnVqaunbtag4fPmwWL17slAQs5bARALLVr18/O5QonY4KGhsbWwMy/586PVDPnur4oBBtzin8cztwCArpVEotiuBL667eWxUgqiSfwrqgqq9eWlZK0lWWCU07Td2exOMWQOUhoAMAhFJVUDWQrbZ0NmzYYKeWj02bNjnr9s477xS82hUAFIP6+no71CbdEl/FSOdyhXT79++3U9oo+Jo6daodSy1ZABn0N2/ePPvK9rRc+qFLYZ2q26pknUqlq+pvUGAX1uOrV7du3ewQStnYpl1m3PbsSqwCKA8EdACAQOvWrTNvvPGG84v/vn377NTyc+DAAbN8+XInqNPNGABUCnVMoDbQ/NIpPVfM3DAsyKRJkwKr9Qad//v3z1+PmipZ9+KLLzpVf9U2qp/ap0sl2XNyvew7d9I2Wj4MaNnnVG3tTIsbAGII6AAACfQlXFVZFc4F9VJXrrZt2+aEdFp3bkQAVILTTjvNqf7ppZJnYeFWKVEAphJqflrfWbNm2bE2CvX8VWN79uzZ4aqmCgFVWu+cc84xl112mdNja1hPrdl0XBS0fJqmZUdx63zkiBPO9d/XvrQngMpEQAcAcKjDhK2b1jshlW5qjsS+OFYarbPWvZK3AYDypqqTaotNgVFQ6blCtBdXKCqhpo4k/Gpqasypp55qx9oElaI7/vjjnee64Zr+a/tdddVVTvCmAE5BnLdUnp5z1llnOT29qidYvZ9Ku5133nntqthqPGhZgppd8AeIcswxx7SGdHpfzUvLXAhuNVytO23mZU49to7Z2f74BFC5qmI3ZEVdoDboolqqGjdzAgZQnJp37TA7tm4y27ak32GCbkxK0eOPP552Rxdqm2nEiBFm35HUVY0AROv1+XPtUOW59tpr7VB21Pac2kgLMnv2bNPQ0GDH4hRoJWt3LZcUAKmHUy/dJ6gjimQUHiks85cUVLtvc+fObVedd86cOR0qfaaSh7q+uMFa0PbKxFNPPdVu2RQGKvDrqKD9le5+TbU+Wv8HHnjAjiGVobv3mJkbt5jeJd7xFlBqmm+/zQ5lRz+85AMl6ACggh06dNBsWr/GrF31bkbhXKVQO3wqTbdx3Wpz8OABOxUAyo/CrmeffdaOlQ+FXEEl48Kqur7++uspe1n10/NVLdhb6k0BV1CJt3So7degdgAXLVqU9rLlupBDqpKV6bSZh7iesX04fsdOwjkA7RDQAUCF2tW0zaxZudSsX7vS7N/X8fZvyp1usDY0rnK2VdP2LXYqAJQHBT4KsFQSLahaZTnIpKqrgih12pBuuKaSY3p+UID14IMPmo0b0//xS/tC4VxYG4AK7ZYsWZIypNO6Pv3003YsN7R+ldQubT4pnGvYTc/xANojoAOACqMwzg2cdu7gy3a6FGiuXrnErFu93Oxr4Ys1gNKl8EnVWRUGPfLIIwWrpholhWhBwZaqbfp7dVUYpXBNJdYUSimE89L203RtP1XrTFa6TEGZqquqXVMFZ/55aVz7Qo9rX6TqoGPhwoVO1VwFf955aVjLpGXOV9iqHme1zv7wUuuVSRBZyUbubDbjtzfZMQBIRBt0BUQbdACitl3tzG3eYHY3Z//lsBLaoAvTq3cfM2DQUDNg8FA7BUDUKrkNOgDFr3b/ATNz42YzaC+1FoCo0AYdACBye/c0m7XvvWvWrFySk3Cu0u3ZvcusWbXUKYWoYQAAgFBHjVNyjnAOQDIEdABQxo4cOWy2blrnhElbN603RV5ouuRs27LBCek2b2w0hw9n1qg4AACoDBOadprxsT8ASIaADgDKVPPOHU54tPa9ZaZlz247Fbmm9ujULp229a6d2+1UAAAAYwbvbTHjd1B7AUBqtEFXQLRBB6AQDh484LQzp9JdB/bvs1NRCF27dTcDBtU57dN1697DTgVQCLRBB6DYdD98xJy4cZMZuYsfSoFiQBt0AICCadq+xSnJpV5aCecK7+CB/WbjutVOleId2zbbqQAAoBKp5BzhHIB0EdABQBloq2a5xOxq2manIipu9eLG2D5p2bvHTgUAAJWioXm3Gb+DducApI+ADgBKXGJHBYftVERNHXRsie2TNauWmK2b6aADAIBKUXPwoBPOVR+iAykA6SOgA4AStbt5pxPM6W/P7l12KorN3t3NZu2qd2N/sf0U22cAAKC8jdveZIbu2WvHACA9BHQAUGIOHzpkNm1Y6wRzKj2H0rBty0anbbrNsX2nfQgAAMrPmKZdZgJVWwF0AAEdAJQQtS+nkGf9mhVm/z5+mS01+1r2mnWxfad9uKtpu50KAADKQf+WfWZC007TmWYtAHQAAR0AlID9+1rM+rUrnVJz6qkVpc3pbVdB69pVZv/+FjsVAACUqs5Hjjol5xTSAUBHENABQJHbsW2zWfveu2bT+jXm4MEDdipK3cED+2P7dLVZu3Kp2bF1k50KAABKkUrOjdlJm8AAOo6ADgCK1L69e0zj6uVOSavmnTvsVJSb5l1Nzj5ufG+Zadm7204FAAClQh1CjN/RZMcAoGMI6ACgyBw9etRs3bTeCW22bGw0Rw4fto+gXB05csRs2bTOqcK8dfN65xgAAADFr+ehw0441/vAQTsFADqGgA4Aisju5iYnpFGV1j27qSZRafbuaTZrV73rHAO7m+kBDgCAYqdwrqF5jx0DgI4joAOAInDo0EGnjTkFM9u3brRTUal0DKxe8Y7ZuH61OUS7gwAAFKUhe1vM5G00QwIgNwjoACBiO3dsNWtWLHF6aVVvrYAc2L/PbFi7ygltdYwAAIDicvL6TaaKZikA5AgBHQBERGGcQjkngGnaZqcCiXRs6BhZt2aF2dey104FAABR63WQducA5E7V0SJvibq5udkOlb7GzeWzLgCys33rJrNt8wanzTkgXb1q+poBg4bG/ursFAAd0TC4xg6Vj5qa8lsnIJlC3CfW3HmXHQJQDppvv80OZSdf11xK0AFAAe3dvcvpnXXNyiWEc8jYnuadTmk6HUM6lgAAAACUBwI6ACiAI0cOmy0bG51gRSXnirzwMoraUecY0rGkY0rHFgAAAIDSRkAHAAWg9sMaVy83LXvphh+5oWNJx5SOLQAAAACljYAOAAqgbthIU1c/0nTt1t1OAbLTtWs3M7RhlHNsAQAAAChtBHQAUAAK5oY2jDYjxkw0tf0H2alAx+gYGjF2kqmrH0XoCwAAAJQBAjoAKKA+ffubkWMnmYaR40yP6l52KpCe6p69TX3s2FHQq2MJAAAAQHkgoAOAAuvUqbMZVNdgRo6ZZAYOHmaqqqrsI0A4HSsK5gbHjp3OnbvYqQAAAADKAQEdAESkZ+8aM3z0BDNizCTTq6avnQok0rGhY0THSs9eNXYqAAAAgHJCQAcAEes/cIgZMXqiGTJshOnSpaudikqnY0HHhI6NAYPq7FQAAAAA5YiADgCKQI/qnmbY8DFOFca+/QbaqahUfWsHOMeCjgkdGwAAAADKGwEdABQRhXOqyqhgpnuPajsVlaJb9x5m6PB4b78EtQAAAEDlIKADgCLTtWu3eNXGMVRtrCT9B9Y5PfzWDRtpusSOAQAAAACVg4AOAIpU75paJ6QbPmqC06EEypM6flA7c9rX2ucAAAAAKg8BHQAUtSozcMgwM2L0JDOorsF07tzFTkep69Spc2zf1sc7gRg81FRVVdlHAAAAAFQaAjoAKAHVPXuZhpHjnFJWffr2t1NRqmr69rOlI8eb6l697VQAAAAAlYqADgBKSG3/QU6wU1c/yulQAKVF7QsObRjllJrrN2CwnQoAAACg0hHQAUCJ6dqtOyFPCSJcBQAAABCGgA4ASpRbTVJVX1UFFsWpumdvU+9WT64dYKcCAAAAQBsCOgAoYepoQJ1HjBgzyelMAsVl4OBhTjA3mA4+AAAAACRBQAcAZaBnrxozfNQEM3LsJNO7ptZORVR61fR1QtPhoyc4+wYAAAAAkiGgA4Ay0n9gnVNiq27YSNOlazc7FYXSpUtXM2TYCKd9wAGD6uxUAAAAAEiu6miMHS5Kzc3Ndqj0NW4un3UBUPx27thmtm3ZEPu/1U7Jrcsuu8wOlZbHH3/cHDx40I7lTt9+A8yAQUNj/wfaKQCKVcPg8ivZWlNDaV1UlkLcJ9bceZcdAlAOmm+/zQ5lJ1/XXErQAUCZUmCk0nT1I8aaHtU97VTkmnpkHTp8tFNqjnAOAAAAQEcQ0AFAGVOVy8FDhzvhUX+qXOZcvErxJKoUAwAAAMgKAR0AVACn04LRE51OC3r17mOnoqO0DdUph0oo1vShUw4AAAAA2SGgA4AKUVVVZQYOHuaESoPrGkznzp3tI0hXVadOZuCQeifsHDhkmLNNAQAAACBbBHQAUGF6VPcy9SPHOVUz+9QOsFORSk3ffmZkbJsNHzXe9OjZy04FAAAAgOwR0AFAhartP8gpTTe0YbTp3qPaToVfdXW1qasf6ZSa6zdgsJ0KAAAAALlDQAcAFaxr125O+KT21Aif2hs6dKg5/vjjnRBTvbUCAAAAQD4Q0AEAnOqbqvLaMGq86du3r51auWpqasyxxx5rpk2bZgYNGmSnAgAAAEB+ENABABydOnUyg4bUO6HUqFGjnPFKo04fRo4c6ZSaGzNmjOnSpYt9BAAAAADyh4AOAJCgT58+5rjjjnOCuoEDB9qp5a9///5OMDd16lTTr18/OxUAAAAA8o+ADgAQqL6+3gmsxo8fb7p3726nlp+uXbuasWPHOus6fPhwOxUAAAAACoeADgAQqmfPnmbSpElOaTp1mFBuhgwZ4qzb5MmTTe/eve1UAAAAACgsAjoAQEqDBw82M2bMcKq+qsRZKTt48KDp3LmzE8rNnDnT1NXV2UcAAAAAIBoEdACAtKgDBXUeceaZZ5Z0qKWw8eyzz3aqtWqdAAAAACBqBHQAgIxUV1c7Jc9K1axZs5x1AAAAAIBiQUAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIhQ1dEYO1yUmpub7RAAAACAIDU1NXYIqAzcJwKISr6uuZSgAwAAAAAAACJEQAcAAAAAAABEiIAOAAAAAAAAiBABHQAAAAAAABAhAjoAAAAAAAAgQgR0AAAAAAAAQIQI6AAAAAAAAIAIEdABAAAAAAAAESKgAwAAAAAAACJEQAcAAAAAAABEiIAOAAAAAAAAiBABHQAAAAAAABAhAjoAAAAAAAAgQgR0AAAAAAAAQIQI6AAAAAAAAIAIEdABAAAAAAAAESKgAwAAAAAAACJEQAcAAAAAAABEiIAOAAAAAAAAiBABHQAAAAAAABAhAjoAAAAAAAAgQgR0AAAAAAAAQIQI6AAAAAAAAIAIEdABAAAAAAAAESKgAwAAAAAAACJEQAcAAAAAAABEiIAOAAAAAAAAiBABHQAAAAAAABAhAjoAAAAAAAAgQgR0AAAAAAAAQIQI6AAAAAAAAIAIEdABAAAAAAAAESKgAwAAAAAAACJEQAcAAAAAAABEiIAOAAAAAAAAiBABHQAAAAAAABAhAjoAAAAAAAAgQgR0AAAAAAAAQIQI6AAAAAAAAIAIEdABAAAAAAAAESKgAwAAAAAAACJEQAcAAAAAAABEiIAOAAAAAAAAiBABHQAAAAAAABAhAjoAAAAAAAAgQgR0AAAAAAAAQIQI6AAAAAAAAIAIEdABAAAAAAAAESKgAwAAAAAAACJUdTTGDgMAAAAAAAAoMErQAQAAAAAAABEioAMAAAAAAAAiREAHAAAAAAAARIiADgAAAAAAAIgQAR0AAAAAAAAQIQI6AAAAAAAAIEIEdAAAAAAAAECECOgAAAAAAACACBHQAQAAAAAAAJEx5v8DqSauEBK6raMAAAAASUVORK5CYII=", - "text/plain": [ - "" - ] - }, - "execution_count": 1, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_client.png\", width=800)" - ] - }, - { - "cell_type": "markdown", - "id": "5b73135c", - "metadata": {}, - "source": [ - "🐧🐧🐧\n", - "In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.\n", - "\n", - "Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.\n", - "\n", - "This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library `lomas-client`. \n", - "🐧🐧🐧" - ] - }, - { - "cell_type": "markdown", - "id": "01ae30d2", - "metadata": {}, - "source": [ - "## Step 1: Install the library\n", - "To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library `lomas-client` on her local developping environment. \n", - "\n", - "It can be installed via the pip command:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "28fbdd79-8c15-49a9-bcf9-fcdeac09d2b5", - "metadata": {}, - "outputs": [], - "source": [ - "#!pip install lomas-client" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "6fb569fc", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client import Client\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "9c63718b", - "metadata": {}, - "source": [ - "## Step 2: Initialise the client\n", - "\n", - "Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server. \n", - "\n", - "To create the client, Dr. Antartica needs to give it a few parameters:\n", - "- a url: the root application endpoint to the remote secure server.\n", - "- user_name: her name as registered in the database (Dr. Alice Antartica)\n", - "- dataset_name: the name of the dataset that she wants to query (PENGUIN)\n", - "\n", - "She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit (as is done in the Admin Notebook for Users and Datasets management)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "941991f7", - "metadata": {}, - "outputs": [], - "source": [ - "APP_URL = \"http://localhost:80\"\n", - "USER_NAME = \"Dr. Antartica\"\n", - "DATASET_NAME = \"PENGUIN\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "markdown", - "id": "0ec400c8", - "metadata": {}, - "source": [ - "And that's it for the preparation. She is now ready to use the various functionnalities offered by `lomas_client`." - ] - }, - { - "cell_type": "markdown", - "id": "9b9a5f13", - "metadata": {}, - "source": [ - "## Step 3: Understand the functionnalities of the library" - ] - }, - { - "cell_type": "markdown", - "id": "c7cb5531", - "metadata": {}, - "source": [ - "### Getting dataset metadata\n", - "\n", - "Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the `get_dataset_metadata()` function of the client. As this is public information, this does not cost any budget.\n", - "\n", - "This function returns metadata information in a format based on [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). Any metadata is required for Smartnoise-SQL is also required here and additional information such that the different categories in a string type column column can be added." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "0fdebac9-57fc-4410-878b-5a77425af634", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "penguin_metadata = client.get_dataset_metadata()\n", - "penguin_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "d338ed96", - "metadata": {}, - "source": [ - "Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) with their associated categories (i.e. the species column has 3 possibilities: 'Adelie', 'Chinstrap', 'Gentoo') and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds (i.e. the body mass of penguin ranges from 2000 to 7000 gramms). She also knows based on the field `max_ids: 1` that each penguin can only be once in the dataset and on the field `row_privacy: True` that each row represents a single penguin. " - ] - }, - { - "cell_type": "markdown", - "id": "5a3c899d", - "metadata": {}, - "source": [ - "### Get a dummy dataset\n", - "\n", - "Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset. \n", - "\n", - "Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets.\n", - "Getting a dummy dataset does not affect the budget as there is no differential privacy here. It is not a synthetic dataset and all that could be learn here is already present in the public metadata (it is created randomly on the fly based on the metadata).\n", - "\n", - "Dr. Antartica first create a dummy dataset with 200 rows and chooses a seed of 0." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "01f4365a", - "metadata": {}, - "outputs": [], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "3f553b29", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0GentooBiscoe49.20847316.117959190.1259502873.291927FEMALE
1GentooTorgersen55.03162819.963435242.9291423639.940005FEMALE
2ChinstrapTorgersen51.09671816.777518159.9614935401.743330MALE
3AdelieBiscoe49.07091114.796037244.5301532316.038092MALE
4ChinstrapBiscoe44.82791813.246787236.9488535036.246870FEMALE
\n", - "
" - ], - "text/plain": [ - " species island bill_length_mm bill_depth_mm flipper_length_mm \n", - "0 Gentoo Biscoe 49.208473 16.117959 190.125950 \\\n", - "1 Gentoo Torgersen 55.031628 19.963435 242.929142 \n", - "2 Chinstrap Torgersen 51.096718 16.777518 159.961493 \n", - "3 Adelie Biscoe 49.070911 14.796037 244.530153 \n", - "4 Chinstrap Biscoe 44.827918 13.246787 236.948853 \n", - "\n", - " body_mass_g sex \n", - "0 2873.291927 FEMALE \n", - "1 3639.940005 FEMALE \n", - "2 5401.743330 MALE \n", - "3 2316.038092 MALE \n", - "4 5036.246870 FEMALE " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset(\n", - " nb_rows = NB_ROWS, \n", - " seed = SEED\n", - ")\n", - "\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "98e6fda2-dde7-4f8b-a787-c9a1e3571ebe", - "metadata": {}, - "source": [ - "### Query on dummy dataset\n", - "Now that she has an idea of what the data looks like, she wants to start querying the real dataset for her research. \n", - "\n", - "However, her budget is limited and it would be a waste to spend it by mistake on a coding error. Therefore the client/server pipeline has functionnal testing capabilities for the users. It is possible to test a query on a `dummy` dataset to ensure that everything is working properly. Dr. Antartica will not be able to use the results of a dummy query for her analysis (as the data is random) but if the query on the dummy dataset works, she can be confident that her query will also work on the real dataset.\n", - "This functionnal testing on the dummy does not have any impact on the budget as it is on random data only.\n", - "\n", - "To test on the dummy data instead of the real data, the function call is exactly the same with the only exception of the flag `dummy=True`. In the following cell, she will test with `smartnoise_query` but it is the same flag for `opendp_query`. She can optionnaly give two additional parameters to set the seed and the number of rows of the dummy dataset.\n", - "\n", - "Another more advanced possibility for functionnal tests with the dummy is to compare results of queries on a local dummy and the remote dummy with a very high budget: \n", - "- create a local dummy on the notebook with a specific seed and number of rows\n", - "- compute locally the wanted query on this local dummy with python functions like numpy\n", - "- query the server on the same remote dummy with (`dummy=True`, same seed and same number of row) and a very big buget to limit noise as much as possible (don't worry this won't cost any real budget)\n", - "- compare and verify that the local and remote dummy have similar results." - ] - }, - { - "cell_type": "markdown", - "id": "243c73e3-daec-45d6-a3c8-ae1d60439ec4", - "metadata": {}, - "source": [ - "#### Average and number of rows with smartnoise-sql library on remote dummy" - ] - }, - { - "cell_type": "markdown", - "id": "d1f8ea18-ccab-4f75-9490-b4d1144b39db", - "metadata": {}, - "source": [ - "Dr. Antartica will now try a query to get the number of penguin and their average bill length (in mm) on the dummy dataset. She does not forget to \n", - "- set the `dummy` flag to True\n", - "- set very high budget values to be able to compare results with a similar local dummy (with the same seed and number of rows) if she wants to verify that the function do what is expected. Here she will just check that the number of rows is close to what she sets as parameter." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3946425d", - "metadata": {}, - "outputs": [], - "source": [ - "# Number of penguin and average bill length in mm\n", - "QUERY = \"SELECT COUNT(*) AS nb_penguins, \\\n", - " AVG(bill_length_mm) AS avg_bill_length_mm \\\n", - " FROM df\"" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "90cf2a6d", - "metadata": {}, - "outputs": [], - "source": [ - "# On the remote server dummy dataframe\n", - "dummy_res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0,\n", - " delta = 0.99,\n", - " dummy = True, \n", - " nb_rows = NB_ROWS,\n", - " seed = SEED\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "a30f277e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average bill length in remote dummy: 47.52mm.\n", - "Number of rows in remote dummy: 199.\n" - ] - } - ], - "source": [ - "print(f\"Average bill length in remote dummy: {np.round(dummy_res['query_response']['avg_bill_length_mm'][0], 2)}mm.\")\n", - "print(f\"Number of rows in remote dummy: {np.round(dummy_res['query_response']['nb_penguins'][0], 2)}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "167e8c6d-6c93-4ab4-9ba7-bf7e783a6bc2", - "metadata": {}, - "source": [ - "No functionnal errors happened and the estimated number of rows is very close (if not equal) to the number of rows that she set for the dummy dataframe. She is now even more confident in using her query on the server." - ] - }, - { - "cell_type": "markdown", - "id": "324454ed", - "metadata": {}, - "source": [ - "### Get current budget\n", - "\n", - "It is the first time that Dr. Antartica connects to the server and she wants to know how much buget the queen assigned her.\n", - "Therefore, she calls the fonction `get_initial_budget`." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "61a467f3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'initial_epsilon': 10.0, 'initial_delta': 0.005}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_initial_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "bc8f7a74", - "metadata": {}, - "source": [ - "She sees that she has 10.0 epsilon and 0.005 epsilon at her disposal.\n", - "\n", - "Then she checks her total spent budget `get_total_spent_budget`. As she only did queries on metadata on dummy dataframes, this should still be 0." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "afd22f84", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "05daf5a4", - "metadata": {}, - "source": [ - "It will also be useful to know what the remaining budget is. Therefore, she calls the function `get_remaining_budget`. It just substarcts the total spent budget from the initial budget." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "6260cf54", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10.0, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "20298e00", - "metadata": {}, - "source": [ - "As expected, for now the remaining budget is equal to the inital budget." - ] - }, - { - "cell_type": "markdown", - "id": "b746374c", - "metadata": {}, - "source": [ - "### Estimate cost of a query\n", - "Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The `estimate cost` function returns the estimated real cost of any query.\n", - "\n", - "Again, of course, this will not impact the user's budget.\n", - "\n", - "Dr. Antartica checks the budget that computing the average bill length will really cost her if she asks the query with an `epsilon` and a `delta`." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "fd5ed08a", - "metadata": {}, - "outputs": [], - "source": [ - "EPSILON = 0.5\n", - "DELTA = 1e-4" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "133020c6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 1.5, 'delta_cost': 0.00014999500000001387}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "3c6a3a8c", - "metadata": {}, - "source": [ - "This query would actually cost her 1.5 epsilon and delta 1.4999e-4. She decides that it is good enough." - ] - }, - { - "cell_type": "markdown", - "id": "e5379edf", - "metadata": {}, - "source": [ - "### Query on real private dataset with smartnoise-sql\n", - "Now that all the safeguard functions were tested, Dr. Antartica is ready to query on the real dataset and get a differentially private response of the number of penguins and average bill length. By default, the flag `dummy` is False so setting it is optional. She uses the values of `epsilon` and `delta` that she selected just before.\n", - "\n", - "Careful: This command DOES spend the budget of the user and the remaining budget is updated for every query." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "19e60263", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10.0, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "69767fac", - "metadata": {}, - "outputs": [], - "source": [ - "response = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA,\n", - " dummy = False # Optionnal\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "6dbbdf93", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins in real data: 342.\n", - "Average bill length of penguins in real data: 42.93mm.\n" - ] - } - ], - "source": [ - "nb_penguins = response['query_response']['nb_penguins'].iloc[0]\n", - "print(f\"Number of penguins in real data: {nb_penguins}.\")\n", - "\n", - "avg_bill_length = np.round(response['query_response']['avg_bill_length_mm'].iloc[0], 2)\n", - "print(f\"Average bill length of penguins in real data: {avg_bill_length}mm.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b2767e65", - "metadata": {}, - "source": [ - "After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "4ced8a56-e2c3-4bd1-b94b-95cbbcd58a15", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'requested_by': 'Dr. Antartica',\n", - " 'query_response': nb_penguins avg_bill_length_mm\n", - " 0 342 42.930651,\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "response" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "39701fe5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 8.5, 'remaining_delta': 0.004850004999999986}" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "e37c587f", - "metadata": {}, - "source": [ - "As can be seen in `get_total_spent_budget()`, it is the budget estimated with `estimate_smartnoise_cost()` that was spent." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "487f835f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 1.5, 'total_spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "eef4afcd", - "metadata": {}, - "source": [ - "Dr. Antartica has now a differentially private estimation of the number of penguins in the dataset and is confident to use the library for the rest of her analyses." - ] - }, - { - "cell_type": "markdown", - "id": "04929993", - "metadata": {}, - "source": [ - "## Step 4: Penguin statistics with opendp" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "b9685226", - "metadata": {}, - "outputs": [], - "source": [ - "import opendp as dp\n", - "import opendp.transformations as trans\n", - "import opendp.measurements as meas" - ] - }, - { - "cell_type": "markdown", - "id": "bbbca191", - "metadata": {}, - "source": [ - "### Confidence intervals for bill length over the whole population" - ] - }, - { - "cell_type": "markdown", - "id": "9d41bd58", - "metadata": {}, - "source": [ - "She is first interested to have a better idea of the distribution of bill length of all species. She already has the count and average from the previous step, so she only needs the variance values.\n", - "\n", - "She first checks the metadata again to use the relevant values in the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "4331d86f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "penguin_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "f90e0425", - "metadata": {}, - "source": [ - "She can define the columns names and the bounds of the relevant column." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "ff8cb7b6", - "metadata": {}, - "outputs": [], - "source": [ - "columns = [\"species\", \"island\", \"bill_length_mm\", \"bill_depth_mm\", \"flipper_length_mm\", \"body_mass_g\", \"sex\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "70b2bdb1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(30.0, 65.0)" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bill_length_min = penguin_metadata['columns']['bill_length_mm']['lower']\n", - "bill_length_max = penguin_metadata['columns']['bill_length_mm']['upper']\n", - "bill_length_min, bill_length_max" - ] - }, - { - "cell_type": "markdown", - "id": "e93ae087", - "metadata": {}, - "source": [ - "She can now define the pipeline of the transformation to have the variance that she wants on the data:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "75e4933b", - "metadata": {}, - "outputs": [], - "source": [ - "bill_length_transformation_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"bill_length_mm\", TOA=str) >>\n", - " trans.then_cast_default(TOA=float) >>\n", - " trans.then_clamp(bounds=(bill_length_min, bill_length_max)) >>\n", - " trans.then_resize(size=nb_penguins.tolist(), constant=avg_bill_length) >>\n", - " trans.then_variance()\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "411d464c", - "metadata": {}, - "source": [ - "However, when she tries to execute it on the server, she has an error (see below). " - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "8041a647", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Server error status 400: {\"InvalidQueryException\":\"The pipeline provided is not a measurement. It cannot be processed in this server.\"}\n" - ] - } - ], - "source": [ - "# Expect to fail !!!\n", - "client.opendp_query(\n", - " opendp_pipeline = bill_length_transformation_pipeline,\n", - " dummy=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d06c59dc", - "metadata": {}, - "source": [ - "This is because the server will only allow measurement pipeline with differentially private results. She adds Laplacian noise to the pipeline and should be able to instantiate the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "b8162859", - "metadata": {}, - "outputs": [], - "source": [ - "var_bill_length_measurement_pipeline = (\n", - " bill_length_transformation_pipeline >>\n", - " meas.then_laplace(scale=5.0)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "fc7e0ecd", - "metadata": {}, - "source": [ - "Now that there is a measurement, she is able to apply the pipeline on the dummy dataset of the server." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "df61bce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for variance: 27.36\n" - ] - } - ], - "source": [ - "dummy_var_res = client.opendp_query(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for variance: {np.round(dummy_var_res['query_response'], 2)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "ded11ac4", - "metadata": {}, - "source": [ - "With opendp, the function `estimate_opendp_cost` is particularly useful to estimate the used `epsilon` and `delta` based on the `scale` value." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "7ae7f735", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 0.7163742690067888, 'delta_cost': 0}" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1c791d36", - "metadata": {}, - "source": [ - "She can now execute the query on the real dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "085555a5", - "metadata": {}, - "outputs": [], - "source": [ - "var_res = client.opendp_query(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "674332e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins: 342 (from previous smartnoise-sql query).\n", - "Average bill length: 42.93 (from previous smartnoise-sql query).\n", - "Variance of bill length: 27.686 (from opendp query).\n" - ] - } - ], - "source": [ - "print(f\"Number of penguins: {nb_penguins} (from previous smartnoise-sql query).\")\n", - "\n", - "print(f\"Average bill length: {np.round(avg_bill_length, 2)} (from previous smartnoise-sql query).\")\n", - "\n", - "var_bill_length = var_res['query_response']\n", - "print(f\"Variance of bill length: {np.round(var_bill_length, 3)} (from opendp query).\")" - ] - }, - { - "cell_type": "markdown", - "id": "367081be-1159-45d8-9129-88fba20fb697", - "metadata": {}, - "source": [ - "She can now do all the postprocessing that she wants with the returned data without adding any privacy risk. " - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "f72b19d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Standard error of bill length: 0.28.\n" - ] - } - ], - "source": [ - "# Get standard error\n", - "standard_error = np.sqrt(var_bill_length/nb_penguins)\n", - "print(f\"Standard error of bill length: {np.round(standard_error, 2)}.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "62630a03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The 95% confidence interval of the bill length of all penguins is [42.37, 43.49].\n" - ] - } - ], - "source": [ - " # Compute the 95% confidence interval\n", - "ZSCORE = 1.96\n", - "lower_bound = np.round(avg_bill_length - ZSCORE*standard_error, 2)\n", - "upper_bound = np.round(avg_bill_length + ZSCORE*standard_error, 2)\n", - "print(f\"The 95% confidence interval of the bill length of all penguins is [{lower_bound}, {upper_bound}].\")" - ] - }, - { - "cell_type": "markdown", - "id": "0d30d98e-26f4-44ec-a0f0-7a039f344860", - "metadata": {}, - "source": [ - "### Count per species" - ] - }, - { - "cell_type": "markdown", - "id": "b6a3cc00-8734-4479-81a3-781c91b8eb06", - "metadata": {}, - "source": [ - "She can also creates an histogram of the number of penguin per species.\n", - "\n", - "She first extract the categories from the metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "558916a6-78a9-4589-abe8-41472f0c66d7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Adelie', 'Chinstrap', 'Gentoo']" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "categories = penguin_metadata['columns']['species']['categories']\n", - "categories" - ] - }, - { - "cell_type": "markdown", - "id": "5f05aeb0-42d1-4444-a458-d2989494f544", - "metadata": {}, - "source": [ - "Then, writes the pipeline:" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "505a2793-6750-4f2a-9a9d-09f3a4ae4099", - "metadata": {}, - "outputs": [], - "source": [ - "species_count_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"species\", TOA=str) >>\n", - " trans.then_count_by_categories(categories=categories) >>\n", - " meas.then_laplace(scale=0.5)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "0f4109a9-c2f2-4365-bfa1-b5b2512919f9", - "metadata": {}, - "source": [ - "Verify it works on the dummy:" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "712b1c74-cac6-4b3f-9421-8d8ffa9debc7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for histogram: [38, 33, 28, 0]\n" - ] - } - ], - "source": [ - "dummy_res = client.opendp_query(\n", - " opendp_pipeline = species_count_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for histogram: {dummy_res['query_response']}\")" - ] - }, - { - "cell_type": "markdown", - "id": "66de7794-9aae-41d1-ba98-6b234a05d6f8", - "metadata": {}, - "source": [ - "Checks the required cost:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "3f942b1b-94bc-468e-8374-3034f2e6b8ce", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 2.0, 'delta_cost': 0}" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = species_count_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1adfe446-ff13-41f4-9f3c-9998e2ae0f00", - "metadata": {}, - "source": [ - "And finally apply the pipeline on the real dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "c6fe47b7-048e-404a-bedb-720e734b0c70", - "metadata": {}, - "outputs": [], - "source": [ - "species_counts_res = client.opendp_query(\n", - " opendp_pipeline = species_count_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "78d1aa05-2777-476b-9520-f9206399670d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'requested_by': 'Dr. Antartica',\n", - " 'query_response': [152, 68, 124, 0],\n", - " 'spent_epsilon': 2.0,\n", - " 'spent_delta': 0}" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "species_counts_res" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "b8d5fd82-5ded-47ec-8135-f6870865055d", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Species Adelie has 152 penguins.\n", - "Species Chinstrap has 68 penguins.\n", - "Species Gentoo has 124 penguins.\n", - "Species Unknown has 0 penguins.\n" - ] - } - ], - "source": [ - "for i, count in enumerate(species_counts_res['query_response']):\n", - " if i == len(categories):\n", - " print(f\"Species Unknown has {count} penguins.\")\n", - " else:\n", - " print(f\"Species {categories[i]} has {count} penguins.\")" - ] - }, - { - "cell_type": "markdown", - "id": "94eaf59b-c108-424c-8978-b1c86e141ccb", - "metadata": {}, - "source": [ - "## Step 5: See archives of queries" - ] - }, - { - "cell_type": "markdown", - "id": "64003c53-de56-4bdc-a3c2-0c3e40031919", - "metadata": {}, - "source": [ - "She now wants to verify all the queries that she did on the real data. It is possible because an archive of all queries is kept in a secure database. With a function call she can see her queries, budget and associated responses." - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "008fd230-cdfd-4e03-91ce-5a60b06c106d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'query_str': 'SELECT COUNT(*) AS nb_penguins, AVG(bill_length_mm) AS avg_bill_length_mm FROM df',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'epsilon': 0.5,\n", - " 'delta': 0.0001,\n", - " 'mechanisms': {},\n", - " 'postprocess': True},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': {'index': [0],\n", - " 'columns': ['nb_penguins', 'avg_bill_length_mm'],\n", - " 'data': [[342, 42.93065072561188]],\n", - " 'index_names': [None],\n", - " 'column_names': [None]},\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387},\n", - " 'timestamp': 1717666971.0667355,\n", - " 'dp_librairy': 'smartnoise_sql'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': 27.68648927291686,\n", - " 'spent_epsilon': 0.7163742690067888,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1717666974.674265,\n", - " 'dp_librairy': 'opendp'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': [152, 68, 124, 0],\n", - " 'spent_epsilon': 2.0,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1717666977.146055,\n", - " 'dp_librairy': 'opendp'}]" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "previous_queries = client.get_previous_queries()\n", - "previous_queries" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.0rc1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/Demo_Client_Notebook_polars.html b/html/de/notebooks/Demo_Client_Notebook_polars.html deleted file mode 100644 index 459fc598..00000000 --- a/html/de/notebooks/Demo_Client_Notebook_polars.html +++ /dev/null @@ -1,1470 +0,0 @@ - - - - - - - Lomas: Client demo with polar — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Lomas: Client demo with polar

-

This notebook showcases how researcher could use the lomas platform. It explains the different functionnalities provided by the lomas-client library to interact with the secure server.

-

The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.

-

Each user has access to one or multiple projects and for each dataset has a limited budget with \(\epsilon\) and \(\delta\) values.

-
-
[1]:
-
-
-
from IPython.display import Image
-Image(filename="images/image_demo_client.png", width=800)
-
-
-
-
-
[1]:
-
-
-
-../_images/notebooks_Demo_Client_Notebook_polars_2_0.png -
-
-

🐧🐧🐧 In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.

-

Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.

-

This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library lomas-client. 🐧🐧🐧

-
-

Step 1: Install the library

-

To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library lomas-client on her local developping environment.

-

It can be installed via the pip command:

-
-
[2]:
-
-
-
#!pip install lomas-cliententententent
-
-
-
-
-
[3]:
-
-
-
from lomas_client.client import Client
-import numpy as np
-
-
-
-
-
-

Step 2: Initialise the client

-

Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server.

-

To create the client, Dr. Antartica needs to give it a few parameters: - a url: the root application endpoint to the remote secure server. - user_name: her name as registered in the database (Dr. Alice Antartica) - dataset_name: the name of the dataset that she wants to query (PENGUIN)

-

She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit (as is done in the Admin Notebook for Users and Datasets management).

-
-
[5]:
-
-
-
APP_URL = "http://lomas_server_dev:80"
-#APP_URL = "https://lomas-server.lab.sspcloud.fr"
-USER_NAME = "Dr. Antartica"
-DATASET_NAME = "PENGUIN"
-client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)
-
-
-
-

And that’s it for the preparation. She is now ready to use the various functionnalities offered by lomas_client.

-
-
-

Step 3: Understand the functionnalities of the library

-
-

Getting dataset metadata

-

Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the get_dataset_metadata() function of the client. As this is public information, this does not cost any budget.

-

This function returns metadata information in a format based on SmartnoiseSQL dictionary format, where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). Any metadata is required for Smartnoise-SQL is also required here and additional information such that the different categories in a string type column column can be added.

-
-
[6]:
-
-
-
penguin_metadata = client.get_dataset_metadata()
-penguin_metadata
-
-
-
-
-
[6]:
-
-
-
-
-{'max_ids': 1,
- 'row_privacy': True,
- 'censor_dims': False,
- 'columns': {'species': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Adelie', 'Chinstrap', 'Gentoo']},
-  'island': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Torgersen', 'Biscoe', 'Dream']},
-  'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},
-  'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},
-  'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},
-  'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},
-  'sex': {'type': 'string',
-   'cardinality': 2,
-   'categories': ['MALE', 'FEMALE']}}}
-
-
-

Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) with their associated categories (i.e. the species column has 3 possibilities: ‚Adelie‘, ‚Chinstrap‘, ‚Gentoo‘) and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds (i.e. the body mass of penguin ranges from 2000 to 7000 gramms). She also knows based on the field max_ids: 1 that each penguin can only be once in the dataset and on the field -row_privacy: True that each row represents a single penguin.

-
-
-

Get a dummy dataset

-

Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset.

-

Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets. Getting a dummy dataset does not affect the budget as there is no differential privacy here. It is not a synthetic dataset and all that could be learn here is already present in the public metadata (it is created randomly on the fly based on the -metadata).

-

Dr. Antartica first create a dummy dataset with 200 rows and chooses a seed of 0.

-
-
[7]:
-
-
-
NB_ROWS = 200
-SEED = 0
-
-
-
-
-
[8]:
-
-
-
df_dummy = client.get_dummy_dataset(
-    nb_rows = NB_ROWS,
-    seed = SEED
-)
-
-print(df_dummy.shape)
-df_dummy.head()
-
-
-
-
-
-
-
-
-(200, 7)
-
-
-
-
[8]:
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0GentooBiscoe49.20847316.117959190.1259502873.291927FEMALE
1GentooTorgersen55.03162819.963435242.9291423639.940005FEMALE
2ChinstrapTorgersen51.09671816.777518159.9614935401.743330MALE
3AdelieBiscoe49.07091114.796037244.5301532316.038092MALE
4ChinstrapBiscoe44.82791813.246787236.9488535036.246870FEMALE
-
-
-
-
-

Query on dummy dataset

-

Now that she has an idea of what the data looks like, she wants to start querying the real dataset for her research.

-

However, her budget is limited and it would be a waste to spend it by mistake on a coding error. Therefore the client/server pipeline has functionnal testing capabilities for the users. It is possible to test a query on a dummy dataset to ensure that everything is working properly. Dr. Antartica will not be able to use the results of a dummy query for her analysis (as the data is random) but if the query on the dummy dataset works, she can be confident that her query will also work on the -real dataset. This functionnal testing on the dummy does not have any impact on the budget as it is on random data only.

-

To test on the dummy data instead of the real data, the function call is exactly the same with the only exception of the flag dummy=True. In the following cell, she will test with smartnoise_query but it is the same flag for opendp_query. She can optionnaly give two additional parameters to set the seed and the number of rows of the dummy dataset.

-

Another more advanced possibility for functionnal tests with the dummy is to compare results of queries on a local dummy and the remote dummy with a very high budget: - create a local dummy on the notebook with a specific seed and number of rows - compute locally the wanted query on this local dummy with python functions like numpy - query the server on the same remote dummy with (dummy=True, same seed and same number of row) and a very big buget to limit noise as much as possible (don’t -worry this won’t cost any real budget) - compare and verify that the local and remote dummy have similar results.

-
-

Average and number of rows with smartnoise-sql library on remote dummy

-

Dr. Antartica will now try a query to get the number of penguin and their average bill length (in mm) on the dummy dataset. She does not forget to - set the dummy flag to True - set very high budget values to be able to compare results with a similar local dummy (with the same seed and number of rows) if she wants to verify that the function do what is expected. Here she will just check that the number of rows is close to what she sets as parameter.

-
-
[9]:
-
-
-
# Number of penguin and average bill length in mm
-QUERY = "SELECT COUNT(*) AS nb_penguins, \
-        AVG(bill_length_mm) AS avg_bill_length_mm \
-        FROM df"
-
-
-
-
-
[10]:
-
-
-
# On the remote server dummy dataframe
-dummy_res = client.smartnoise_query(
-    query = QUERY,
-    epsilon = 100.0,
-    delta = 0.99,
-    dummy = True,
-    nb_rows = NB_ROWS,
-    seed = SEED
-)
-
-
-
-
-
[11]:
-
-
-
print(f"Average bill length in remote dummy: {np.round(dummy_res['query_response']['avg_bill_length_mm'][0], 2)}mm.")
-print(f"Number of rows in remote dummy: {np.round(dummy_res['query_response']['nb_penguins'][0], 2)}.")
-
-
-
-
-
-
-
-
-Average bill length in remote dummy: 47.51mm.
-Number of rows in remote dummy: 200.
-
-
-

No functionnal errors happened and the estimated number of rows is very close (if not equal) to the number of rows that she set for the dummy dataframe. She is now even more confident in using her query on the server.

-
-
-
-

Get current budget

-

It is the first time that Dr. Antartica connects to the server and she wants to know how much buget the queen assigned her. Therefore, she calls the fonction get_initial_budget.

-
-
[12]:
-
-
-
client.get_initial_budget()
-
-
-
-
-
[12]:
-
-
-
-
-{'initial_epsilon': 10, 'initial_delta': 0.005}
-
-
-

She sees that she has 10.0 epsilon and 0.005 epsilon at her disposal.

-

Then she checks her total spent budget get_total_spent_budget. As she only did queries on metadata on dummy dataframes, this should still be 0.

-
-
[13]:
-
-
-
client.get_total_spent_budget()
-
-
-
-
-
[13]:
-
-
-
-
-{'total_spent_epsilon': 0, 'total_spent_delta': 0}
-
-
-

It will also be useful to know what the remaining budget is. Therefore, she calls the function get_remaining_budget. It just substarcts the total spent budget from the initial budget.

-
-
[14]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[14]:
-
-
-
-
-{'remaining_epsilon': 10, 'remaining_delta': 0.005}
-
-
-

As expected, for now the remaining budget is equal to the inital budget.

-
-
-

Estimate cost of a query

-

Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The estimate cost function returns the estimated real cost of any query.

-

Again, of course, this will not impact the user’s budget.

-

Dr. Antartica checks the budget that computing the average bill length will really cost her if she asks the query with an epsilon and a delta.

-
-
[15]:
-
-
-
EPSILON = 0.5
-DELTA = 1e-4
-
-
-
-
-
[16]:
-
-
-
client.estimate_smartnoise_cost(
-    query = QUERY,
-    epsilon = EPSILON,
-    delta = DELTA
-)
-
-
-
-
-
[16]:
-
-
-
-
-{'epsilon_cost': 1.5, 'delta_cost': 0.00014999500000001387}
-
-
-

This query would actually cost her 1.5 epsilon and delta 1.4999e-4. She decides that it is good enough.

-
-
-

Query on real private dataset with smartnoise-sql

-

Now that all the safeguard functions were tested, Dr. Antartica is ready to query on the real dataset and get a differentially private response of the number of penguins and average bill length. By default, the flag dummy is False so setting it is optional. She uses the values of epsilon and delta that she selected just before.

-

Careful: This command DOES spend the budget of the user and the remaining budget is updated for every query.

-
-
[17]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[17]:
-
-
-
-
-{'remaining_epsilon': 10, 'remaining_delta': 0.005}
-
-
-
-
[18]:
-
-
-
response = client.smartnoise_query(
-    query = QUERY,
-    epsilon = EPSILON,
-    delta = DELTA,
-    dummy = False # Optionnal
-)
-
-
-
-
-
[19]:
-
-
-
nb_penguins = response['query_response']['nb_penguins'].iloc[0]
-print(f"Number of penguins in real data: {nb_penguins}.")
-
-avg_bill_length = np.round(response['query_response']['avg_bill_length_mm'].iloc[0], 2)
-print(f"Average bill length of penguins in real data: {avg_bill_length}mm.")
-
-
-
-
-
-
-
-
-Number of penguins in real data: 344.
-Average bill length of penguins in real data: 43.84mm.
-
-
-

After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:

-
-
[20]:
-
-
-
response
-
-
-
-
-
[20]:
-
-
-
-
-{'requested_by': 'Dr. Antartica',
- 'query_response':    nb_penguins  avg_bill_length_mm
- 0          344           43.836323,
- 'spent_epsilon': 1.5,
- 'spent_delta': 0.00014999500000001387}
-
-
-
-
[21]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[21]:
-
-
-
-
-{'remaining_epsilon': 8.5, 'remaining_delta': 0.004850004999999986}
-
-
-

As can be seen in get_total_spent_budget(), it is the budget estimated with estimate_smartnoise_cost() that was spent.

-
-
[22]:
-
-
-
client.get_total_spent_budget()
-
-
-
-
-
[22]:
-
-
-
-
-{'total_spent_epsilon': 1.5, 'total_spent_delta': 0.00014999500000001387}
-
-
-

Dr. Antartica has now a differentially private estimation of the number of penguins in the dataset and is confident to use the library for the rest of her analyses.

-
-
-
-

Step 4: Penguin statistics with opendp

-
-
[24]:
-
-
-
import opendp.prelude as dp
-import opendp.transformations as trans
-import opendp.measurements as meas
-
-dp.enable_features("contrib")
-
-
-
-
-

Confidence intervals for bill length over the whole population

-

She is first interested to have a better idea of the distribution of bill length of all species. She already has the count and average from the previous step, so she only needs the variance values.

-

She first checks the metadata again to use the relevant values in the pipeline.

-
-
[25]:
-
-
-
penguin_metadata
-
-
-
-
-
[25]:
-
-
-
-
-{'max_ids': 1,
- 'row_privacy': True,
- 'censor_dims': False,
- 'columns': {'species': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Adelie', 'Chinstrap', 'Gentoo']},
-  'island': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Torgersen', 'Biscoe', 'Dream']},
-  'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},
-  'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},
-  'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},
-  'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},
-  'sex': {'type': 'string',
-   'cardinality': 2,
-   'categories': ['MALE', 'FEMALE']}}}
-
-
-

She can define the columns names and the bounds of the relevant column.

-
-
[26]:
-
-
-
columns = ["species", "island", "bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g", "sex"]
-
-
-
-
-
[28]:
-
-
-
bill_length_min = penguin_metadata['columns']['bill_length_mm']['lower']
-bill_length_max = penguin_metadata['columns']['bill_length_mm']['upper']
-bill_length_min, bill_length_max
-
-
-
-
-
[28]:
-
-
-
-
-(30.0, 65.0)
-
-
-

She can now define the pipeline of the transformation to have the variance that she wants on the data:

-
-
[29]:
-
-
-
bill_length_transformation_pipeline = (
-    trans.make_split_dataframe(separator=",", col_names=columns) >>
-    trans.make_select_column(key="bill_length_mm", TOA=str) >>
-    trans.then_cast_default(TOA=float) >>
-    trans.then_clamp(bounds=(bill_length_min, bill_length_max)) >>
-    trans.then_resize(size=nb_penguins.tolist(), constant=avg_bill_length) >>
-    trans.then_variance()
-)
-
-
-
-

However, when she tries to execute it on the server, she has an error (see below).

-
-
[30]:
-
-
-
# Expect to fail !!!
-client.opendp_query(
-    opendp_pipeline = bill_length_transformation_pipeline,
-    dummy=True
-)
-
-
-
-
-
-
-
-
-Server error status 400: {"InvalidQueryException":"The pipeline provided is not a measurement. It cannot be processed in this server."}
-
-
-

This is because the server will only allow measurement pipeline with differentially private results. She adds Laplacian noise to the pipeline and should be able to instantiate the pipeline.

-
-
[31]:
-
-
-
var_bill_length_measurement_pipeline = (
-    bill_length_transformation_pipeline >>
-    meas.then_laplace(scale=5.0)
-)
-
-
-
-

Now that there is a measurement, she is able to apply the pipeline on the dummy dataset of the server.

-
-
[32]:
-
-
-
dummy_var_res = client.opendp_query(
-    opendp_pipeline = var_bill_length_measurement_pipeline,
-    dummy=True
-)
-print(f"Dummy result for variance: {np.round(dummy_var_res['query_response'], 2)}")
-
-
-
-
-
-
-
-
-Dummy result for variance: 32.97
-
-
-

With opendp, the function estimate_opendp_cost is particularly useful to estimate the used epsilon and delta based on the scale value.

-
-
[33]:
-
-
-
cost_res = client.estimate_opendp_cost(
-    opendp_pipeline = var_bill_length_measurement_pipeline
-)
-cost_res
-
-
-
-
-
[33]:
-
-
-
-
-{'epsilon_cost': 0.7122093023265229, 'delta_cost': 0}
-
-
-

She can now execute the query on the real dataset.

-
-
[34]:
-
-
-
var_res = client.opendp_query(
-    opendp_pipeline = var_bill_length_measurement_pipeline,
-)
-
-
-
-
-
[35]:
-
-
-
print(f"Number of penguins: {nb_penguins} (from previous smartnoise-sql query).")
-
-print(f"Average bill length: {np.round(avg_bill_length, 2)} (from previous smartnoise-sql query).")
-
-var_bill_length = var_res['query_response']
-print(f"Variance of bill length: {np.round(var_bill_length, 3)} (from opendp query).")
-
-
-
-
-
-
-
-
-Number of penguins: 344 (from previous smartnoise-sql query).
-Average bill length: 43.84 (from previous smartnoise-sql query).
-Variance of bill length: 28.052 (from opendp query).
-
-
-

She can now do all the postprocessing that she wants with the returned data without adding any privacy risk.

-
-
[36]:
-
-
-
# Get standard error
-standard_error = np.sqrt(var_bill_length/nb_penguins)
-print(f"Standard error of bill length: {np.round(standard_error, 2)}.")
-
-
-
-
-
-
-
-
-Standard error of bill length: 0.29.
-
-
-
-
[37]:
-
-
-
 # Compute the 95% confidence interval
-ZSCORE = 1.96
-lower_bound = np.round(avg_bill_length - ZSCORE*standard_error, 2)
-upper_bound = np.round(avg_bill_length + ZSCORE*standard_error, 2)
-print(f"The 95% confidence interval of the bill length of all penguins is [{lower_bound}, {upper_bound}].")
-
-
-
-
-
-
-
-
-The 95% confidence interval of the bill length of all penguins is [43.28, 44.4].
-
-
-
-
-

Count per species

-

She can also creates an histogram of the number of penguin per species.

-

She first extract the categories from the metadata:

-
-
[39]:
-
-
-
categories = penguin_metadata['columns']['species']['categories']
-categories
-
-
-
-
-
[39]:
-
-
-
-
-['Adelie', 'Chinstrap', 'Gentoo']
-
-
-

Then, writes the pipeline:

-
-
[40]:
-
-
-
species_count_pipeline = (
-    trans.make_split_dataframe(separator=",", col_names=columns) >>
-    trans.make_select_column(key="species", TOA=str) >>
-    trans.then_count_by_categories(categories=categories) >>
-    meas.then_laplace(scale=0.5)
-)
-
-
-
-

Verify it works on the dummy:

-
-
[41]:
-
-
-
dummy_res = client.opendp_query(
-    opendp_pipeline = species_count_pipeline,
-    dummy=True
-)
-print(f"Dummy result for histogram: {dummy_res['query_response']}")
-
-
-
-
-
-
-
-
-Dummy result for histogram: [38, 33, 29, 0]
-
-
-

Checks the required cost:

-
-
[42]:
-
-
-
cost_res = client.estimate_opendp_cost(
-    opendp_pipeline = species_count_pipeline
-)
-cost_res
-
-
-
-
-
[42]:
-
-
-
-
-{'epsilon_cost': 2.0, 'delta_cost': 0}
-
-
-

And finally apply the pipeline on the real dataset:

-
-
[43]:
-
-
-
species_counts_res = client.opendp_query(
-    opendp_pipeline = species_count_pipeline,
-)
-
-
-
-
-
[44]:
-
-
-
for i, count in enumerate(species_counts_res['query_response']):
-    if i == len(categories):
-        print(f"Species Unknown has {count} penguins.")
-    else:
-        print(f"Species {categories[i]} has {count} penguins.")
-
-
-
-
-
-
-
-
-Species Adelie has 152 penguins.
-Species Chinstrap has 68 penguins.
-Species Gentoo has 124 penguins.
-Species Unknown has 0 penguins.
-
-
-
-
-
-

Step 5: See archives of queries

-

She now wants to verify all the queries that she did on the real data. It is possible because an archive of all queries is kept in a secure database. With a function call she can see her queries, budget and associated responses.

-
-
[47]:
-
-
-
previous_queries = client.get_previous_queries()
-previous_queries
-
-
-
-
-
[47]:
-
-
-
-
-[{'user_name': 'Dr. Antartica',
-  'dataset_name': 'PENGUIN',
-  'client_input': {'query_str': 'SELECT COUNT(*) AS nb_penguins,         AVG(bill_length_mm) AS avg_bill_length_mm         FROM df',
-   'dataset_name': 'PENGUIN',
-   'epsilon': 0.5,
-   'delta': 0.0001,
-   'mechanisms': {},
-   'postprocess': True},
-  'response': {'requested_by': 'Dr. Antartica',
-   'query_response': {'index': [0],
-    'columns': ['nb_penguins', 'avg_bill_length_mm'],
-    'data': [[342, 43.13189211774378]],
-    'index_names': [None],
-    'column_names': [None]},
-   'spent_epsilon': 1.5,
-   'spent_delta': 0.00014999500000001387},
-  'timestamp': 1714988610.19844,
-  'dp_librairy': 'smartnoise_sql'},
- {'user_name': 'Dr. Antartica',
-  'dataset_name': 'PENGUIN',
-  'client_input': {'dataset_name': 'PENGUIN',
-   'opendp_json': <opendp.mod.Measurement at 0x7f2513f245f0>,
-   'fixed_delta': None},
-  'response': {'requested_by': 'Dr. Antartica',
-   'query_response': 10.184409589415381,
-   'spent_epsilon': 0.7163742690067888,
-   'spent_delta': 0},
-  'timestamp': 1714988634.4750721,
-  'dp_librairy': 'opendp'},
- {'user_name': 'Dr. Antartica',
-  'dataset_name': 'PENGUIN',
-  'client_input': {'dataset_name': 'PENGUIN',
-   'opendp_json': <opendp.mod.Measurement at 0x7f2513f24b90>,
-   'fixed_delta': None},
-  'response': {'requested_by': 'Dr. Antartica',
-   'query_response': [152, 67, 123, 0],
-   'spent_epsilon': 2.0,
-   'spent_delta': 0},
-  'timestamp': 1714988645.1652308,
-  'dp_librairy': 'opendp'},
- {'user_name': 'Dr. Antartica',
-  'dataset_name': 'PENGUIN',
-  'client_input': {'query_str': 'SELECT COUNT(*) AS nb_penguins,         AVG(bill_length_mm) AS avg_bill_length_mm         FROM df',
-   'dataset_name': 'PENGUIN',
-   'epsilon': 0.5,
-   'delta': 0.0001,
-   'mechanisms': {},
-   'postprocess': True},
-  'response': {'requested_by': 'Dr. Antartica',
-   'query_response': {'index': [0],
-    'columns': ['nb_penguins', 'avg_bill_length_mm'],
-    'data': [[344, 43.83632334140567]],
-    'index_names': [None],
-    'column_names': [None]},
-   'spent_epsilon': 1.5,
-   'spent_delta': 0.00014999500000001387},
-  'timestamp': 1714990018.1717296,
-  'dp_librairy': 'smartnoise_sql'},
- {'user_name': 'Dr. Antartica',
-  'dataset_name': 'PENGUIN',
-  'client_input': {'dataset_name': 'PENGUIN',
-   'opendp_json': <opendp.mod.Measurement at 0x7f2513f25910>,
-   'fixed_delta': None},
-  'response': {'requested_by': 'Dr. Antartica',
-   'query_response': 28.051717576669322,
-   'spent_epsilon': 0.7122093023265229,
-   'spent_delta': 0},
-  'timestamp': 1714990066.3611896,
-  'dp_librairy': 'opendp'},
- {'user_name': 'Dr. Antartica',
-  'dataset_name': 'PENGUIN',
-  'client_input': {'dataset_name': 'PENGUIN',
-   'opendp_json': <opendp.mod.Measurement at 0x7f2513f24290>,
-   'fixed_delta': None},
-  'response': {'requested_by': 'Dr. Antartica',
-   'query_response': [152, 68, 124, 0],
-   'spent_epsilon': 2.0,
-   'spent_delta': 0},
-  'timestamp': 1714990074.9196112,
-  'dp_librairy': 'opendp'}]
-
-
-
-
-
-

FSO Example: (Synthetic) Income dataset

-
-

Boxplot of income per partitions of the population

-
-

Disclaimer: Temporary Version of OpenDP with Polars

-
-
[50]:
-
-
-
# Import library
-import numpy as np
-import pandas as pd
-import polars as pl
-import opendp.prelude as dp
-
-dp.enable_features("contrib")
-
-
-
-
-
-

Switching user and exploring new dataset

-

Let us now change user and select another dataset to play with. For this example, we will use an income distribution dataset (synthetic) from the Swiss Federal Statistical Office.

-
-
[51]:
-
-
-
USER_NAME = "Dr. FSO"
-DATASET_NAME = "FSO_INCOME_SYNTHETIC"
-client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)
-
-fso_income_metadata = client.get_dataset_metadata()
-fso_income_metadata
-
-
-
-
-
[51]:
-
-
-
-
-{'max_ids': 1,
- 'columns': {'region': {'type': 'int'},
-  'eco_branch': {'type': 'int'},
-  'profession': {'type': 'int'},
-  'education': {'type': 'int'},
-  'age': {'type': 'int'},
-  'sex': {'type': 'int'},
-  'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}
-
-
-

Let us also check how much budget we have.

-
-
[52]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[52]:
-
-
-
-
-{'remaining_epsilon': 43.0, 'remaining_delta': 0.005}
-
-
-
-
-

Data preparation

-
-

Column types and bounds

-
-
[53]:
-
-
-
# Income bounds
-income_lower_bound, income_upper_bound = 1_000.0, 100_000.0
-
-
-
-
-
[54]:
-
-
-
# Define dtype domain with bounds
-lf_domain = dp.lazyframe_domain([
-    dp.series_domain("region", dp.atom_domain(T=int)),
-    dp.series_domain("eco_branch", dp.atom_domain(T=int)),
-    dp.series_domain("profession", dp.atom_domain(T=int)),
-    dp.series_domain("education", dp.atom_domain(T=int)),
-    dp.series_domain("age", dp.atom_domain(T=int)),
-    dp.series_domain("sex", dp.atom_domain(T=int)),
-    dp.series_domain("income", dp.atom_domain(
-        T=float,
-        bounds=(income_lower_bound, income_upper_bound)
-    ))
-])
-
-
-
-
-
-
-
-
----------------------------------------------------------------------------
-AttributeError                            Traceback (most recent call last)
-Cell In[54], line 2
-      1 # Define dtype domain with bounds
-----> 2 lf_domain = dp.lazyframe_domain([
-      3     dp.series_domain("region", dp.atom_domain(T=int)),
-      4     dp.series_domain("eco_branch", dp.atom_domain(T=int)),
-      5     dp.series_domain("profession", dp.atom_domain(T=int)),
-      6     dp.series_domain("education", dp.atom_domain(T=int)),
-      7     dp.series_domain("age", dp.atom_domain(T=int)),
-      8     dp.series_domain("sex", dp.atom_domain(T=int)),
-      9     dp.series_domain("income", dp.atom_domain(
-     10         T=float,
-     11         bounds=(income_lower_bound, income_upper_bound)
-     12     ))
-     13 ])
-
-AttributeError: module 'opendp.prelude' has no attribute 'lazyframe_domain'
-
-
-
-
-

Counts per partition of the population

-
-
[ ]:
-
-
-
# Total
-total_counts = pl.LazyFrame({
-    "counts": [2_032_543]
-}, schema_overrides={"counts": pl.UInt32})
-
-# For sex
-sex_counts = pl.LazyFrame({
-    "sex": [0, 1],
-    "counts": [634_720, 1_397_823]
-}, schema_overrides={"sex": pl.Int32, "counts": pl.UInt32})
-
-# For region
-region_counts = pl.LazyFrame({
-    "region": [1, 2, 3, 4, 5, 6, 7],
-    "counts": [352_001, 474_690, 267_304, 366_879, 284_638, 210_800, 76_231]
-}, schema_overrides={"region": pl.Int32, "counts": pl.UInt32})
-
-# For region and sex
-sex_region_counts = pl.LazyFrame({
-    "sex": [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
-    "region": [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7],
-    "counts": [113_367, 148_265, 83_326, 113_715, 87_668, 64_357, 24_022, 238_634, 326_425, 183_978, 253_164, 196_970, 146_443, 52_209]
-}, schema_overrides={"sex": pl.Int32, "region": pl.Int32, "counts": pl.UInt32})
-
-# Add counts to margin
-lf_domain = lf_domain.with_counts(
-    total_counts
-).with_counts(sex_counts).with_counts(region_counts).with_counts(sex_region_counts)
-
-
-
-
-
-
-

Income distribution for partitions of the population:

-
-

Prepare the pipeline

-
-
[ ]:
-
-
-
# Prepare a list of candidates
-candidates = [x * 250.0 for x in range(8, 52)]
-print(candidates)
-
-
-
-
-
[ ]:
-
-
-
# Partitions
-PARTITIONS = ['sex', 'region']
-
-
-
-
-
[ ]:
-
-
-
metric = dp.symmetric_distance()                                     # Input metric
-expr_domain = dp.expr_domain(lf_domain, grouping_columns=PARTITIONS) # Expr domain (Groupby)
-temperature = 1000.0                                                  # Noise parameter
-
-
-
-
-
[ ]:
-
-
-
def make_quantile_pipeline(quantile):
-    # Create expression
-    return (
-        (dp.csv_domain(lf_domain), metric)
-        >> dp.t.then_scan_csv()
-        >> dp.t.then_groupby_stable(PARTITIONS)
-        >> dp.m.then_private_agg(
-            dp.c.make_basic_composition(
-                [
-                    (expr_domain, dp.l1(metric))
-                    >> dp.t.then_col('income')
-                    >> dp.m.then_private_quantile_expr(candidates, temperature, quantile)
-                ]
-            )
-        )
-        >> dp.t.make_collect(lf_domain, metric)
-    )
-
-
-
-
-
[ ]:
-
-
-
q25 = make_quantile_pipeline(0.25)
-q50 = make_quantile_pipeline(0.5)
-q75 = make_quantile_pipeline(0.75)
-
-
-
-
-
-

Apply pipeline on data

-

Let us first try out the pipeline with a dummy query and then estimate the cost of the different pipelines.

-
-
[ ]:
-
-
-
dummy_r25 = client.opendp_query(
-    opendp_pipeline = q25,
-    dummy=True,
-    input_data_type="path"
-)
-dummy_r25
-
-
-
-
-
[ ]:
-
-
-
cost_q25 = client.estimate_opendp_cost(q25, input_data_type="path")
-cost_q50 = client.estimate_opendp_cost(q50, input_data_type="path")
-cost_q75 = client.estimate_opendp_cost(q75, input_data_type="path")
-
-print(f"The estimated costs are respectively {cost_q25}, {cost_q50} and {cost_q75} for q25, q50 and q75")
-
-
-
-

Since our budget is 45, we know that we can execute these three pipelines remotely.

-
-
[ ]:
-
-
-
r25 = client.opendp_query(q25, input_data_type="path")
-r50 = client.opendp_query(q50, input_data_type="path")
-r75 = client.opendp_query(q75, input_data_type="path")
-
-
-
-

Let us put together the results and show them in a table. Notice that the output is a polars dataframe, we thus need to transform it to a pandas DataFrame if we want to work with pandas.

-
-
[ ]:
-
-
-
r25 = r25["query_response"].to_pandas()
-r50 = r50["query_response"].to_pandas()
-r75 = r75["query_response"].to_pandas()
-
-
-
-
-
[ ]:
-
-
-
results = pd.merge(r25, r50, on=PARTITIONS, suffixes=('_25', '_50'))
-results = pd.merge(results, r75, on=PARTITIONS)
-results.sort_values(by = ['region', 'sex']).head()
-
-
-
-
-
-

Visualise results

-
-
[ ]:
-
-
-
import seaborn as sns
-import matplotlib.pyplot as plt
-
-
-
-
-
[ ]:
-
-
-
def quantile_data(q1, q2, q3):
-    return np.concatenate((np.random.uniform(q1[0], q2[0], size=50), np.random.uniform(q2[0], q3[0], size=50)))
-
-results['data'] = results.apply(
-    lambda row: quantile_data(row["income_25"], row["income_50"], row["income"]),
-    axis=1,
-)
-results['sex'] = results['sex'].replace({0: 'woman', 1: 'man'})
-results['region'] = results['region'].replace({1: 'Lemanique', 2: 'Mittleland', 3: 'North-West', 4: 'Zürich', 5: 'Oriental', 6: 'Central', 7: 'Ticino'})
-results = results.explode('data', ignore_index=True)
-
-
-
-
-
[ ]:
-
-
-
plt.figure(figsize=(10, 6))
-sns.boxplot(x="region", y="data", hue="sex", data=results, palette="Set1", width=0.5);
-plt.xticks(fontsize=12)
-plt.yticks(fontsize=12)
-plt.xlabel('Regions', fontsize=15)
-plt.ylabel('Income per month (in CHF)', fontsize=15)
-plt.title('Income per partition of the population', fontsize=16)
-plt.show()
-
-
-
-
-
[ ]:
-
-
-

-
-
-
-
-
-
-
- - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/Demo_Client_Notebook_polars.ipynb b/html/de/notebooks/Demo_Client_Notebook_polars.ipynb deleted file mode 100644 index 7a2f05e4..00000000 --- a/html/de/notebooks/Demo_Client_Notebook_polars.ipynb +++ /dev/null @@ -1,1883 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3f18d338", - "metadata": {}, - "source": [ - "# Lomas: Client demo with polar" - ] - }, - { - "cell_type": "markdown", - "id": "1582a2ae", - "metadata": {}, - "source": [ - "This notebook showcases how researcher could use the lomas platform. It explains the different functionnalities provided by the `lomas-client` library to interact with the secure server.\n", - "\n", - "The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.\n", - "\n", - "Each user has access to one or multiple projects and for each dataset has a limited budget with $\\epsilon$ and $\\delta$ values." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "23bb4f13-7800-41b2-b429-68c2d02243d0", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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P/i1WfzOMjsFhZBuOCe6juo8qnMqAABR0XeGX/z8D3YMQDEioEOr+fPfsEOJXnlloR1CsXhr4VLnJj5fIZ1+nYsHc012CoBC+MtfnkqozpoOBUAP/flJO4Z8Uxiabjjn9ecHn8j7DysAACSj7/b8YAQULwI6OJ5/boFzkxdEJTlU/bWcqAixqlO6f1FUb82WbuLvueePdgxAOXh70bt2KE5V2z/xiQ8lnK++dPvN5ripE+0z4nSephRdYSx6K3EfqcrQNde833z7O//Suo++9n8/by686Ez7jDhdY59/foEdAwAge/qe4P2O4P3TdWnOB893mlrwWvT2UjsEoNjQBl0BlEIbdKoyqVJZLt38ecd1o3HVVZfYsY4rlrbfikW620PPW7QouNRGPtosKNR+og264kIbdOEKdR73fyb05TqsDdB//ep3EkrbJTsX6DM9b97LZtmy1a0/xtTXD4md6yeZc889PWU7o25nCMuXrUp4T81j3PjR5uyzZwV2hJBuGyupzgXex3U++sAHzzN/e3xe63VKy3HBBbPN6WfMdMZFpdWeeur52HOWtC6zblLGjx9pZs8+qcPnTf86KUD1vq+XvypssmuptvGjj8519pFbelnhn5b34ovPCu1owr88Ombuv/9hszC2bbSv3XmoiQSXttfXv/FFO5ZIP9j98pdtVaD0feALX7jRjrXp6DHVkX2ZD7RBB5Q+2qBL7zu6/1qU7DUdvRaJ+9pknVulao8122u3e21qbNyUUBPIXYewtrz921XX62FDB5u//W1e63LoenjBhbMT2vDL5fej0aNHtFt3zWf27Fl522733/+IefyxZ+1YfDlWrFhj5se+M2j7aR5TY+t91VWXtl7Xw7axlrW+oa7g7aVnqtjboCOgK4BiD+j0gf7SF79px+InH32QvScpndS+852v2LHk9OV+wSsLW79suydEfbBXrVqT9KISdHLUF/1UJyv3pOSeTETzPiP2BT/oS346Fzf/TYQed09I3hsSd3t15ESUznJ4qYTMt+/8mR2LO2nW8YEdOLjbZMXy1QkXANH7BPXm5F+eIP5lDLsYuxeFZBfjoJtyraNu1txt7F4YZp95UtJGbTuyvl7F8KUiagR04Qp1Hle7j94vOzq/XHHFhVn1AvrjH/82IZzx0zF67TWXhQYiqgqj6pnuOS+MSpH5PyP5COh0/hfv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", - "text/plain": [ - "" - ] - }, - "execution_count": 1, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_client.png\", width=800)" - ] - }, - { - "cell_type": "markdown", - "id": "5b73135c", - "metadata": {}, - "source": [ - "🐧🐧🐧\n", - "In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.\n", - "\n", - "Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.\n", - "\n", - "This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library `lomas-client`. \n", - "🐧🐧🐧" - ] - }, - { - "cell_type": "markdown", - "id": "01ae30d2", - "metadata": {}, - "source": [ - "## Step 1: Install the library\n", - "To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library `lomas-client` on her local developping environment. \n", - "\n", - "It can be installed via the pip command:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "28fbdd79-8c15-49a9-bcf9-fcdeac09d2b5", - "metadata": {}, - "outputs": [], - "source": [ - "#!pip install lomas-cliententententent" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "6fb569fc", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client.client import Client\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "9c63718b", - "metadata": {}, - "source": [ - "## Step 2: Initialise the client\n", - "\n", - "Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server. \n", - "\n", - "To create the client, Dr. Antartica needs to give it a few parameters:\n", - "- a url: the root application endpoint to the remote secure server.\n", - "- user_name: her name as registered in the database (Dr. Alice Antartica)\n", - "- dataset_name: the name of the dataset that she wants to query (PENGUIN)\n", - "\n", - "She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit (as is done in the Admin Notebook for Users and Datasets management)." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "941991f7", - "metadata": {}, - "outputs": [], - "source": [ - "APP_URL = \"http://lomas_server_dev:80\"\n", - "#APP_URL = \"https://lomas-server.lab.sspcloud.fr\"\n", - "USER_NAME = \"Dr. Antartica\"\n", - "DATASET_NAME = \"PENGUIN\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "markdown", - "id": "0ec400c8", - "metadata": {}, - "source": [ - "And that's it for the preparation. She is now ready to use the various functionnalities offered by `lomas_client`." - ] - }, - { - "cell_type": "markdown", - "id": "9b9a5f13", - "metadata": {}, - "source": [ - "## Step 3: Understand the functionnalities of the library" - ] - }, - { - "cell_type": "markdown", - "id": "c7cb5531", - "metadata": {}, - "source": [ - "### Getting dataset metadata\n", - "\n", - "Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the `get_dataset_metadata()` function of the client. As this is public information, this does not cost any budget.\n", - "\n", - "This function returns metadata information in a format based on [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). Any metadata is required for Smartnoise-SQL is also required here and additional information such that the different categories in a string type column column can be added." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "d15cbe39", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "penguin_metadata = client.get_dataset_metadata()\n", - "penguin_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "d338ed96", - "metadata": {}, - "source": [ - "Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) with their associated categories (i.e. the species column has 3 possibilities: 'Adelie', 'Chinstrap', 'Gentoo') and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds (i.e. the body mass of penguin ranges from 2000 to 7000 gramms). She also knows based on the field `max_ids: 1` that each penguin can only be once in the dataset and on the field `row_privacy: True` that each row represents a single penguin. " - ] - }, - { - "cell_type": "markdown", - "id": "5a3c899d", - "metadata": {}, - "source": [ - "### Get a dummy dataset\n", - "\n", - "Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset. \n", - "\n", - "Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets.\n", - "Getting a dummy dataset does not affect the budget as there is no differential privacy here. It is not a synthetic dataset and all that could be learn here is already present in the public metadata (it is created randomly on the fly based on the metadata).\n", - "\n", - "Dr. Antartica first create a dummy dataset with 200 rows and chooses a seed of 0." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "01f4365a", - "metadata": {}, - "outputs": [], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3f553b29", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0GentooBiscoe49.20847316.117959190.1259502873.291927FEMALE
1GentooTorgersen55.03162819.963435242.9291423639.940005FEMALE
2ChinstrapTorgersen51.09671816.777518159.9614935401.743330MALE
3AdelieBiscoe49.07091114.796037244.5301532316.038092MALE
4ChinstrapBiscoe44.82791813.246787236.9488535036.246870FEMALE
\n", - "
" - ], - "text/plain": [ - " species island bill_length_mm bill_depth_mm flipper_length_mm \n", - "0 Gentoo Biscoe 49.208473 16.117959 190.125950 \\\n", - "1 Gentoo Torgersen 55.031628 19.963435 242.929142 \n", - "2 Chinstrap Torgersen 51.096718 16.777518 159.961493 \n", - "3 Adelie Biscoe 49.070911 14.796037 244.530153 \n", - "4 Chinstrap Biscoe 44.827918 13.246787 236.948853 \n", - "\n", - " body_mass_g sex \n", - "0 2873.291927 FEMALE \n", - "1 3639.940005 FEMALE \n", - "2 5401.743330 MALE \n", - "3 2316.038092 MALE \n", - "4 5036.246870 FEMALE " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset(\n", - " nb_rows = NB_ROWS, \n", - " seed = SEED\n", - ")\n", - "\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "98e6fda2-dde7-4f8b-a787-c9a1e3571ebe", - "metadata": {}, - "source": [ - "### Query on dummy dataset\n", - "Now that she has an idea of what the data looks like, she wants to start querying the real dataset for her research. \n", - "\n", - "However, her budget is limited and it would be a waste to spend it by mistake on a coding error. Therefore the client/server pipeline has functionnal testing capabilities for the users. It is possible to test a query on a `dummy` dataset to ensure that everything is working properly. Dr. Antartica will not be able to use the results of a dummy query for her analysis (as the data is random) but if the query on the dummy dataset works, she can be confident that her query will also work on the real dataset.\n", - "This functionnal testing on the dummy does not have any impact on the budget as it is on random data only.\n", - "\n", - "To test on the dummy data instead of the real data, the function call is exactly the same with the only exception of the flag `dummy=True`. In the following cell, she will test with `smartnoise_query` but it is the same flag for `opendp_query`. She can optionnaly give two additional parameters to set the seed and the number of rows of the dummy dataset.\n", - "\n", - "Another more advanced possibility for functionnal tests with the dummy is to compare results of queries on a local dummy and the remote dummy with a very high budget: \n", - "- create a local dummy on the notebook with a specific seed and number of rows\n", - "- compute locally the wanted query on this local dummy with python functions like numpy\n", - "- query the server on the same remote dummy with (`dummy=True`, same seed and same number of row) and a very big buget to limit noise as much as possible (don't worry this won't cost any real budget)\n", - "- compare and verify that the local and remote dummy have similar results." - ] - }, - { - "cell_type": "markdown", - "id": "243c73e3-daec-45d6-a3c8-ae1d60439ec4", - "metadata": {}, - "source": [ - "#### Average and number of rows with smartnoise-sql library on remote dummy" - ] - }, - { - "cell_type": "markdown", - "id": "d1f8ea18-ccab-4f75-9490-b4d1144b39db", - "metadata": {}, - "source": [ - "Dr. Antartica will now try a query to get the number of penguin and their average bill length (in mm) on the dummy dataset. She does not forget to \n", - "- set the `dummy` flag to True\n", - "- set very high budget values to be able to compare results with a similar local dummy (with the same seed and number of rows) if she wants to verify that the function do what is expected. Here she will just check that the number of rows is close to what she sets as parameter." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "3946425d", - "metadata": {}, - "outputs": [], - "source": [ - "# Number of penguin and average bill length in mm\n", - "QUERY = \"SELECT COUNT(*) AS nb_penguins, \\\n", - " AVG(bill_length_mm) AS avg_bill_length_mm \\\n", - " FROM df\"" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "90cf2a6d", - "metadata": {}, - "outputs": [], - "source": [ - "# On the remote server dummy dataframe\n", - "dummy_res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0,\n", - " delta = 0.99,\n", - " dummy = True, \n", - " nb_rows = NB_ROWS,\n", - " seed = SEED\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a30f277e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average bill length in remote dummy: 47.51mm.\n", - "Number of rows in remote dummy: 200.\n" - ] - } - ], - "source": [ - "print(f\"Average bill length in remote dummy: {np.round(dummy_res['query_response']['avg_bill_length_mm'][0], 2)}mm.\")\n", - "print(f\"Number of rows in remote dummy: {np.round(dummy_res['query_response']['nb_penguins'][0], 2)}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "167e8c6d-6c93-4ab4-9ba7-bf7e783a6bc2", - "metadata": {}, - "source": [ - "No functionnal errors happened and the estimated number of rows is very close (if not equal) to the number of rows that she set for the dummy dataframe. She is now even more confident in using her query on the server." - ] - }, - { - "cell_type": "markdown", - "id": "324454ed", - "metadata": {}, - "source": [ - "### Get current budget\n", - "\n", - "It is the first time that Dr. Antartica connects to the server and she wants to know how much buget the queen assigned her.\n", - "Therefore, she calls the fonction `get_initial_budget`." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "61a467f3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'initial_epsilon': 10, 'initial_delta': 0.005}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_initial_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "bc8f7a74", - "metadata": {}, - "source": [ - "She sees that she has 10.0 epsilon and 0.005 epsilon at her disposal.\n", - "\n", - "Then she checks her total spent budget `get_total_spent_budget`. As she only did queries on metadata on dummy dataframes, this should still be 0." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "afd22f84", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0, 'total_spent_delta': 0}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "05daf5a4", - "metadata": {}, - "source": [ - "It will also be useful to know what the remaining budget is. Therefore, she calls the function `get_remaining_budget`. It just substarcts the total spent budget from the initial budget." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "6260cf54", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "20298e00", - "metadata": {}, - "source": [ - "As expected, for now the remaining budget is equal to the inital budget." - ] - }, - { - "cell_type": "markdown", - "id": "b746374c", - "metadata": {}, - "source": [ - "### Estimate cost of a query\n", - "Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The `estimate cost` function returns the estimated real cost of any query.\n", - "\n", - "Again, of course, this will not impact the user's budget.\n", - "\n", - "Dr. Antartica checks the budget that computing the average bill length will really cost her if she asks the query with an `epsilon` and a `delta`." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "fd5ed08a", - "metadata": {}, - "outputs": [], - "source": [ - "EPSILON = 0.5\n", - "DELTA = 1e-4" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "133020c6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 1.5, 'delta_cost': 0.00014999500000001387}" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "3c6a3a8c", - "metadata": {}, - "source": [ - "This query would actually cost her 1.5 epsilon and delta 1.4999e-4. She decides that it is good enough." - ] - }, - { - "cell_type": "markdown", - "id": "e5379edf", - "metadata": {}, - "source": [ - "### Query on real private dataset with smartnoise-sql\n", - "Now that all the safeguard functions were tested, Dr. Antartica is ready to query on the real dataset and get a differentially private response of the number of penguins and average bill length. By default, the flag `dummy` is False so setting it is optional. She uses the values of `epsilon` and `delta` that she selected just before.\n", - "\n", - "Careful: This command DOES spend the budget of the user and the remaining budget is updated for every query." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "19e60263", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "69767fac", - "metadata": {}, - "outputs": [], - "source": [ - "response = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA,\n", - " dummy = False # Optionnal\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "6dbbdf93", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins in real data: 344.\n", - "Average bill length of penguins in real data: 43.84mm.\n" - ] - } - ], - "source": [ - "nb_penguins = response['query_response']['nb_penguins'].iloc[0]\n", - "print(f\"Number of penguins in real data: {nb_penguins}.\")\n", - "\n", - "avg_bill_length = np.round(response['query_response']['avg_bill_length_mm'].iloc[0], 2)\n", - "print(f\"Average bill length of penguins in real data: {avg_bill_length}mm.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b2767e65", - "metadata": {}, - "source": [ - "After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "4ced8a56-e2c3-4bd1-b94b-95cbbcd58a15", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'requested_by': 'Dr. Antartica',\n", - " 'query_response': nb_penguins avg_bill_length_mm\n", - " 0 344 43.836323,\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "response" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "39701fe5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 8.5, 'remaining_delta': 0.004850004999999986}" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "e37c587f", - "metadata": {}, - "source": [ - "As can be seen in `get_total_spent_budget()`, it is the budget estimated with `estimate_smartnoise_cost()` that was spent." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "487f835f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 1.5, 'total_spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "eef4afcd", - "metadata": {}, - "source": [ - "Dr. Antartica has now a differentially private estimation of the number of penguins in the dataset and is confident to use the library for the rest of her analyses." - ] - }, - { - "cell_type": "markdown", - "id": "04929993", - "metadata": {}, - "source": [ - "## Step 4: Penguin statistics with opendp" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "b9685226", - "metadata": {}, - "outputs": [], - "source": [ - "import opendp.prelude as dp\n", - "import opendp.transformations as trans\n", - "import opendp.measurements as meas\n", - "\n", - "dp.enable_features(\"contrib\")" - ] - }, - { - "cell_type": "markdown", - "id": "bbbca191", - "metadata": {}, - "source": [ - "### Confidence intervals for bill length over the whole population" - ] - }, - { - "cell_type": "markdown", - "id": "9d41bd58", - "metadata": {}, - "source": [ - "She is first interested to have a better idea of the distribution of bill length of all species. She already has the count and average from the previous step, so she only needs the variance values.\n", - "\n", - "She first checks the metadata again to use the relevant values in the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "4331d86f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "penguin_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "f90e0425", - "metadata": {}, - "source": [ - "She can define the columns names and the bounds of the relevant column." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "ff8cb7b6", - "metadata": {}, - "outputs": [], - "source": [ - "columns = [\"species\", \"island\", \"bill_length_mm\", \"bill_depth_mm\", \"flipper_length_mm\", \"body_mass_g\", \"sex\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "70b2bdb1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(30.0, 65.0)" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bill_length_min = penguin_metadata['columns']['bill_length_mm']['lower']\n", - "bill_length_max = penguin_metadata['columns']['bill_length_mm']['upper']\n", - "bill_length_min, bill_length_max" - ] - }, - { - "cell_type": "markdown", - "id": "e93ae087", - "metadata": {}, - "source": [ - "She can now define the pipeline of the transformation to have the variance that she wants on the data:" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "75e4933b", - "metadata": {}, - "outputs": [], - "source": [ - "bill_length_transformation_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"bill_length_mm\", TOA=str) >>\n", - " trans.then_cast_default(TOA=float) >>\n", - " trans.then_clamp(bounds=(bill_length_min, bill_length_max)) >>\n", - " trans.then_resize(size=nb_penguins.tolist(), constant=avg_bill_length) >>\n", - " trans.then_variance()\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "411d464c", - "metadata": {}, - "source": [ - "However, when she tries to execute it on the server, she has an error (see below). " - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "8041a647", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Server error status 400: {\"InvalidQueryException\":\"The pipeline provided is not a measurement. It cannot be processed in this server.\"}\n" - ] - } - ], - "source": [ - "# Expect to fail !!!\n", - "client.opendp_query(\n", - " opendp_pipeline = bill_length_transformation_pipeline,\n", - " dummy=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d06c59dc", - "metadata": {}, - "source": [ - "This is because the server will only allow measurement pipeline with differentially private results. She adds Laplacian noise to the pipeline and should be able to instantiate the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "b8162859", - "metadata": {}, - "outputs": [], - "source": [ - "var_bill_length_measurement_pipeline = (\n", - " bill_length_transformation_pipeline >>\n", - " meas.then_laplace(scale=5.0)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "fc7e0ecd", - "metadata": {}, - "source": [ - "Now that there is a measurement, she is able to apply the pipeline on the dummy dataset of the server." - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "df61bce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for variance: 32.97\n" - ] - } - ], - "source": [ - "dummy_var_res = client.opendp_query(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for variance: {np.round(dummy_var_res['query_response'], 2)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "ded11ac4", - "metadata": {}, - "source": [ - "With opendp, the function `estimate_opendp_cost` is particularly useful to estimate the used `epsilon` and `delta` based on the `scale` value." - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "7ae7f735", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 0.7122093023265229, 'delta_cost': 0}" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1c791d36", - "metadata": {}, - "source": [ - "She can now execute the query on the real dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "085555a5", - "metadata": {}, - "outputs": [], - "source": [ - "var_res = client.opendp_query(\n", - " opendp_pipeline = var_bill_length_measurement_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "674332e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins: 344 (from previous smartnoise-sql query).\n", - "Average bill length: 43.84 (from previous smartnoise-sql query).\n", - "Variance of bill length: 28.052 (from opendp query).\n" - ] - } - ], - "source": [ - "print(f\"Number of penguins: {nb_penguins} (from previous smartnoise-sql query).\")\n", - "\n", - "print(f\"Average bill length: {np.round(avg_bill_length, 2)} (from previous smartnoise-sql query).\")\n", - "\n", - "var_bill_length = var_res['query_response']\n", - "print(f\"Variance of bill length: {np.round(var_bill_length, 3)} (from opendp query).\")" - ] - }, - { - "cell_type": "markdown", - "id": "367081be-1159-45d8-9129-88fba20fb697", - "metadata": {}, - "source": [ - "She can now do all the postprocessing that she wants with the returned data without adding any privacy risk. " - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "f72b19d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Standard error of bill length: 0.29.\n" - ] - } - ], - "source": [ - "# Get standard error\n", - "standard_error = np.sqrt(var_bill_length/nb_penguins)\n", - "print(f\"Standard error of bill length: {np.round(standard_error, 2)}.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "62630a03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The 95% confidence interval of the bill length of all penguins is [43.28, 44.4].\n" - ] - } - ], - "source": [ - " # Compute the 95% confidence interval\n", - "ZSCORE = 1.96\n", - "lower_bound = np.round(avg_bill_length - ZSCORE*standard_error, 2)\n", - "upper_bound = np.round(avg_bill_length + ZSCORE*standard_error, 2)\n", - "print(f\"The 95% confidence interval of the bill length of all penguins is [{lower_bound}, {upper_bound}].\")" - ] - }, - { - "cell_type": "markdown", - "id": "0d30d98e-26f4-44ec-a0f0-7a039f344860", - "metadata": {}, - "source": [ - "### Count per species" - ] - }, - { - "cell_type": "markdown", - "id": "b6a3cc00-8734-4479-81a3-781c91b8eb06", - "metadata": {}, - "source": [ - "She can also creates an histogram of the number of penguin per species.\n", - "\n", - "She first extract the categories from the metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "558916a6-78a9-4589-abe8-41472f0c66d7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Adelie', 'Chinstrap', 'Gentoo']" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "categories = penguin_metadata['columns']['species']['categories']\n", - "categories" - ] - }, - { - "cell_type": "markdown", - "id": "5f05aeb0-42d1-4444-a458-d2989494f544", - "metadata": {}, - "source": [ - "Then, writes the pipeline:" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "505a2793-6750-4f2a-9a9d-09f3a4ae4099", - "metadata": {}, - "outputs": [], - "source": [ - "species_count_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"species\", TOA=str) >>\n", - " trans.then_count_by_categories(categories=categories) >>\n", - " meas.then_laplace(scale=0.5)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "0f4109a9-c2f2-4365-bfa1-b5b2512919f9", - "metadata": {}, - "source": [ - "Verify it works on the dummy:" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "712b1c74-cac6-4b3f-9421-8d8ffa9debc7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for histogram: [38, 33, 29, 0]\n" - ] - } - ], - "source": [ - "dummy_res = client.opendp_query(\n", - " opendp_pipeline = species_count_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for histogram: {dummy_res['query_response']}\")" - ] - }, - { - "cell_type": "markdown", - "id": "66de7794-9aae-41d1-ba98-6b234a05d6f8", - "metadata": {}, - "source": [ - "Checks the required cost:" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "3f942b1b-94bc-468e-8374-3034f2e6b8ce", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 2.0, 'delta_cost': 0}" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = species_count_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1adfe446-ff13-41f4-9f3c-9998e2ae0f00", - "metadata": {}, - "source": [ - "And finally apply the pipeline on the real dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "c6fe47b7-048e-404a-bedb-720e734b0c70", - "metadata": {}, - "outputs": [], - "source": [ - "species_counts_res = client.opendp_query(\n", - " opendp_pipeline = species_count_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "b8d5fd82-5ded-47ec-8135-f6870865055d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Species Adelie has 152 penguins.\n", - "Species Chinstrap has 68 penguins.\n", - "Species Gentoo has 124 penguins.\n", - "Species Unknown has 0 penguins.\n" - ] - } - ], - "source": [ - "for i, count in enumerate(species_counts_res['query_response']):\n", - " if i == len(categories):\n", - " print(f\"Species Unknown has {count} penguins.\")\n", - " else:\n", - " print(f\"Species {categories[i]} has {count} penguins.\")" - ] - }, - { - "cell_type": "markdown", - "id": "94eaf59b-c108-424c-8978-b1c86e141ccb", - "metadata": {}, - "source": [ - "## Step 5: See archives of queries" - ] - }, - { - "cell_type": "markdown", - "id": "64003c53-de56-4bdc-a3c2-0c3e40031919", - "metadata": {}, - "source": [ - "She now wants to verify all the queries that she did on the real data. It is possible because an archive of all queries is kept in a secure database. With a function call she can see her queries, budget and associated responses." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "008fd230-cdfd-4e03-91ce-5a60b06c106d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'query_str': 'SELECT COUNT(*) AS nb_penguins, AVG(bill_length_mm) AS avg_bill_length_mm FROM df',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'epsilon': 0.5,\n", - " 'delta': 0.0001,\n", - " 'mechanisms': {},\n", - " 'postprocess': True},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': {'index': [0],\n", - " 'columns': ['nb_penguins', 'avg_bill_length_mm'],\n", - " 'data': [[342, 43.13189211774378]],\n", - " 'index_names': [None],\n", - " 'column_names': [None]},\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387},\n", - " 'timestamp': 1714988610.19844,\n", - " 'dp_librairy': 'smartnoise_sql'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': 10.184409589415381,\n", - " 'spent_epsilon': 0.7163742690067888,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1714988634.4750721,\n", - " 'dp_librairy': 'opendp'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': [152, 67, 123, 0],\n", - " 'spent_epsilon': 2.0,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1714988645.1652308,\n", - " 'dp_librairy': 'opendp'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'query_str': 'SELECT COUNT(*) AS nb_penguins, AVG(bill_length_mm) AS avg_bill_length_mm FROM df',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'epsilon': 0.5,\n", - " 'delta': 0.0001,\n", - " 'mechanisms': {},\n", - " 'postprocess': True},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': {'index': [0],\n", - " 'columns': ['nb_penguins', 'avg_bill_length_mm'],\n", - " 'data': [[344, 43.83632334140567]],\n", - " 'index_names': [None],\n", - " 'column_names': [None]},\n", - " 'spent_epsilon': 1.5,\n", - " 'spent_delta': 0.00014999500000001387},\n", - " 'timestamp': 1714990018.1717296,\n", - " 'dp_librairy': 'smartnoise_sql'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': 28.051717576669322,\n", - " 'spent_epsilon': 0.7122093023265229,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1714990066.3611896,\n", - " 'dp_librairy': 'opendp'},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'dataset_name': 'PENGUIN',\n", - " 'client_input': {'dataset_name': 'PENGUIN',\n", - " 'opendp_json': ,\n", - " 'fixed_delta': None},\n", - " 'response': {'requested_by': 'Dr. Antartica',\n", - " 'query_response': [152, 68, 124, 0],\n", - " 'spent_epsilon': 2.0,\n", - " 'spent_delta': 0},\n", - " 'timestamp': 1714990074.9196112,\n", - " 'dp_librairy': 'opendp'}]" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "previous_queries = client.get_previous_queries()\n", - "previous_queries" - ] - }, - { - "cell_type": "markdown", - "id": "422536e7-2fd5-441f-a28d-14ffa9014084", - "metadata": {}, - "source": [ - "# FSO Example: (Synthetic) Income dataset\n", - "\n", - "## Boxplot of income per partitions of the population" - ] - }, - { - "cell_type": "markdown", - "id": "ee11c6c2-c474-4d08-ae03-3523643ba61f", - "metadata": {}, - "source": [ - "### Disclaimer: Temporary Version of OpenDP with Polars" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "71109933-5837-4c22-bebc-4c82142ef548", - "metadata": {}, - "outputs": [], - "source": [ - "# Import library\n", - "import numpy as np\n", - "import pandas as pd\n", - "import polars as pl\n", - "import opendp.prelude as dp\n", - "\n", - "dp.enable_features(\"contrib\")" - ] - }, - { - "cell_type": "markdown", - "id": "2663f674-bc04-4b79-a240-80ef84860f19", - "metadata": {}, - "source": [ - "### Switching user and exploring new dataset\n", - "\n", - "Let us now change user and select another dataset to play with. For this example, we will use an income distribution dataset (synthetic) from the Swiss Federal Statistical Office." - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "bb0e35e1-357d-4291-a211-15a815bd68af", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'columns': {'region': {'type': 'int'},\n", - " 'eco_branch': {'type': 'int'},\n", - " 'profession': {'type': 'int'},\n", - " 'education': {'type': 'int'},\n", - " 'age': {'type': 'int'},\n", - " 'sex': {'type': 'int'},\n", - " 'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "USER_NAME = \"Dr. FSO\"\n", - "DATASET_NAME = \"FSO_INCOME_SYNTHETIC\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)\n", - "\n", - "fso_income_metadata = client.get_dataset_metadata()\n", - "fso_income_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "729e0423-e7fd-4edb-ae44-d12cf0844de0", - "metadata": {}, - "source": [ - "Let us also check how much budget we have." - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "id": "7f6da2fd-0387-4b12-8630-07b6b3d11650", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 43.0, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 52, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "48af044f-5279-4e39-ba85-dd96fd5cac64", - "metadata": {}, - "source": [ - "### Data preparation" - ] - }, - { - "cell_type": "markdown", - "id": "1d43022a-0df2-4740-8c48-ef7776a8c0fc", - "metadata": {}, - "source": [ - "#### Column types and bounds" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "id": "7d0cc01a-76ca-4bf3-840b-f2ec6a2cd9b9", - "metadata": {}, - "outputs": [], - "source": [ - "# Income bounds\n", - "income_lower_bound, income_upper_bound = 1_000.0, 100_000.0" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "id": "32d6854b-510f-411b-8f73-127663686ac3", - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "module 'opendp.prelude' has no attribute 'lazyframe_domain'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[54], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Define dtype domain with bounds\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m lf_domain \u001b[38;5;241m=\u001b[39m \u001b[43mdp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlazyframe_domain\u001b[49m([\n\u001b[1;32m 3\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mregion\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 4\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124meco_branch\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 5\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mprofession\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 6\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124meducation\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 7\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mage\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 8\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msex\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mint\u001b[39m)),\n\u001b[1;32m 9\u001b[0m dp\u001b[38;5;241m.\u001b[39mseries_domain(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mincome\u001b[39m\u001b[38;5;124m\"\u001b[39m, dp\u001b[38;5;241m.\u001b[39matom_domain(\n\u001b[1;32m 10\u001b[0m T\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mfloat\u001b[39m,\n\u001b[1;32m 11\u001b[0m bounds\u001b[38;5;241m=\u001b[39m(income_lower_bound, income_upper_bound)\n\u001b[1;32m 12\u001b[0m ))\n\u001b[1;32m 13\u001b[0m ])\n", - "\u001b[0;31mAttributeError\u001b[0m: module 'opendp.prelude' has no attribute 'lazyframe_domain'" - ] - } - ], - "source": [ - "# Define dtype domain with bounds\n", - "lf_domain = dp.lazyframe_domain([\n", - " dp.series_domain(\"region\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"eco_branch\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"profession\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"education\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"age\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"sex\", dp.atom_domain(T=int)),\n", - " dp.series_domain(\"income\", dp.atom_domain(\n", - " T=float,\n", - " bounds=(income_lower_bound, income_upper_bound)\n", - " ))\n", - "])" - ] - }, - { - "cell_type": "markdown", - "id": "5b539720-91b7-4c98-927d-58f641739f2f", - "metadata": {}, - "source": [ - "#### Counts per partition of the population" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "94c6c379-e5f5-41f7-a721-71f1b57f9915", - "metadata": {}, - "outputs": [], - "source": [ - "# Total\n", - "total_counts = pl.LazyFrame({\n", - " \"counts\": [2_032_543]\n", - "}, schema_overrides={\"counts\": pl.UInt32})\n", - "\n", - "# For sex\n", - "sex_counts = pl.LazyFrame({\n", - " \"sex\": [0, 1], \n", - " \"counts\": [634_720, 1_397_823]\n", - "}, schema_overrides={\"sex\": pl.Int32, \"counts\": pl.UInt32})\n", - "\n", - "# For region\n", - "region_counts = pl.LazyFrame({\n", - " \"region\": [1, 2, 3, 4, 5, 6, 7],\n", - " \"counts\": [352_001, 474_690, 267_304, 366_879, 284_638, 210_800, 76_231]\n", - "}, schema_overrides={\"region\": pl.Int32, \"counts\": pl.UInt32})\n", - "\n", - "# For region and sex\n", - "sex_region_counts = pl.LazyFrame({\n", - " \"sex\": [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], \n", - " \"region\": [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7], \n", - " \"counts\": [113_367, 148_265, 83_326, 113_715, 87_668, 64_357, 24_022, 238_634, 326_425, 183_978, 253_164, 196_970, 146_443, 52_209]\n", - "}, schema_overrides={\"sex\": pl.Int32, \"region\": pl.Int32, \"counts\": pl.UInt32})\n", - "\n", - "# Add counts to margin\n", - "lf_domain = lf_domain.with_counts(\n", - " total_counts\n", - ").with_counts(sex_counts).with_counts(region_counts).with_counts(sex_region_counts)" - ] - }, - { - "cell_type": "markdown", - "id": "4bff5ce4-2758-4454-ad58-497511c278be", - "metadata": {}, - "source": [ - "### Income distribution for partitions of the population:\n", - "#### Prepare the pipeline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8aada47e-cbc5-451b-9f1f-7b7311f29b63", - "metadata": {}, - "outputs": [], - "source": [ - "# Prepare a list of candidates\n", - "candidates = [x * 250.0 for x in range(8, 52)]\n", - "print(candidates)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ac3e2b5-92b6-4fdc-a8be-09a18f2f9c06", - "metadata": {}, - "outputs": [], - "source": [ - "# Partitions\n", - "PARTITIONS = ['sex', 'region']" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7099c026-e677-477d-9df0-60e406498303", - "metadata": {}, - "outputs": [], - "source": [ - "metric = dp.symmetric_distance() # Input metric\n", - "expr_domain = dp.expr_domain(lf_domain, grouping_columns=PARTITIONS) # Expr domain (Groupby)\n", - "temperature = 1000.0 # Noise parameter" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "382562c8-0a5a-4ece-a351-1381d10ff892", - "metadata": {}, - "outputs": [], - "source": [ - "def make_quantile_pipeline(quantile):\n", - " # Create expression\n", - " return (\n", - " (dp.csv_domain(lf_domain), metric)\n", - " >> dp.t.then_scan_csv()\n", - " >> dp.t.then_groupby_stable(PARTITIONS)\n", - " >> dp.m.then_private_agg(\n", - " dp.c.make_basic_composition(\n", - " [\n", - " (expr_domain, dp.l1(metric))\n", - " >> dp.t.then_col('income')\n", - " >> dp.m.then_private_quantile_expr(candidates, temperature, quantile)\n", - " ]\n", - " )\n", - " )\n", - " >> dp.t.make_collect(lf_domain, metric)\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "29f5ff59-b411-4714-b273-0a5e47a946a4", - "metadata": {}, - "outputs": [], - "source": [ - "q25 = make_quantile_pipeline(0.25)\n", - "q50 = make_quantile_pipeline(0.5)\n", - "q75 = make_quantile_pipeline(0.75)" - ] - }, - { - "cell_type": "markdown", - "id": "20f77fb2-390e-4c30-a34c-a0f235eb08d4", - "metadata": {}, - "source": [ - "#### Apply pipeline on data" - ] - }, - { - "cell_type": "markdown", - "id": "20e27602-0b4d-414c-a20a-562068d76965", - "metadata": {}, - "source": [ - "Let us first try out the pipeline with a dummy query and then estimate the cost of the different pipelines." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "577c8059-bda2-4f9f-a6bd-10cec7e8aa03", - "metadata": {}, - "outputs": [], - "source": [ - "dummy_r25 = client.opendp_query(\n", - " opendp_pipeline = q25, \n", - " dummy=True,\n", - " input_data_type=\"path\"\n", - ")\n", - "dummy_r25" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d56609e0-f28c-4d71-b7ca-80509acaa2af", - "metadata": {}, - "outputs": [], - "source": [ - "cost_q25 = client.estimate_opendp_cost(q25, input_data_type=\"path\")\n", - "cost_q50 = client.estimate_opendp_cost(q50, input_data_type=\"path\")\n", - "cost_q75 = client.estimate_opendp_cost(q75, input_data_type=\"path\")\n", - "\n", - "print(f\"The estimated costs are respectively {cost_q25}, {cost_q50} and {cost_q75} for q25, q50 and q75\")" - ] - }, - { - "cell_type": "markdown", - "id": "678d7741-e154-4c53-8834-5b76fa1298f5", - "metadata": {}, - "source": [ - "Since our budget is 45, we know that we can execute these three pipelines remotely." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fbf3b57e-3e66-4f98-b18c-39b183a80aa0", - "metadata": {}, - "outputs": [], - "source": [ - "r25 = client.opendp_query(q25, input_data_type=\"path\")\n", - "r50 = client.opendp_query(q50, input_data_type=\"path\")\n", - "r75 = client.opendp_query(q75, input_data_type=\"path\")" - ] - }, - { - "cell_type": "markdown", - "id": "34e2780e-2fc2-4b6f-86f0-d66522346149", - "metadata": {}, - "source": [ - "Let us put together the results and show them in a table. Notice that the output is a polars dataframe, we thus need to transform it to a pandas DataFrame if we want to work with pandas." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd36778a-ed94-4540-b803-4c8911a6da67", - "metadata": {}, - "outputs": [], - "source": [ - "r25 = r25[\"query_response\"].to_pandas()\n", - "r50 = r50[\"query_response\"].to_pandas()\n", - "r75 = r75[\"query_response\"].to_pandas()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "811e715f-888a-4946-937f-7f75d011843b", - "metadata": {}, - "outputs": [], - "source": [ - "results = pd.merge(r25, r50, on=PARTITIONS, suffixes=('_25', '_50'))\n", - "results = pd.merge(results, r75, on=PARTITIONS)\n", - "results.sort_values(by = ['region', 'sex']).head()" - ] - }, - { - "cell_type": "markdown", - "id": "16aa6910-25da-4c49-ab44-92cc4418f9d6", - "metadata": {}, - "source": [ - "#### Visualise results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "77f053b5-66dc-4252-b5ec-4a2ec3df3076", - "metadata": {}, - "outputs": [], - "source": [ - "import seaborn as sns\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "58de1e66-75f4-4be0-b4a8-19352250d897", - "metadata": {}, - "outputs": [], - "source": [ - "def quantile_data(q1, q2, q3):\n", - " return np.concatenate((np.random.uniform(q1[0], q2[0], size=50), np.random.uniform(q2[0], q3[0], size=50)))\n", - "\n", - "results['data'] = results.apply(\n", - " lambda row: quantile_data(row[\"income_25\"], row[\"income_50\"], row[\"income\"]),\n", - " axis=1,\n", - ")\n", - "results['sex'] = results['sex'].replace({0: 'woman', 1: 'man'})\n", - "results['region'] = results['region'].replace({1: 'Lemanique', 2: 'Mittleland', 3: 'North-West', 4: 'Zürich', 5: 'Oriental', 6: 'Central', 7: 'Ticino'})\n", - "results = results.explode('data', ignore_index=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "65b63c19-d710-4368-9dbd-88a737fd793f", - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure(figsize=(10, 6))\n", - "sns.boxplot(x=\"region\", y=\"data\", hue=\"sex\", data=results, palette=\"Set1\", width=0.5);\n", - "plt.xticks(fontsize=12)\n", - "plt.yticks(fontsize=12)\n", - "plt.xlabel('Regions', fontsize=15)\n", - "plt.ylabel('Income per month (in CHF)', fontsize=15)\n", - "plt.title('Income per partition of the population', fontsize=16)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "acd4e078-f92d-4edb-835a-a891a15e08a5", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/demo_kubernetes_admin_notebook.html b/html/de/notebooks/demo_kubernetes_admin_notebook.html deleted file mode 100644 index 07657fc8..00000000 --- a/html/de/notebooks/demo_kubernetes_admin_notebook.html +++ /dev/null @@ -1,450 +0,0 @@ - - - - - - - Secure Data Disclosure on Kubernetes: Server Administration — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Secure Data Disclosure on Kubernetes: Server Administration

-
-

This notebook showcases how a data owner could add and make their data available to certain user. We will do this in a step by step fashion.

-
-
[2]:
-
-
-
from IPython.display import Image
-Image(filename="images/image_demo_admin_side.png", width=800)
-
-
-
-
-
[2]:
-
-
-
-../_images/notebooks_demo_kubernetes_admin_notebook_2_0.png -
-
-
-
-
-

Start of DEMO

-
-

Since the service has been deployed in the demo 1, the URL should be accessible.

-
-
[1]:
-
-
-
URL = 'https://lomas-server.lab.sspcloud.fr/'
-
-
-
-
-

Administering the service by accessing the mongoDB

-
-
[2]:
-
-
-
import os
-os.chdir('../lomas_server/')
-
-
-
-

Let’s add a formatting function to have more readable outputs.

-
-
[3]:
-
-
-
from ast import literal_eval
-import subprocess
-
-def run(command, to_dict=False):
-    command = f"python mongodb_admin.py {command}"
-    completed_process = subprocess.run(command, shell=True, text=True, capture_output=True)
-    output = completed_process.stdout
-    if to_dict:
-        return literal_eval(output)
-    else:
-        output = output.rstrip('\n').replace(r'\n', '\n')
-        return print(output)
-
-
-
-
-
-

Preparing the database

-
-
-
-

Some existing options

-
-
[4]:
-
-
-
run("--help") # !python mongodb_admin.py --help
-
-
-
-
-
-
-
-
-usage: MongoDB administration script for the user database [-h]
-                                                           {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}
-                                                           ...
-
-options:
-  -h, --help            show this help message and exit
-
-subcommands:
-  {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}
-                        user database administration operations
-    add_user            add user to users collection
-    add_user_with_budget
-                        add user with budget to users collection
-    del_user            delete user from users collection
-    add_dataset_to_user
-                        add dataset with initialized budget values for a user
-    del_dataset_to_user
-                        delete dataset for user in users collection
-    set_budget_field    set budget field to given value for given user and
-                        dataset
-    set_may_query       set may query field to given value for given user
-    show_user           show all metadata of user
-    create_users_collection
-                        create users collection from yaml file
-    add_dataset         set in which database the dataset is stored
-    add_datasets        create dataset to database type collection
-    drop_collection     delete collection from database
-    show_collection     print the users collection
-
-
-
-
-

Cleaning the database

-
-
[5]:
-
-
-
run("drop_collection --collection datasets")
-run("drop_collection --collection metadata")
-run("drop_collection --collection users")
-
-
-
-
-
-
-
-
-Deleted collection datasets.
-Deleted collection metadata.
-Deleted collection users.
-
-
-
-

Datasets (add and drop)

-
-
-
-

For each dataset, 2 informations are required:

-
    -
  • the type of database in which the dataset is stored

  • -
-
-
    -
  • a path to the metadata of the dataset (stored as a yaml file).

  • -
-
-
-
-

Metadata are expected to be in the same format as SmartnoiseSQL dictionary format, where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details).

-
-

Add one dataset

-
-
-
-

We can add one dataset with its name, database type and path to medata file:

-
-
[6]:
-
-
-
run("add_dataset -d PENGUIN -db REMOTE_HTTP_DB -mp ../data/collections/metadata/penguin_metadata.yaml")
-
-
-
-
-
-
-
-
-Added dataset PENGUIN with database REMOTE_HTTP_DB and metadata from ../data/collections/metadata/penguin_metadata.yaml.
-
-
-
-
[7]:
-
-
-
run("add_datasets --path ../data/collections/dataset_collection.yaml -c")
-
-
-
-
-
-
-
-
-Cleaning done.
-Added datasets collection from yaml at ../data/collections/dataset_collection.yaml.
-Added metadata of IRIS dataset.
-Added metadata of PENGUIN dataset.
-Added metadata of TITANIC dataset.
-Added metadata of FSO_INCOME_SYNTHETIC dataset.
-
-
-
-

Users

-
-
-
-

Adding users

-
-
[8]:
-
-
-
run("add_user_with_budget --user 'Dr. Antartica' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001")
-
-
-
-
-
-
-
-
-Added access to user Dr. Antartica with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.
-
-
-
-
[9]:
-
-
-
run("set_budget_field --user 'Dr. Antartica' --dataset 'PENGUIN' --field initial_epsilon --value 20.0")
-
-
-
-
-
-
-
-
-Set budget of Dr. Antartica for dataset PENGUIN of initial_epsilon to 20.0.
-
-
-
-
-

We add the data based on a yaml file:

-
-
[10]:
-
-
-
run("create_users_collection --path ../data/collections/user_collection.yaml -c")
-
-
-
-
-
-
-
-
-Cleaning done.
-
-Added user data from yaml at ../data/collections/user_collection.yaml.
-
-
-
-
[11]:
-
-
-
run("show_collection --collection queries_archives")
-
-
-
-
-
-
-
-
-[]
-
-
-
-

Stopping the service: Let’s not do it right now!

-

To tear down the service, we simply execute the command helm uninstall lomas-service

-
-
[11]:
-
-
-
!helm uninstall lomas-service
-
-
-
-
-
-
-
-
-release "sdd-service" uninstalled
-
-
-
-
-
- - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/demo_kubernetes_admin_notebook.ipynb b/html/de/notebooks/demo_kubernetes_admin_notebook.ipynb deleted file mode 100644 index e0646059..00000000 --- a/html/de/notebooks/demo_kubernetes_admin_notebook.ipynb +++ /dev/null @@ -1,469 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Secure Data Disclosure on Kubernetes: Server Administration" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "#### This notebook showcases how a data owner could add and make their data available to certain user. We will do this in a step by step fashion." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "19283e29", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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5tS9qF/LUn2msHAUAAAgFwSEAACg5KjbTIWCb7/9Nq0GI1IXVNpe7777rtm+fbtdCgBwqaruazp1Lp0WVECcBbVm3LZtm50KFvQ8jTeYqa1bt9qpaFQG86NuRtWNqsuAAQPsVHNr1661UwAAAIVBQAgAAErKypUrvUoWdSu6b98+uxRR6G73JUuWmNmzZ3vdXdHtKAC4de7S1VT27G3ngPgppV4ZggJClWdylU0rws2bN9upaNSKMShU9BsLMah14XvvvWenAAAACoOAEAAAlATd+f3OO+944VamlTJItWXLFm87KmhlWwKAmwLCTp3drXqAQti7d6+dKjy/bisTNI5xObj66qsjPWpra+1fNJfNeIt+XZYG0ZiFflxBoJb5rZvGXlToCAAAUEgEhAAAoEVpDL2lS5eat956yyxbtsy8//779l+Qq+TWmPv377dLAQDSuUs3WhGiqPbs2WOn3DQeXb6oW0tkTzdbZUo3Z/mFwK5uRocPH26nmosy9iIAAECuCAgBAECL0d3RCrDmzJnDuHkFwniOAOCvqppWhCieXbt22Sm33r3zF1h369bNTrkFtXaDMevXr7dTmQkKFtO7GfXrXlQho8JGAACAQiMgBAAARbd7924zf/58r9Xg6tWr7VIU0tq1a72QcN68eaEVlAAQFwoHq6r9xwAD8mnDhg12yq1nz552KnedO3e2U80pgIpT95VhYWk+6aY3P/369bNTwd2LZtN6EQAAIBttDjWy0yUlm/7e46Ji7c2N/y3J3QYAQKCGfj8wa9asMStWrAitJHNp3769mTp1qp3DM88844WtmerVq5cZNGiQGTBggDe/aj3lLgDxtWf3LrNs0btm9y7G+0LhXX755aZdu3Z2LtWBAwfMgw8+aOfcFCydcsopXjnqjTfecAZ96qr0pJNOsnPNbd261Tz++ON2LpXG8/Nz33332algmb5GPt4zU6NGjTITJkywc83l8r4XXXSRqaiosHOpVHZT68QPfehDZsiQIXZpqldeecUsX77czgHRjJ98mp0CAOSito/7Gp5vfmWFYqMFIQAAKIqtu7uYd99912vFlk04iPxJdO36zjvveJWEABBnnTp3MVXVfe0cUFhB110FhxMnTrRzbuPGjTMdO3Y0tbW15uKLLzZnnHFGs7HtFH4Fybb7zChjJIa9t4vfuH2S/tnKgXpt8JMY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KH2t+LnooouKx+I5A/Wa0CyeffaZ9PiGtWnnjqfzGgBGKnMQDnPmDAJguDIHYfPwfQKg/8xBOHj6MwdhNREwzZo1Ky/1FMORRsVhmf7MQVimt/e4evXqdOONN+al/qk2J11/5partt1Q63mKYPDUU0+tea6effv2FccnhmytV5zHGP60t3kgu4vXXL9+fc3npNqxqTYPYGd93UY9v9fbOaymP32H5jBrzoK0aNkRadSoUXkNwMhnDkIAAACAFtNbKLNw4cLcYrCcdNJJ6ayzzqrrJlqEe8uWLSvCrVrnjoznXXjhhUXIW284GOJ34nfreU1odjEXofkIAUY2ASEAAADQ8qI6MCrBKpk2bVpuUYsxY8bkVk+9zbEYIhxcvnx5nwK7EKHiOeecU1NgF88biHkR4zVj2FEYKWI+wm1Pbc1LAIw0AkIAAACAdjt37sytniZOnJhbjTd37tzcKrdjx47cak5Lly6tGrhVO84h5vWLcLC/Yh8i/KsmhhUdiHCwQ4SEp512Wl6C4W33rh1p88a1ad++vXkNACOJgBAAAACg3ZNPPplbPY0fPz63Gisq3mIevGo2bdqUW80n9v+EE07IS+U2btyYWz3F7x955JF5qaeoPrz//vvTd7/73WIew+uvv76Yc7CSCP8iBKyk0rHumMsw5uWL1+n4ideL169WBVlpPkoYjp7YvDE9vsFQowAjkYAQAAAAoAaNnl8uth8Vb9WG1YzgKoZDbSZRMbhixYqicu68886rWpEX+/+b3/wmL/V04oknVnz/UTn5gx/8IN12221p+/btxboIG2+88cYitKskQsCycxf7XOm17rzzzmK73eemjNeL14/9qBQSxjZjiNTh7uqrr34+GI2gtJJt27Z1CVHjJ36XkePxjWvSk088npcAGCkEhAAAAAA1aERlWEe4FnPXnX/++b0Od/n444N3kz6Gy3zTm97U689LX/rSIthbtmxZr5WWUZXXEe51FyFeterJm2++ueLvRmhXqZIwArujjjoqLx0wc+bM3OqpWlViiP144IEHisAzArL169c/X3H4s5/9rNgfGCn27tmTNm1Yk3bvqj48MADDi4AQAAAAoEF6C9k6wrUIxqpVDoYIo2655Za8NPxs3bq1CPkqiRCv0jGIYLTa0KShWpXb/Pnzc+uAasf7zDPP7LVi9K677krf/va3i2q5H/3oR89XHDZbhScMhG1PPpEe37AmLwEwEggIAQAAAIaBhx9+uGIFXbOLgO8nP/lJXio3Y8aM3OopKvR6E8cmqvnKRFDbXQSWlUybNi399m//dnr1q19dDBc6Z86c/Ai0rsc3rk1bNvV+LQIwPAgIAQAAAJpcBGzDcdjKqHqM+QGvvfbaXsPNasOrPvnkk7lVXbXnxVCundUSOkZQuHz58nT22Weniy66KJ177rkCQ1rWs88+mzaurz78LgDDh4AQAAAAoImtXLmyCNiGiwgFozovgsEYgrPWYLNaQDgQw3Z2ryKMIUsrVRyWifkVZ82a1SUwjLkjYx5JaAWjR49Js+cN/FysAAwNASEAAABADXbt2pVbgyPCq+uvv77qvH3NZvfu3enOO+9M3/ve9wa04rFs/sayn0WLKocXZQFkzCMYgWZfRGAYc0fGPJIXXnhhUVkII9nseQvTzNnz8hIAw52AEAAYFnbu3JnuvvvuvES444470tNPP52XAIBGG4gqtkoiDIyfGEo0KgYjGLz66quLKrehEvvzzW9+8/mf2Kdf/epXVefui9DsxBNPLCrrhoM4pzG3Y19Dwg4RPkZl4QUXXJAmT56c18LIMXXazDRrzoK8BMBIICAEAJra/v3700MPPZR+/OMfpw0bNuS1hM2bN6cbb7wx3XfffcVxAgD6Z+rUqbnVU18DpO4hW6WfCAPjJ4YSjYrBoQwGK4l9ioq7qA6M4UOricq6V7/61cMiLItKx1tuuaU4V/0Vw5iec845QkJGlHHjJ6RZ8xYW/wVg5BAQAgBNa/369enWW29Nv/71r9MzzzzT77/sHoniuMQNuriptXbt2rwWAOiLcePG5VZPMZoBB0So1ltIOG3atHTmmWfmpaF38MEH51ZPUUkYAe3PfvazooqzP987o5rw1FNPzUsw/MW8g1OmzshLAIwUAkIAoOnEsJkRCsb8NaoGa7Np06bieN1zzz3pqaeeymsBgFrNmTOnGB6zkieffDK36BAhYYRp1URIOBDDjZZVXdb7E5WPvYmgMKo4v/3tbxdhYfzB2o4dO/KjtZs1a1ZuwfA2c/b8NHvuwrwEwEgiIAQAmspjjz1WBF0xrOiePXvyWmoRf+kec+jE0F9xc8uwowBQu8MPPzy3yjVy/sHhLEK03gK0GG50xYoVeamy3bt351ZPQzFkZ5zzH/3oR+mqq65K3/3ud4v5FyMwrGUo0oMOOqim99zdzJkzc6u6GMoUGu3gtqlp9ryFadSoUXkNACOJgBAAaArxV/l33313EW5t2bIlr6UvnnjiieI4RtDqWAJAbebPn59bPUVwJSAst3379nTHHXf0OiTncccd12vIV20Y18MOOyy3hka8z/h+FYFhDEUageHq1avzowMngkVoBmPa++LMOQvSxEmVh+YFYHgTEAIAQyrm0HvkkUfSL3/5y7Ry5cr07LPP5kfor87VmHv37s1rAYDuYgjMasOLrlu3LrcoE+Fpb2FZBF8vfelL81K5rVu35lZPUYXYmwgg3/SmN6ULLrggvfrVr05nnHFGOumkk4pKvrJwMoaVjcdOO+204rnxexdeeGG66KKL8jMqi8Dwxhtv7NPwo/117LHH5hY0TgwrOmPW3LwEwEgkIAQAhkzMWRMBVgzXZN68xjCfIwBUF+FgtfApKuPi31Gqu/nmm3sdejPm5as27Ga14xy/u3Tp0rxU7sQTTyz+G8NvxtyHixYtSsuXLy/Wn3/++cVjHWJbZ599dvHYsmXLiufG702aNKkIi2sdHjT+2K1eES5WMmPGjNyqrLfjAP01bcbsNGuOeQcBRjoBIQAw6Pbu2Z3uu+++ompwzZo1eS2NFJUPcdPt3nvvHZK/dAeAZtFRNRbBYFSK9VaZFvP7Vgt0OOCWW27pdajRo48+uuJQo3Gc4w/IKjnhhBMq/m6EZtXOZcwd2FlUPVb7ThRDovYWxMXjleYCjOMQ33fL7Nq1K7d6ioAyqh4rOffcc5tu/kHzIY4sEyZOKqoHx44bl9cAMFKN+Ui73G4qe/bsyS2qeWq74wTA8LJ1y6a0fvXKtHbt6l5vIHU3ZsyYdPjhh+cl4oZlPccw/sI95iSMv+4fPXp0mjJlSrHe9wmA/lu/ZmVu0WhRSbZgwYK81NMxxxxT9efQQw8tfv/ggw/udb63+DfzhhtuyEuVRVDU8e9qd3F/4/77789LzSfC0krDq9a77xHwTZ06tajeqySOeRz7SnM6xjGPc1Rm7NixxXfBeI2YFzJeLwLDCPPi3FY6n/F96Sc/+UmPIdfHjRtXBMZl4rtS9JOZM2cWyzFfdoc4Zi984QvTkUceWTyvTHznimHey8TvLFmyJC/1FK85ffr0Yk7Gjvd41FFHpVNOOaVY35v4w7DNmzfnpa6qne9qvxfr4xhXEvsV57RjXxcvXtz+fX9tfpThZN6iZWn6zPLrAmCkmzK58pDzA6na0PaDadT+drndVHobloLnrN7oOAEwPOzc8XTasml92tz+88wz9QWDHeKm0HnnnZeXuP7664sbR30RYWvcuImfp/eMyWsB6Ks7buk9RGJgRMDRMZRkI0V12XXXXVdT9WDMXxdDVJaJ+xtXX311Xmo+Me9epQqwvu57tW12+NnPflYxJKx2PPsihrO/66678lJXUZEXofNAikDymmuuqdp3onq1UTcHb7/99orVi9XOTbXfC5dcckmvoXqHZu/3lJs1d0FatPSINGrUqLwGoLUsmjM4VfHNUn1viFEAoKHib5Ee37g2PfrwfWnj+tV9DgcZWFFNuHLlymLY0U0b1qRnn302PwIA1BMO0lMtQ40ee+yxudXTjTfeWHWo0XrE0KKVwsEQQeVADr8e7zvef299J0LL/hiKP6zfunVrbvUu/hiN4eXgtmnF0KLCQYDWISAEABrm6W1PpkcfuS899sj9acd2Ve/N6KmnnkqrVz5QBLjbnqr9pg8AjFQRTAkH+2fjxo095vzrLv5y/rTTTstLPV177bW9bqM3q1evTj/60Y/yUrk4z3G+ByKQ7AgHK1VGdhaVen0dejaOy1AMWxvzh9cq5lJk+Dho7Lg0e97CNGFi+RyfAIxMAkIAYMA9s29f2rjusfTYI/cVw4rS/J7YvCE9+vC9af3aVWnfXnMSAtB6IiCKIRYjmBIO9l9UAfZWmRfDiFaaAzBEuBfnpN5quXh+/F7sQy3ifMd578trhQgGo//EsKK1hIMdbrvttqKSsLdqyw4x72I8v7fQs1Ei+K1nfxk+Zs1ZkKbNmJ2XAGgVYz7SLrebSkyETe+e2u44AdBcntq6Ja1fszJtWr867du3N68dGDFU0eGHH56XePjhhwf0Bk0M//r0U1vT7l070ujRY9KEif7yG6BW8W8fgyPmi1uwYEFe6psIWmIe3wiDosrspptuSvfee2/avHlzfkZ9li5dmqZMmZKXuor7G0NR7VWrmNOx0lx4/d33OMZLlizJSz2NHj06zZgxIz3wwAN5TU9xTmIfYtSDeH7H8Ied58KL70MR8m3atCndfffd6Re/+EWfzmXHa23YsKF4rbB3794ex6fj9Tr6z80331z0n3huveK1Ytj3eL34rhvvq+O1Q7zGE088UTznhhtuKJ4fql0H69atq/j+q53var/XoWN/J0yYUOxv5211HJeO/e3YV5rb9Jlz0vxFy9r7nWFhAaZMbsz8wN01ah7ieo3aHxMDNaG+/MVWK1q90XECoDns3r0zbd64Pm15fH3au2d3Xjuwxo4dm84777y8xPXXX1/ceGuEMWMOSrPmLkgzZ89P4ydMzGsBqOSOW27ILQAYHiZOmpwWLTuimH8QgJQWzWnLrcaKodabgSFGAYB+e+LxDemxh+9LG9aualg4yOCKasINax8t5pDc0n5+AQCAkSOqcWfPXSQcBGhhAkIAoM927ng6rV75QBEibXtqa17LSBJDjj768H3psZX3p53bjVwAAAAjway5C9PMOfPzEgCtSEAIANTt2WefTZs2rEmrHrq3+G8sM3Lt3/9senzD2rTq4fbzvX51+/l+Jj8CAAAMN21Tp6fZcxfmJQBalYAQAKhLVApGxWBUDkYFIa1j547tafWqB4uKwm1PPpHXAgAAw8W4ceOLcNA84wAICAGAmsTcguvXrEqPPnRvMecgreuJzRuLkDj6w969e/JaAACg2c2auyBNnT4rLwHQykbtb5fbTWXbNnPc1GL1RscJgMbb/vRT6bFH7h/yisGxY8emQw45JC/xyCOPpL179+aloTFh4qS05NAj0+SDp+Q1AK3pjltuyC0AaE4zZs1Li5YdkcaMGZPXANDZojltudVYbW2D8zq9ERAOcwJCABpp184dacvj69PjG9emZ/bty2uhqzFjDkozZ89LM+fMTxMmTs5rAVqLgBCAZjZpclsRDvrDPoDKWi0gNMQoAFBqy6b16bFH7ksb1j4qHKSqZ57ZlzauX13MTbh507q8FgAAaAajR48u5h0UDgLQmYAQAOiiYzjRmGPu6W1P5rXQu+g7ERJG34k2AAAw9CIcnDF7Xl4CgOcICAGAQucqsBhStElHIWcY2LxxXXs/ujdtXPeY6lMAABhCU6bNTLPmLcxLAHCAgBAASE89uaUIBtesejDt2rk9r4W+i/kr1zz6UFFN+NTWLXktAAAwWMZPmFhUD44bNyGvAYADBIQA0ML27N6V1q1+pAgHt27ZlNfCwIl+9egj9xb9LPobAAAwOGbNWZCmTJuRlwCgKwEhALSoJzZvLKq71q9Zlfbu2Z3XwsDbu2dP0c+iv215fENeCwAANErMORgBIQBUIiAEgBazc8f2tHrVg0XV4LYnn8hrofGivz32yH1p9coH2vvh03ktAAAwkCZNbivCwdFjxuQ1ANCTgBAAWsb+tHnjurTq4XvTpvWr07PPPpPXw+B59tln06YNa4qA+vENa9P+/fvzIwAAQH+NHj0mzZ63ME0+eEpeAwDlBIQA0AK2b3uyCGRiiMed27fltTB0drT3w8dW3l/0y6e3bc1rAQCA/ohwcMaseXkJACoTEALACLZv3960Ye2jRdXg5k3r81poHlseX59WPXRvWr92Vdq3d09eCwAA1GvKtJlp9tyFeQkAqhMQAsAI9eTWzUV11trHHk67d+3Ma6H57Nm9K6177JGivz75xON5LQAAUKvxEyYW1YNjx43PawCgOgEhAIwwEQYKWxiOhNoAANA3s+YsSFOmzshLANA7ASEAjCAxXGMELIZrZLjqGBY3+rFhcQEAoHcRDs6etygvAUBtBIQAMALs2L4tPbby/iJUeXrb1rwWhq/ox9GfH33kvrTj6afyWgAAoLPJbVOLgHDUqFF5DQDURkAIAMPYs888kzatX51WPXxvenzD2rR///78CIwE+9PmjeuKkDD6+bPPPpPXAwAAYw46KM2euzBNnHxwXgMAtRMQAsAw9fRTW4vgZPWqB9OuHdvzWhh5drb37+jnUVG47ckn8loAAGhtEQ5OnzknLwFAfQSEADDM7N27J61fs6oIB5/YvDGvhZEv+ntUy65bvTLt2bM7rwUAgNYz+eApaf6iZXkJAOonIASAYWTrlk1FFdW61Y+k3bt25rXQOvbu2Z3Wr1lZXAdxPQAAQCtadvgL2v/XvIMA9J2AEACGgV07t6e1jz5UVA0+tXVzXguta9uTW9KjD9+b1sQQu+3XBwAAtIqYe37c+Al5CQD6ZlT7Pyj7c7upbNu2LbeoZvVGxwlgpNu8aV3avHFd2v70U3kN0NmkyW1p5pz5aebs+WnUKH9FDQxfi+a05Ra1amtzzIC+a7b7j+7zAQytwfo+3izfYVUQAkCTikAwhlGMH+EgVLZj+7b02CP3p8dW3u9aAQAAAKiBgBAAmswz+/alDeseS6seureoHgRqE5W2EahvXL86PfvMM3ktAAAAAN0JCAGgicT8gjHPYMw3uHvXjrwWqFXMRxjzEj43X+eWvBYAAACAzgSEANAEdu/amdY+9nBR/bR1y6a8FuirJzZvTI8+cm9at/qRtGf3rrwWAAAAgCAgBIAhFkFGzJ22Ye2jae/ePXkt0F979+xJ69esKqoJ4zoDAAAA4DkCQgAYQjFP2r59e9O+vXvzGmCgxfW1b+8e8xICAAAAZAJCABhCo8eMSbPnLkxLDl1R/Hf0aP80w0CJ62nW3AXPXV/zFhXXGwAAAAACQgBoCpMmt6VFy45Iiw9ZntqmTMtrgb46uG1qEQwuXra8uL4AAAAAOEBACABNZMaseWnxoSvS3AVL09hx4/NaoFYHjR1XXD9LDj0yTZ85N68FAAAAoDMBIQA0mfHjJ6YFiw9JSw5ZkabNmJ3XAr2J6yWum7h+xk+YmNcCAAAA0J2AEACa1JRpM4rhERcuOSxNmDgprwW6i+sjrpMIB6dOn5nXAgAAAFCJgBAAmthBY8emOfMXp8WHrEgz58zPa4EOM2bPK4LBuE7GHHRQXgsAAABANQJCABgGDm6bWoQgSw5dkSYfPCWvhdYV18HiQ5YX18Xk9usDAAAAgNoJCAFgGJk5e34REs6Zt0i1FC3poPZ+H/0/roNZcxakUaNG5UcAAAAAqJWAEACGmQkTJ6eFSw8vKqemTDPfGq2jbeqMtOTQI4v+H9cBAAAAAH0jIASAYWrajNlFSDh/0SFp3PgJeS2MPNG/o59H1eDU6bPyWgAAAAD6SkAIAMPY2HHj0ryFS4ugcMasuXktjBzTZ84p+nf083Hjxue1AAAAAPSHgBAARoC2qdPT4kNWpEXLjkgTJx2c18LwNWHS5LS4vT9H1WD0bwAAAAAGjoAQAEaI0aNHp9lzFxaByqw5C4plGG5GjRqVZs1dUFQNzmrvz6NHj8mPAAAAADBQ3DkEgBFm0uS2tPiQ5UXA0jZlWl4LzS/6awTci5ctT5MPnpLXAgAAADDQBIQAMEJNnzU3LTnsyDR3wZJ00NhxeS00n+if8xYsTUsOPTLNmDUvrwUAAACgUQSEADCCjRs3IS1YfGhaeuiRadqM2XktNI+p02cVVYPzFx+Sxo2fkNcCAAAA0EgCQgBoAVOmzSiGHF249PA0YeLkvBaGzoSJk4rwOsLBqdNm5rUAAAAADAYBIQC0iDEHHZTmzFtUzE84c/b8vBYG38zZ84rAuhj+9qCxeS0AAAAAg0VACAAt5uC2qUXVVvxMPnhKXguNN2lyW9HvFh/S3vfa+yEAAAAAQ0NACAAtKqoIlx52ZJo7f3EaPWZMXgsDb8yY56pXIxyMfjdq1Kj8CAAAAABDQUAIAC1s/IRJacGSw9LSQ48s5imEgTZl6owiGIz5LydOOjivBQAAAGAoCQgBgDRtxuy05JAj04LFh6bx4yfmtdB3Y8eNT/MXHVKEg9G/AAAAAGgeAkIAoDB23Lg0d8GStPiQ5Wn6rLl5LdRv+sw5ackhK9K8hUuLoBAAAACA5iIgBAC6aJs6vQh3Fi07Ik2a3JbXQu8mTj44LVp6eFE1aMhaAAAAgOYlIAQAehg9enSaPXdhEfTEf2MZqpk1d0ERLM+et6i9v4zJawEAAABoRqP2t8vtprJt27bcoprVGx0nABpv25NPpMdW3p9279qZ1wyNsWPHpkMOOSQv8cgjj6S9e/fmpaExbvyEtHjZEWnKtJl5DQB9tWiOyv16tbU5ZkDfNdv9R/f5AIbWYH0fb5bvsALCYc4XBwAGy+7dO9OWTevT5k3r0t49e/LawRUB4XnnnZeXuP7669POnUMT2h40dlyaOXte+8/8NH7CxLwWgP4QENZPQAj0h4AQgM5aLSA0XhgAUJPx4yem+YsOSUsOOTJNmzE7r6UVTZ0+qxh+dsHiQ4WDAAAAAMOQgBAAqMuUaTOKueYWLDksTZg4Oa+lFUQYGKHg0sOOTFMNKQoAAAAwbAkIAYC6jTnooDR3/uKiiiyGmGTki+FEIxieu2BJGjPmoLwWAAAAgOFIQAgA9Nnkg6cUIWEER9Fm5Jl0cFtafMjy9p8V6eAp0/JaAAAAAIYzASEA0G8z58xPSw49Ms2Zv7ioLmT4Gz16THE+l7af11lzFqRRo0blRwAAAAAY7gSEAMCAmDBxUlq45LAiUIp5Chm+pkydUcwzGOfTPJMAAAAAI4+AEAAYUFOnzyqGHF2w+JA0fvzEvJbhYNz4CWnewmXFsLHTZszOawEAAAAYaQSEAMCAGztufJq7YGlafOiKNGPWvLyWZjZ95pwi2J2/aFlx/gAAAAAYuQSEAEDDtE2ZVlSjLVp2RJo0uS2vpZlMmTIlLVp6eHGe2qZOz2sBAAAAGMkEhABAQ40aNSrNnruwCKDiv6PHjMmPMJRGjx6dli1blo477rg0e96i9mXnBQAAAKBVCAgBgEExcdLBRSVhDGOpUm1ozZw5swgGjznmmDRt2rS8FgAAAIBWISAEAAZVx1x3y5cvTxMmTMhrGQzjxo1Lhx9+eBEOLlq0KK8FAAAAoNUICAGAQTdu/IS0YsWKdPzxx6cFCxbktTTSvHnzimDwqKOOSpMnT85rAQAAAGhFAkIAYMjMnj27CK1e8IIXpLa2tryWgXTwwQcXoWAc5wgJAQAAAEBACAAMqYMOOigddthhRYC1ZMmSNGrUqPwI/bV48eLiuMawojG8KAAAAAAEASEA0BSmT59ehFnxM2PGjLyWvpg2bVo65phjHEsAAAAASgkIAYCm0lH1FlWFY8eOzWupRVRjHnLIIcXxW7ZsmWpMAAAAAEoJCAGAphPz5sW8hMcff3yaO3duXks1HfM5Hn300WnKlCl5LQAAAAD0JCAEAJrWvHnz0sknn1yEhWPGjCkq5Ogqjsvy5cuL47RgwYK8FgAAAAAqExACAE0thsmM4UZf/vKXqybsZubMmemMM85IK1asKIJCAAAAAKiFgBAAGBYmTpyYjjnmmLxEOOGEE4rhWAEAAACgHgJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCECQgAAAAAAAGghAkIAAAAAAABoIQJCAAAAAAAAaCGj9rfL7aaybdu23AIAAADoqa2tLbcA6uf+IwBDoVm+w6ogBAAAAAAAgBYiIAQAAAAAAIAWIiAEAAAAAACAFiIgBAAAAAAAgBYiIAQAAAAAAIAWIiAEAAAAAACAFiIgBAAAAAAAgBYiIAQAAAAAAIAWIiAEAAAAAACAFiIgBAAAAAAAgBYiIAQAAAAAAIAWIiAEAAAAAACAFiIgBAAAAAAAgBYiIAQAAAAAAIAWIiAEAAAAAACAFiIgBAAAAAAAgBYiIAQAAAAAAIAWMmp/u9xuKtu2bcstgIF1zLkfyK2B8Y6LX54uu/SCvNTcPv3lq9NXv/PjvHTAZz/8jnT26cfmJUIjj1WlbZf1pUrPHSh3X/uZ3KpPPe9hqPzmwdXpO9f8PN1xz8PpwZXr89qUJk+akJYfMj8dvmx+OveMY9OLT1ieHwEAhpu2trbcAqif+48ADIVm+Q6rghAAGHG+fuVP0yXv+Uz6zjU3dwkHw/Ydu9Id9zxSPPb09l15LZ1df9Nd6W3v/9u8RD3Wb9paBOhf/Pq1eQ0AAABA8xEQAgAjylU//EX61BeuzEvVHb1iSW4ROoLB93/0q0WISu06gsELL72iqK7dvnN3fgQAAACg+QgIAYAR5Yc3/jK3qps3Z3qaN3taXmptMRzr5Z/8mmCwj6Ji9a3v/9vngsEdqlIBAACA5icgBABGlJtuvTe3unrv284r5lyMn+u+8eH0sct+Jz/CJ//+yvT9H9+Rl6hHVF1Gxer6jU/kNQAAAADNb9T+drndVEwSDDTKMed+ILe6itBgpIvh76LCpbvPfvgd6ezTj81LhEYeq0rbfsfFL0+XXXpBXqpdDAlZVvXVyPM60O9hIJVd44cvm5eu/NLleYnuKvWhVvhc7K8ICKPysrtmuBYAWkFbW1tuAdTP/UcAhkKzfIdVQQgAjHhtkyfmFgAAAACgghBoOUNVQRhVJtf+9M70wCPr0oMr1+e1z1U2HXHI/HTuy47rc7VXx7bvuGfl88PcxfxqJ7xwWZft9rcqLuYp+841P2/f/3Vdqo3itQ5fOje99KSj0lte+7K8tn/Wb9qaPvMPVz8/7GEcp9NPPiq9+uUnpKMOX1Ss6yyOwc2331/s2/3tx7j7PGAnvPCQ9v2clk570Yp04StPyWsrU0FYXSMqCH9+x/3pf/3gll77cW/KrvE4///y2fflpfpd9cNfpL//2g+L/Zo8aUKxT6884/iKfSnmpLvr3ngfW3ucm/j95e3X/OHL2q/7M45NLz5heX6kNrEvN//nfaXbjuM1f/a0dNwLlrVfjytq3nYjKgjrvYYHU6XPstDxmXzskctq+jwb7hWE9fbtOK9FH7z9vi6ftR39+rQTn/uMNb8nMFhUEAL94f4jAEOhWb7DjhIQAq1msAPCCD0+/y8/LL353l2EGO952ytrvqkfN2ov/8TXet326Scfmf7iv12SvnHVT/sUenW/0V9NBBTvfesrew3hKt1Uj305esWSdOGlV/QI+UL3m+71HN8OEQD8ybsvrHqcBYTV1fseKj2/47r76Of+LX3nmpuLdiUd/bgseKh0XVfT+ZovO4YdoWKlfY9A5KovX95lfzqHLbWq9r46i2vmU1/87oBtu9L7qqbzMRuIa7gvYW6l160lBI7Psr/8H9+uOE9ld3GO/3v7Z0XZ51m9fa77tdHXULbef8MGqm+HeP53rvm/pee1s/j9i89/SZ8+zwDqJSAE+sP9RwCGgiFGAVpAhAX/7S+/WnN4Fc+79E++WFQf9SYqYN5a4QZzd3Ez/N1/9qW0fcfuvKZ2EcDF79YSDoYIL/780/+aLv/k1/Ka+j23r+U3oN984YGKnti3eo5vh6jgjN+L32fovfYPrug1HAzRjyMQH0xxDVcK0V798hd1CVDiuo2+X0+AFzquzwivKoltRyjWiG03Qq3X8GCK6z1Cy1rDwRDvob+fZ82qnr4d/SfOaTy/t3AwxHPiuXFtD3bfAwAAAGojIARokLgZ/ddfuKqmm6ndfeoLVxZVMpXEDdcPXvHNusKCCMVqCWE6i9f5VPt76Dwkaq0iUIxqk3r9y7//pOLrverlJ3S5aR371pfjG+L3ovKQoRXBSz39K8LgWgL0gbBt+87iGq7k4vNfnFvPBfZ/98/fz0v1i2MQFb5lBmLbgxms1nMND5Y4hvFHAX39vIjPs8Hqd4Ohnr4dooK8nmC1w2D3PQAAAKB2AkKABohg7c8//f+V3oyOYea+/fkPFMPBXfeNDxfLMRxbd8VQghUqLyJIqHYDvvP2/+Tdry3dfi1iKL6y14lhC7/8qXcVrxE/8RoxtGh3UUFSb5VetWrA1513am49V1FVtm8xdF7nfYtjEce4TLxWBAcMnY7K1DhvMTRlx3l779vOq9hvf3bbb3KrsaJ/VQqU4hroPI/eP1WorLr4/NOevx7jJ/pm/G6ZH/zkl7nVVaVtR7/u3tcrHbfo69X+6GAg1XoND6ZP/v2Vpcew++dFtGNdmQhpR0o1XD19+4tfv7a0gjyGav7YZW94/tjF9Vt27KI/xDYAAACA5iIgBMhiXqd6fmJep0oiwCur7osgLeZk6rj5GpU0sfw//uIdxXJn8fsxBFx3cYM65oAqE+HAFX/61i7bf8trX1Zsv96QMMKEsoqRCCC/8Fd/0GX+vniNr332faUh4T//2w25VZ94nQg4O24+R/jR+TX//fs9j0G8/hUffGuX58WxiGNc6ab/mvVbcouhEuc65kPrPF/iu95ybvrDt78qL3VV1i87+kn8lInz3/k5lZ5XJq6dzuFl/Pw/v/vq/Ohz12RZgBJBy4f/6PVdwpbomzEnYNn1WKki+I57VubWARE8Rr/u3tfjuH38j9+Q13R17U/vzK3nxO93vJ9K10fn9xw/9ejtGh4s8ccEZaFlnJ/od533Kdqxrux4RKDW+TO5431F3ygTAW7Hc+InjnezqaVvf7XkMzzCwfh3oPPcjHH9xrErC8DLtgEAAAAMLQEhQAOUVQLFDecI0srETem4md7dv//gltw64Pqb7q5YCRPhQJnY/jtef1Zeqk33MCHEzeQPvLP8JneEkW+/qOdrRJhTb5Ve3GCOoLPzUISdQ5YQN6fjxnbchI/nRzgYr19p+MLjXrAst7q68zercouhUK1PxfVSFqQNtgjYO4eXoXN/jD4X4VdUU0V/jGsx9rtz0NJZPH/5IfPzUldlVX5lweHkSeNzq6fY1whwYj/icyX+cCCulUrHuRFquYYHS6WK00rnJ0SlY3ymxDGMcxp/3BHHsNJn7HDVW9+u9O9N9KlKn7VlxzW2MZKGaAUAAICRQEAIMMBiSM2yG/rnnnF8bpU77UUrcuuA2E73cO2ue3tWE4Xehu7rXOlRixtv6XlT/YxTj6p4UzicffoxudXV7XdXHnKwzCt7OVYh9iNubEdVToSF1339zysGsDSvE164rGqfqidIa4QI2mqpeotQJa6x6I9RRfXzqz7R0EDsplt/U3W4yyu/dHmxHxHSRagV10q14zzQarmGB0Mco7KK0wgwq52fOJfxmRLHMM5pfLZ0D9KGu1r6dlm4GsFptWMRxzW23V2lf7sAAACAoSEgBBhgvywZDjDMmz01t8odPLm8Uqp7uLZ+Y3ko0FsAGOFAVMPUIkLOsqqRubOqBwyVAoh6bwzXG2ZWEuFADAl4+Se/VnFYVobW4cvKA8BmcfrJR+VW/0XYH1VU7/6zL1Wdp6+7sus25pC78NIrir5dNhTxUBuoa7i/fnXfo7nVVbP3u8FQS98uG952fg1Bc9vkibl1QNm2AAAAgKEjIAQYYNt37s6trt7/0a+WzmXY8ROPl9nweNdAsCxYqHUYxnlzaqsgenp7z3AwfPU7Py7d984/ZSqFmmVqDTHLRLD5xa9fW4Qmr/2DK9I5b/5o+vNP/2sxP1xZ4Am9Oe6opblVv6hy/PSXry4CwXPe8rF0yXs+kz71hStLK9qqqVR9HH06+nb08bj2Yl7UeL3Bqq6spD/X8ECLILVMb3/s0Apq6dtln5vxb1D3z/3uP2X/TlWaYxMAAAAYGgJCgOzuaz9T108MPVfmzl8PbJVE94CwTKVhGLur9ab4QM/Lt67KUIj9FVVZH/3cv6UXX/jBdOmffDH9/b/8oAhNKgUDNJf+BHDNKMK5CKgjJInQP0L1CAT7E47E8JZlQzZ2F6FMvF68blwPsR9DHRYOtUp/sNFbRTeNGca33vloAQAAgMYREAI0uXqq75pVoypHolowqrK+c83NKgQZUjGcbQRyEc5FQD3QYp7NmDevVh3VhbE/UVkY1bUw1Nas35JbAAAAwFATEAKMANu278yt4a/WucEiHIxqwWqi6upVLz8h/cm7X5suPv+0vBZqd/SKJblV3V/+j2/3GgzG0JvRDz922RvqHoYz5veMkPCzH35HXUFhiMrCqK4drLkKze83PNTatwEAAICRSUAIMMAq3RyPG/tlQ5X29tN9KNOy+QZrHU5z+47y4fa6O2zJ3Nzq6h0Xv7x0H2v5qdXkSeNzq7Ko1vrqv92Ql7qK4CUCweu+8eF05ZcuT1f86VuLIRpr2S50F8Fcb2IoxkrzCkZAHYFgXANxLX/4j16fLnzlKfnR+p19+rFFUBj9O/p5bL/WOUj/+gtXFddOozXTtTZ5Yvm+DPQwysNRb327UoAYn7HdP99r/Yn+CwAAADQHASHAAGv0zfFK8w3WcuP/wZXrcqu6gyfXFjgMlaiEKhtSNCqrIoSJQLCWYAcGwr9//+e51dV733ZeEVD3JxCsJPp39PPY/s+v+kT68qfeVbxeXAOVAsO4Zq6/6e681LwGcs7SSnM31vrHEkOhWebp8xkKAAAAI5uAEGCAHXfU0tzqaqAqVipVKPZ24z8CxBhqsBaVqjzu/PXK3BpaDz+2Ibe6uuhVL84tGDwPrirvj+96y7m51XgvPmF58XpRXRiBYVQWltnwePPPadrbnKXrNz2ZW72r9Fl2xz0P51Zl57zlY8X8jTG35Ke/fHVRKToYFZjV5ukb7PCwbCjcWv8dAQAAAJqbgBBggMUN6bIKnh/85Je5VS5uRL/2D67ocjO67GbwsUeWD/t27Y3Vt/+Nq36aW7Upm+csbgxXu0Ed+x031d/9Z18q2lHp9/M77s+PDpz1G+u/SX/Trb/JLRhYvQVa3dUT1of4LPj6lT8trqn4nHjxhR/s9bo692XH5VZzq1TtWC2I6+2zrruyz7IYlrnaMYzPuTivcZ5ibsmvfufH6f0f/Wq68NIr8jP6r1K1+dPbe1ZHd/heL/NcDrTTTlyRW11Ff6wk+mv00eir0WfjubEOAAAAaC4CQoAGuPj8l+TWAXGzOcK/MnHzNG5Ex03rzjejL3nPZ3rcKI/hCufNmZ6XDojf/+LXr81LXcWN8O9c83/zUm0qVeN98Ipvlt68j3XxGvE+Yz62eA9//ul/TZf+yReLoHAgzZtTPvRdpSrNuEFd6zyNUK+y6zFUCkU+8w9X51bv4jMjPgs+9YUri2sqrvMYKvTz//LD/Ixyla6FubOaa9jISkMmV/qDhriW6wlXwyvPOD63uvpUlTkZ/99/+l5udXXGqUflVv9Vqgb/Xz+4Jbe66svneH/FvzdlIe7f/fP3Kwas//LvPyn6aJyn6LPRd6MPf/Rz/5afAQAAADQDASFAA7z5wpeV3lSN8C+q6zpurMbN6bjh/aG/+ddiubt3XPzy0nmg3n7RWbnV1d//yw+KQKGjyi+2H6Hhf/vLr5bO2VdNVEKWDS8XQVu8h86hX7RjXdlrxDYGeg62Y49clltdxc3oqFjpuOkfxzn2K25QQ6Oc8MLy/hjXdedKq7hOoko4Pgdq9bvtnwFlInyJbcU2O4dc0efjGohrobv4TDr79GPyUleVqtliWyE+Uzq/l4FSKSSL/Y/Pro73Fq8fAVNfruX4/Kn1s6zjMyP+yKFM2fmoNGdrVI13fBZHWNw9MD5sydzc6irO7UB9jvdX/PtT9gcvsR+xP9Enun/elgW40ff+4E1n5yUAAACgGQgIARogbqr+4dtflZe6ihvPUVV3zLkfSOe8+aPFDe+ym76HL5tXBI1l3vLal5UOmxcifIjKw47tR2jY15vK73nbK0uDzrixHtWB8RrxE+2yCr343djGQIuQo2y/QgQL8b5jv+I4V7rR3+HBletyC/qm0nCecd3F9d3bddJZ98ePOnxR8YcCZTquw47+3tHny8LB8I7Xn1X6BwehWlAX243PlEYE7RefX3ne0Pjs6nhv8frfuebm/Ej9/vS9r63ps6zaZ8Z733ZecT66i/kfy0Q1dcdncVTQda/qrFQNHgbyc7y/4t+h+Peou47+Xcvnbfx7WKnvAQAAAENDQAjQIBHi/cm7X5uX6hM3Yz9x+Zuq3lD9i/92SelN22pe9fITcqs2ceP7f/zFOyqGcdXE73z8j99Q8eZ5f8Rx+e/vvjAv1Sb2p7SKaNWG3IK+iWrbi88/LS/VJq7dsnDo4cd69sfLLr2g7u13F9f+u95ybl7q6dU1fjYM9FxyEbjV+97ivZRdy9XE6/T1syz0dvxq+Wy989crc+uA9761vj+giH7T375Qr/i8/cJf/UHd/950iGA1/j0EAAAAmouAEKCB4qboZz/8jopVImXiRnPcjC2rVOksbtpe+aXLa7pZHDfFP3bZGypWOlUTAd8//c176rohH8+Nm/ERnDRKVN9EAFvLDf+otrzqy5en1513al5zQFT5DHToQev58B+9vubgJioC49p92ck957O78ZbfdBkytENsv97PkhDPj2v/ij99a15TLj5v+voHDf1Vz7GL5/X2XiqJz7L4HKhUfV2m1uP3gXde0KcArZ7PsfhcjX8bKg0H20gdIWE94WTHsasWrAIAAABDR0AI0GARkl339T8vbpRG+Nf9JnJHZVuEBt/+/AeKG9HVKge7i5vr8Xux7c7hQWw3boRH9UbcFO/PPIARHvzLZ9+XvvypdxU3iMvCwlgXj0WIEc9tROVgdxHARngZx677PnVU2sQ+x43tOKYvflH5Pl370ztzC/oursXob2XXefTPuBbjWo2KwHDskUuK/3YWwzZef9Pdeamrjs+SuMY6rsOyYCnWxz7EZ048v9Zrv+MPGso+Szqu76NX9NzngdD5c6zzset47Y5jF8/rj46gK7ZV7bOs3uPXsd3Yz+7bjPcTn8Vlf6AQ4rjHZ3TZ51gsd3yOxedqPf82DLR47Y7z1PE+u/e/eK9x7CL0rKfvAQAAAINv1P52ud1Utm3bllsAAAAAPbW1teUWQP3cfwRgKDTLd1gVhAAAAAAAANBCBIQAAAAAAADQQgSEAAAAAAAA0EIEhAAAAAAAANBCBIQAAAAAAADQQgSEAAAAAAAA0EIEhAAAAAAAANBCBIQAAAAAAADQQgSEAAAAAAAA0EIEhAAAAAAAANBCBIQAAAAAAADQQgSEAAAAAAAA0EIEhAAAAAAAANBCBIQAAAAAAADQQgSEAAAAAAAA0EIEhAAAAAAAANBCBIQAAAAAAADQQgSEAAAAAAAA0EIEhAAAAAAAANBCBIQAAAAAAADQQgSEAAAAAAAA0EIEhAAAAAAAANBCBIQAAAAAAADQQgSEAAAAAAAA0EJG7W+X2wAAAAAAAMAIp4IQAAAAAAAAWoiAEAAAAAAAAFqIgBAAAAAAAABaiIAQAAAAAAAAWoiAEAAAAAAAAFqIgBAAAAAAAABaiIAQAAAAAAAAWoiAEAAAAAAAAFqIgBAAAAAAAABaiIAQAAAAAAAAWoiAEAAAAAAAAFpGSv8/lF2KoMUTl3oAAAAASUVORK5CYII=", - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_admin_side.png\", width=800)" - ] - }, - { - "cell_type": "markdown", - "id": "0034a717", - "metadata": {}, - "source": [ - "# Start of DEMO" - ] - }, - { - "cell_type": "markdown", - "id": "2cd74838", - "metadata": {}, - "source": [ - "#### Since the service has been deployed in the demo 1, the URL should be accessible." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "e7578d3a", - "metadata": {}, - "outputs": [], - "source": [ - "URL = 'https://lomas-server.lab.sspcloud.fr/'" - ] - }, - { - "cell_type": "markdown", - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "source": [ - "## Administering the service by accessing the mongoDB" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e249d717", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../lomas_server/')" - ] - }, - { - "cell_type": "markdown", - "id": "1a10543e", - "metadata": {}, - "source": [ - "Let's add a formatting function to have more readable outputs." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a0145cfe", - "metadata": {}, - "outputs": [], - "source": [ - "from ast import literal_eval\n", - "import subprocess\n", - "\n", - "def run(command, to_dict=False):\n", - " command = f\"python mongodb_admin.py {command}\"\n", - " completed_process = subprocess.run(command, shell=True, text=True, capture_output=True)\n", - " output = completed_process.stdout\n", - " if to_dict:\n", - " return literal_eval(output)\n", - " else:\n", - " output = output.rstrip('\\n').replace(r'\\n', '\\n')\n", - " return print(output)" - ] - }, - { - "cell_type": "markdown", - "id": "d368d6a6-f1fe-4f65-9ce1-38c0b39584d1", - "metadata": {}, - "source": [ - "## **Preparing the database**" - ] - }, - { - "cell_type": "markdown", - "id": "b37c19b8-303d-4fe8-b515-33ed1099c581", - "metadata": {}, - "source": [ - "#### Some existing options" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "8a749f4b-93cb-460c-bb40-4880df6e51d9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "usage: MongoDB administration script for the user database [-h]\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}\n", - " ...\n", - "\n", - "options:\n", - " -h, --help show this help message and exit\n", - "\n", - "subcommands:\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}\n", - " user database administration operations\n", - " add_user add user to users collection\n", - " add_user_with_budget\n", - " add user with budget to users collection\n", - " del_user delete user from users collection\n", - " add_dataset_to_user\n", - " add dataset with initialized budget values for a user\n", - " del_dataset_to_user\n", - " delete dataset for user in users collection\n", - " set_budget_field set budget field to given value for given user and\n", - " dataset\n", - " set_may_query set may query field to given value for given user\n", - " show_user show all metadata of user\n", - " create_users_collection\n", - " create users collection from yaml file\n", - " add_dataset set in which database the dataset is stored\n", - " add_datasets create dataset to database type collection\n", - " drop_collection delete collection from database\n", - " show_collection print the users collection\n" - ] - } - ], - "source": [ - "run(\"--help\") # !python mongodb_admin.py --help" - ] - }, - { - "cell_type": "markdown", - "id": "9579cbc3", - "metadata": {}, - "source": [ - "#### Cleaning the database" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "da0863e4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection datasets.\n", - "Deleted collection metadata.\n", - "Deleted collection users.\n" - ] - } - ], - "source": [ - "run(\"drop_collection --collection datasets\") \n", - "run(\"drop_collection --collection metadata\")\n", - "run(\"drop_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "d7edd7d3-20f9-4546-afc8-25661f948d44", - "metadata": {}, - "source": [ - "## **Datasets (add and drop)**" - ] - }, - { - "cell_type": "markdown", - "id": "ed1597b3-767f-470c-a7d7-8fe41dd82da5", - "metadata": {}, - "source": [ - "#### For each dataset, 2 informations are required:\n", - "\n", - "#### - the type of database in which the dataset is stored\n", - "#### - a path to the metadata of the dataset (stored as a yaml file).\n", - "\n", - "#### Metadata are expected to be in the same format as [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details)." - ] - }, - { - "cell_type": "markdown", - "id": "9b0e730b", - "metadata": {}, - "source": [ - "## Add one dataset" - ] - }, - { - "cell_type": "markdown", - "id": "d1d331ea", - "metadata": {}, - "source": [ - "#### We can add **one dataset** with its name, database type and path to medata file:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "53f5787d-e721-43d9-85ce-da842f173381", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added dataset PENGUIN with database REMOTE_HTTP_DB and metadata from ../data/collections/metadata/penguin_metadata.yaml.\n" - ] - } - ], - "source": [ - "run(\"add_dataset -d PENGUIN -db REMOTE_HTTP_DB -mp ../data/collections/metadata/penguin_metadata.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "4e57ddf9", - "metadata": {}, - "source": [ - "### Add multiple datasets" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "0e42f9cb-3a02-45f5-baee-2e06edda739f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "Added datasets collection from yaml at ../data/collections/dataset_collection.yaml. \n", - "Added metadata of IRIS dataset. \n", - "Added metadata of PENGUIN dataset. \n", - "Added metadata of TITANIC dataset. \n", - "Added metadata of FSO_INCOME_SYNTHETIC dataset. \n" - ] - } - ], - "source": [ - "run(\"add_datasets --path ../data/collections/dataset_collection.yaml -c\")" - ] - }, - { - "cell_type": "markdown", - "id": "0e0b85d5", - "metadata": {}, - "source": [ - "## **Users**" - ] - }, - { - "cell_type": "markdown", - "id": "14ab18db-4b6d-4663-bde0-b5d9d3d3d2ee", - "metadata": {}, - "source": [ - "#### Adding users" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "0f6aa33c-6bd1-4d62-ba06-3533b064340d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Dr. Antartica with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"add_user_with_budget --user 'Dr. Antartica' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "markdown", - "id": "0bed2714", - "metadata": {}, - "source": [ - "### And we can also modify existing the total budget of a user:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "87eecb9c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Dr. Antartica for dataset PENGUIN of initial_epsilon to 20.0.\n" - ] - } - ], - "source": [ - "run(\"set_budget_field --user 'Dr. Antartica' --dataset 'PENGUIN' --field initial_epsilon --value 20.0\")" - ] - }, - { - "cell_type": "markdown", - "id": "ba7cfa86", - "metadata": {}, - "source": [ - "### Finally, many users can actually be loaded directly from a single file" - ] - }, - { - "cell_type": "markdown", - "id": "20b3cd2c", - "metadata": {}, - "source": [ - "#### We add the data based on a yaml file:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "87b776f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "Added user data from yaml at ../data/collections/user_collection.yaml.\n" - ] - } - ], - "source": [ - "run(\"create_users_collection --path ../data/collections/user_collection.yaml -c\")" - ] - }, - { - "cell_type": "markdown", - "id": "d8f49d17", - "metadata": {}, - "source": [ - "### Archives of queries" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "4a31b694", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"show_collection --collection queries_archives\")" - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "## **Stopping the service: Let's not do it right now!**\n", - "\n", - "To tear down the service, we simply execute the command `helm uninstall lomas-service`" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "2fdbfafb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "release \"sdd-service\" uninstalled\n" - ] - } - ], - "source": [ - "!helm uninstall lomas-service" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/demo_kubernetes_deployment_notebook.html b/html/de/notebooks/demo_kubernetes_deployment_notebook.html deleted file mode 100644 index a40b45c2..00000000 --- a/html/de/notebooks/demo_kubernetes_deployment_notebook.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - - - Demo - Kubernetes Service Deployment — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Demo - Kubernetes Service Deployment

-
-

Building the container images

-
-

Use docker login to setup your credentials

-
-
[1]:
-
-
-
from IPython.display import Image
-Image(filename="images/image_demo_deployment_containers.png", width=800)
-
-
-
-
-
[1]:
-
-
-
-../_images/notebooks_demo_kubernetes_deployment_notebook_2_0.png -
-
-
-

Server

-
-
[ ]:
-
-
-
!cd .. && docker build --target lomas_server -t <your_registry>/lomas_server:latest .
-!cd .. && docker push <your_registry>/lomas_server:latest
-
-
-
-
-
-

Client

-
-
[ ]:
-
-
-
!cd ../../client/ && docker build --target lomas_client -t <your_registry>/lomas_client:latest .
-!cd ../../client/ && docker push <your_registry>/lomas_client:latest
-
-
-
-
-
-
-
-

Starting the service

-
-
[2]:
-
-
-
from IPython.display import Image
-Image(filename="images/image_demo_deployment_service.png", width=800)
-
-
-
-
-
[2]:
-
-
-
-../_images/notebooks_demo_kubernetes_deployment_notebook_8_0.png -
-
-
-
[3]:
-
-
-
import os
-os.chdir('../deploy/helm/charts/lomas_server')
-
-
-
-
-

Update values.yaml file

-
-
-

Download Helm dependency for the MongoDB chart

-
-
[2]:
-
-
-
!helm dependency update
-
-
-
-
-
-
-
-
-Saving 1 charts
-Downloading mongodb from repo oci://registry-1.docker.io/bitnamicharts
-Pulled: registry-1.docker.io/bitnamicharts/mongodb:13.18.1
-Digest: sha256:f3b2a691537260044746bc4a8898e9ae68e8c29864639737b6da920f99aebe97
-Deleting outdated charts
-
-
-
-
-

Install server chart

-
-
[3]:
-
-
-
!helm install -f values.yaml lomas-service .
-
-
-
-
-
-
-
-
-W1212 08:38:59.187407  178308 warnings.go:70] annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
-NAME: lomas-service
-LAST DEPLOYED: Tue Dec 12 08:38:56 2023
-NAMESPACE: user-aymond
-STATUS: deployed
-REVISION: 1
-TEST SUITE: None
-NOTES:
-1. Get the application URL by running these commands:
-  https://lomas-server.lab.sspcloud.fr/
-
-
-
-
-

Check deployment with kubectl get all and by querying <server_url>/state

-
-
-
-

Starting the client session

-
-
[4]:
-
-
-
os.chdir("../../../../notebooks/")
-from IPython.display import Image
-Image(filename="images/image_demo_deployment_client.png", width=800)
-
-
-
-
-
[4]:
-
-
-
-../_images/notebooks_demo_kubernetes_deployment_notebook_16_0.png -
-
-
-
[5]:
-
-
-
os.chdir("../../client/deploy/helm/charts/lomas_client")
-
-
-
-
-

Update values.yaml file

-
-
-

Install the client chart

-
-
[7]:
-
-
-
!helm install -f values.yaml lomas-client .
-
-
-
-
-
-
-
-
-W1212 10:06:30.269176  189525 warnings.go:70] annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
-NAME: lomas-client
-LAST DEPLOYED: Tue Dec 12 10:06:28 2023
-NAMESPACE: user-aymond
-STATUS: deployed
-REVISION: 1
-TEST SUITE: None
-NOTES:
-1. Get the application URL by running these commands:
-  https://lomas-client.lab.sspcloud.fr/
-
-
-
-
-

Access the client environment through the url and use the password defined in the values file.

-
-
-
-

Stopping the service

-
-
[ ]:
-
-
-
!helm uninstall lomas-service
-!helm uninstall lomas-client
-
-
-
-
-
- - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/demo_kubernetes_deployment_notebook.ipynb b/html/de/notebooks/demo_kubernetes_deployment_notebook.ipynb deleted file mode 100644 index e0f8ce10..00000000 --- a/html/de/notebooks/demo_kubernetes_deployment_notebook.ipynb +++ /dev/null @@ -1,341 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Demo - Kubernetes Service Deployment" - ] - }, - { - "cell_type": "markdown", - "id": "91ba5946", - "metadata": {}, - "source": [ - "## Building the container images\n", - "\n", - "#### Use `docker login` to setup your credentials" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "086a9a84", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "execution_count": 1, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_deployment_containers.png\", width=800)" - ] - }, - { - "cell_type": "markdown", - "id": "988ce0c2", - "metadata": {}, - "source": [ - "### Server" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0650340f", - "metadata": {}, - "outputs": [], - "source": [ - "!cd .. && docker build --target lomas_server -t /lomas_server:latest .\n", - "!cd .. && docker push /lomas_server:latest" - ] - }, - { - "cell_type": "markdown", - "id": "ee01e006", - "metadata": {}, - "source": [ - "### Client" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "36d2d05c", - "metadata": {}, - "outputs": [], - "source": [ - "!cd ../../client/ && docker build --target lomas_client -t /lomas_client:latest .\n", - "!cd ../../client/ && docker push /lomas_client:latest" - ] - }, - { - "cell_type": "markdown", - "id": "0034a717", - "metadata": {}, - "source": [ - "## Starting the service" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "43dccc5e", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_deployment_service.png\", width=800)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e249d717", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../deploy/helm/charts/lomas_server')" - ] - }, - { - "cell_type": "markdown", - "id": "11075ea0", - "metadata": {}, - "source": [ - "\n", - "#### Update `values.yaml` file\n", - "\n", - "#### Download Helm dependency for the MongoDB chart" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "fe550e12", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Saving 1 charts\n", - "Downloading mongodb from repo oci://registry-1.docker.io/bitnamicharts\n", - "Pulled: registry-1.docker.io/bitnamicharts/mongodb:13.18.1\n", - "Digest: sha256:f3b2a691537260044746bc4a8898e9ae68e8c29864639737b6da920f99aebe97\n", - "Deleting outdated charts\n" - ] - } - ], - "source": [ - "!helm dependency update" - ] - }, - { - "cell_type": "markdown", - "id": "2913bce4", - "metadata": {}, - "source": [ - "#### Install server chart" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5ed0e2a9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "W1212 08:38:59.187407 178308 warnings.go:70] annotation \"kubernetes.io/ingress.class\" is deprecated, please use 'spec.ingressClassName' instead\n", - "NAME: lomas-service\n", - "LAST DEPLOYED: Tue Dec 12 08:38:56 2023\n", - "NAMESPACE: user-aymond\n", - "STATUS: deployed\n", - "REVISION: 1\n", - "TEST SUITE: None\n", - "NOTES:\n", - "1. Get the application URL by running these commands:\n", - " https://lomas-server.lab.sspcloud.fr/\n" - ] - } - ], - "source": [ - "!helm install -f values.yaml lomas-service ." - ] - }, - { - "cell_type": "markdown", - "id": "527d4837", - "metadata": {}, - "source": [ - "#### Check deployment with `kubectl get all` and by querying `/state`" - ] - }, - { - "cell_type": "markdown", - "id": "0d564566", - "metadata": {}, - "source": [ - "## Starting the client session\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "81ce5384", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": { - "image/png": { - "width": 800 - } - }, - "output_type": "execute_result" - } - ], - "source": [ - "os.chdir(\"../../../../notebooks/\")\n", - "from IPython.display import Image\n", - "Image(filename=\"images/image_demo_deployment_client.png\", width=800)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "79ccb52f", - "metadata": {}, - "outputs": [], - "source": [ - "os.chdir(\"../../client/deploy/helm/charts/lomas_client\")" - ] - }, - { - "cell_type": "markdown", - "id": "fb673425", - "metadata": {}, - "source": [ - "#### Update `values.yaml` file\n", - "\n", - "#### Install the client chart" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "W1212 10:06:30.269176 189525 warnings.go:70] annotation \"kubernetes.io/ingress.class\" is deprecated, please use 'spec.ingressClassName' instead\n", - "NAME: lomas-client\n", - "LAST DEPLOYED: Tue Dec 12 10:06:28 2023\n", - "NAMESPACE: user-aymond\n", - "STATUS: deployed\n", - "REVISION: 1\n", - "TEST SUITE: None\n", - "NOTES:\n", - "1. Get the application URL by running these commands:\n", - " https://lomas-client.lab.sspcloud.fr/\n" - ] - } - ], - "source": [ - "!helm install -f values.yaml lomas-client ." - ] - }, - { - "cell_type": "markdown", - "id": "aa2bfb9d", - "metadata": {}, - "source": [ - "#### Access the client environment through the url and use the password defined in the values file." - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "## Stopping the service" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "297189d2", - "metadata": {}, - "outputs": [], - "source": [ - "!helm uninstall lomas-service\n", - "!helm uninstall lomas-client" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/income_minimal.html b/html/de/notebooks/income_minimal.html deleted file mode 100644 index 3a1059f9..00000000 --- a/html/de/notebooks/income_minimal.html +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - - Minimal OpenDP example on the income dataset — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Minimal OpenDP example on the income dataset

-

(Just to check if it’s working as intended)

-
-
[1]:
-
-
-
from lomas_client.client import Client
-
-APP_URL = "http://lomas_server_dev:80" # Onyxia: "https://lomas-server-demo.lab.sspcloud.fr"
-USER_NAME = "Dr. FSO"
-DATASET_NAME = "FSO_INCOME_SYNTHETIC"
-client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)
-
-
-
-
-
[2]:
-
-
-
income_metadata = client.get_dataset_metadata()
-income_metadata
-
-
-
-
-
[2]:
-
-
-
-
-{'max_ids': 1,
- 'columns': {'region': {'type': 'int'},
-  'eco_branch': {'type': 'int'},
-  'profession': {'type': 'int'},
-  'education': {'type': 'int'},
-  'age': {'type': 'int'},
-  'sex': {'type': 'int'},
-  'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}
-
-
-
-
[3]:
-
-
-
NB_ROWS = 200
-SEED = 0
-
-df_dummy = client.get_dummy_dataset(
-    nb_rows = NB_ROWS,
-    seed = SEED
-)
-
-print(df_dummy.shape)
-df_dummy.head()
-
-
-
-
-
-
-
-
-(200, 7)
-
-
-
-
[3]:
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
regioneco_branchprofessioneducationagesexincome
0-7268-87594470-70972279447099981.049234
1799903-4654-6748-2700134520528.687956
2-155-5597-5224-35529480803953955.911640
39648758237294274-178-757829734.556213
431239381-5878-25749842816531113.182182
-
-
-
-
[6]:
-
-
-
import opendp.prelude as dp
-import opendp.transformations as trans
-import opendp.measurements as meas
-
-dp.enable_features("contrib")
-
-columns = ["region", "eco_branch", "profession", "education", "age", "sex", "income"]
-
-income_min = float(income_metadata['columns']["income"]["lower"])
-income_max = float(income_metadata['columns']["income"]["upper"])
-
-num_rows_pipeline = (
-    trans.make_split_dataframe(separator=",", col_names=columns) >>
-    trans.make_select_column(key="income", TOA=str) >>
-    trans.then_count() >>
-    meas.then_laplace(scale=0.5) # scale arbitrary
-)
-
-num_rows = client.opendp_query(
-    opendp_pipeline = num_rows_pipeline,
-)["query_response"]
-
-
-
-
-
[7]:
-
-
-
income_average_pipeline = (
-    trans.make_split_dataframe(separator=",", col_names=columns) >>
-    trans.make_select_column(key="income", TOA=str) >>
-    trans.then_cast_default(TOA=float) >>
-    trans.then_clamp(bounds=(income_min, income_max)) >>
-    trans.then_resize(size=num_rows, constant=1000.0) >> # Arbitrary constant
-    trans.then_mean() >>
-    meas.then_laplace(scale=0.5)
-)
-
-income_average = client.opendp_query(
-    opendp_pipeline = income_average_pipeline,
-    dummy=True
-)
-
-
-
-
-
[8]:
-
-
-
income_average
-
-
-
-
-
[8]:
-
-
-
-
-{'query_response': 1001.9747347375568}
-
-
-
-
[ ]:
-
-
-

-
-
-
-
- - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/income_minimal.ipynb b/html/de/notebooks/income_minimal.ipynb deleted file mode 100644 index b1905c5e..00000000 --- a/html/de/notebooks/income_minimal.ipynb +++ /dev/null @@ -1,286 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "13180a2e-3676-4b55-8de4-c114a50aba35", - "metadata": {}, - "source": [ - "## Minimal OpenDP example on the income dataset\n", - "\n", - "(Just to check if it's working as intended)" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "20ebbbe0-71f2-4daf-93f6-185d7574c8fc", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client.client import Client\n", - "\n", - "APP_URL = \"http://lomas_server_dev:80\" # Onyxia: \"https://lomas-server-demo.lab.sspcloud.fr\"\n", - "USER_NAME = \"Dr. FSO\"\n", - "DATASET_NAME = \"FSO_INCOME_SYNTHETIC\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "f1b5cc29-9581-4fd8-9efe-ad818f861fdf", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'columns': {'region': {'type': 'int'},\n", - " 'eco_branch': {'type': 'int'},\n", - " 'profession': {'type': 'int'},\n", - " 'education': {'type': 'int'},\n", - " 'age': {'type': 'int'},\n", - " 'sex': {'type': 'int'},\n", - " 'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "income_metadata = client.get_dataset_metadata()\n", - "income_metadata" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "fc70134d-264b-4088-ac6c-9c56361dbc32", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
regioneco_branchprofessioneducationagesexincome
0-7268-87594470-70972279447099981.049234
1799903-4654-6748-2700134520528.687956
2-155-5597-5224-35529480803953955.911640
39648758237294274-178-757829734.556213
431239381-5878-25749842816531113.182182
\n", - "
" - ], - "text/plain": [ - " region eco_branch profession education age sex income\n", - "0 -7268 -8759 4470 -7097 2279 4470 99981.049234\n", - "1 799 903 -4654 -6748 -2700 1345 20528.687956\n", - "2 -155 -5597 -5224 -3552 9480 8039 53955.911640\n", - "3 9648 7582 3729 4274 -178 -7578 29734.556213\n", - "4 3123 9381 -5878 -2574 9842 8165 31113.182182" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0\n", - "\n", - "df_dummy = client.get_dummy_dataset(\n", - " nb_rows = NB_ROWS, \n", - " seed = SEED\n", - ")\n", - "\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "ce85a354-8a3a-42be-92bf-b06c7ceaf0e7", - "metadata": {}, - "outputs": [], - "source": [ - "import opendp.prelude as dp\n", - "import opendp.transformations as trans\n", - "import opendp.measurements as meas\n", - "\n", - "dp.enable_features(\"contrib\")\n", - "\n", - "columns = [\"region\", \"eco_branch\", \"profession\", \"education\", \"age\", \"sex\", \"income\"]\n", - "\n", - "income_min = float(income_metadata['columns'][\"income\"][\"lower\"])\n", - "income_max = float(income_metadata['columns'][\"income\"][\"upper\"])\n", - "\n", - "num_rows_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"income\", TOA=str) >>\n", - " trans.then_count() >>\n", - " meas.then_laplace(scale=0.5) # scale arbitrary\n", - ")\n", - "\n", - "num_rows = client.opendp_query(\n", - " opendp_pipeline = num_rows_pipeline,\n", - ")[\"query_response\"]\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "1c713e61-4c80-4514-88cd-b15d8e151c5a", - "metadata": {}, - "outputs": [], - "source": [ - "income_average_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"income\", TOA=str) >>\n", - " trans.then_cast_default(TOA=float) >>\n", - " trans.then_clamp(bounds=(income_min, income_max)) >>\n", - " trans.then_resize(size=num_rows, constant=1000.0) >> # Arbitrary constant\n", - " trans.then_mean() >>\n", - " meas.then_laplace(scale=0.5)\n", - ")\n", - "\n", - "income_average = client.opendp_query(\n", - " opendp_pipeline = income_average_pipeline,\n", - " dummy=True\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "834105bb-e8dc-4243-88cf-27b687711b8a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'query_response': 1001.9747347375568}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "income_average" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3b86525b-36cc-4533-b7a5-a298c8bb132f", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/kubernetes_admin_notebook.html b/html/de/notebooks/kubernetes_admin_notebook.html deleted file mode 100644 index f068eb10..00000000 --- a/html/de/notebooks/kubernetes_admin_notebook.html +++ /dev/null @@ -1,1128 +0,0 @@ - - - - - - - Secure Data Disclosure on Kubernetes: Deployment and Server Administration — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • - Quelltext anzeigen -
  • -
-
-
-
-
- -
-

Secure Data Disclosure on Kubernetes: Deployment and Server Administration

-

This notebook showcases how a data owner could set up the service on a kubernetes cluster, add and make their data available to certain user. We will do this in a step by step fashion.

-
-

Deploying the service

-
-

Building the server image

-

The Lomas service is comprised of a fastapi server and a MongoDB database for keeping state and administration. While the database image is public, the server image must first be built and pushed to a registry.

-

NOTE: For now, the server configuration file is copied and put into the server container. This is of course not practical (and not safe, since the configuration file contains passwords and secrets) and will be updated in future versions. The config/example_config.yaml is the one that is copied into the container. One has to change it and rebuild+push the server container in order to change the server configuration.

-
-
[ ]:
-
-
-
# !docker login (=> use personal token from dockerhub, has to be done only once)
-
-!cd .. && docker build --target lomas_server -t <your_registry>/lomas_serverer:latest .
-!cd .. && docker push <your_registry>/lomas-server:latest
-
-
-
-
-
-

Deploying the service Helm chart

-

We use a Helm chart to deploy the service on a Kubernetes cluster. The lomas-server chart is located at deploy/helm/charts/lomas_server, let us change our working directory to this location.

-
-
[2]:
-
-
-
import os
-os.chdir('../deploy/helm/charts/lomas_server')
-
-
-
-

The values.yaml file contains all the configuration values for the service. We must now update the image.repository field to the one we pushed the server container image to. One can also change the url to which the service will be published with ingress.hosts[0].host (or disable this feature by setting ingress.enabled to False).

-
=> Update `values.yaml` file
-
-
-

As previously stated, the service is made up of a server and a MongoDB database. Before installing the chart, we must thus first download that dependency.

-
-
[2]:
-
-
-
!helm dependency update
-
-
-
-
-
-
-
-
-Saving 1 charts
-Downloading mongodb from repo oci://registry-1.docker.io/bitnamicharts
-Save error occurred:  could not download oci://registry-1.docker.io/bitnamicharts/mongodb: failed to copy: httpReadSeeker: failed open: failed to do request: Get "https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/95/953b9cef6799e942255a1d5edcb7cb7508230fb57e4d68d02e27aed4b1694eaf/data?verify=1701701776-w82MF1fkjhlDOYT4WuSJHicDe5c%3D": dial tcp: lookup production.cloudflare.docker.com on 169.254.25.10:53: server misbehaving
-Error: could not download oci://registry-1.docker.io/bitnamicharts/mongodb: failed to copy: httpReadSeeker: failed open: failed to do request: Get "https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/95/953b9cef6799e942255a1d5edcb7cb7508230fb57e4d68d02e27aed4b1694eaf/data?verify=1701701776-w82MF1fkjhlDOYT4WuSJHicDe5c%3D": dial tcp: lookup production.cloudflare.docker.com on 169.254.25.10:53: server misbehaving
-
-
-

Now the chart is ready to be installed, so let the magic happen!

-
-
[3]:
-
-
-
!helm install -f values.yaml lomas-service .
-
-
-
-
-
-
-
-
-Error: INSTALLATION FAILED: cannot re-use a name that is still in use
-
-
-

The installation notes show the url at which the server is exposed. One can have a look at the api docummentation by visiting <server_url>/docs

-

One can also check the whether the service started error free by using the kubectl get all command as well as inspecting the server logs with kubectl logs <server-pod-name>

-
-
-
-

Administering the service by accessing the mongoDB

-

Let’s switch directory again and move to the administration script.

-
-
[3]:
-
-
-
import os
-os.chdir('../../../../lomas_server/')
-
-
-
-

To interact with the mongoDB, we will need to install a few libraries. Let’s do so by creating a python virtual environment and installing the dependencies listed in admin_requirements.txt

-
-
[6]:
-
-
-
!pip install -r ../admin_requirements.txt
-
-
-
-
-
-
-
-
-Requirement already satisfied: annotated-types==0.5.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 1)) (0.5.0)
-Requirement already satisfied: anyio==3.7.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 2)) (3.7.1)
-Requirement already satisfied: asttokens==2.4.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 3)) (2.4.0)
-Requirement already satisfied: backcall==0.2.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 4)) (0.2.0)
-Collecting boto3 (from -r ../admin_requirements.txt (line 5))
-  Downloading boto3-1.34.40-py3-none-any.whl.metadata (6.6 kB)
-Requirement already satisfied: comm==0.1.4 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 6)) (0.1.4)
-Requirement already satisfied: debugpy==1.7.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 7)) (1.7.0)
-Requirement already satisfied: decorator==5.1.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 8)) (5.1.1)
-Requirement already satisfied: dnspython==2.4.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 9)) (2.4.2)
-Requirement already satisfied: executing==1.2.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 10)) (1.2.0)
-Requirement already satisfied: fastapi==0.103.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 11)) (0.103.1)
-Requirement already satisfied: idna==3.4 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 12)) (3.4)
-Requirement already satisfied: ipykernel==6.25.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 13)) (6.25.2)
-Requirement already satisfied: ipython==8.15.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 14)) (8.15.0)
-Requirement already satisfied: jedi==0.19.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 15)) (0.19.0)
-Requirement already satisfied: jupyter_client==8.3.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 16)) (8.3.1)
-Requirement already satisfied: jupyter_core==5.3.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 17)) (5.3.1)
-Requirement already satisfied: matplotlib-inline==0.1.6 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 18)) (0.1.6)
-Requirement already satisfied: nest-asyncio==1.5.7 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 19)) (1.5.7)
-Requirement already satisfied: packaging==23.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 20)) (23.1)
-Requirement already satisfied: parso==0.8.3 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 21)) (0.8.3)
-Requirement already satisfied: pexpect==4.8.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 22)) (4.8.0)
-Requirement already satisfied: pickleshare==0.7.5 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 23)) (0.7.5)
-Requirement already satisfied: platformdirs==3.10.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 24)) (3.10.0)
-Requirement already satisfied: prompt-toolkit==3.0.39 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 25)) (3.0.39)
-Requirement already satisfied: psutil==5.9.5 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 26)) (5.9.5)
-Requirement already satisfied: ptyprocess==0.7.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 27)) (0.7.0)
-Requirement already satisfied: pure-eval==0.2.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 28)) (0.2.2)
-Requirement already satisfied: pyaml==23.9.5 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 29)) (23.9.5)
-Requirement already satisfied: pydantic==2.3.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 30)) (2.3.0)
-Requirement already satisfied: pydantic_core==2.6.3 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 31)) (2.6.3)
-Requirement already satisfied: Pygments==2.16.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 32)) (2.16.1)
-Requirement already satisfied: pymongo==4.5.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 33)) (4.5.0)
-Requirement already satisfied: python-dateutil==2.8.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 34)) (2.8.2)
-Requirement already satisfied: PyYAML==6.0.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 35)) (6.0.1)
-Requirement already satisfied: pyzmq==25.1.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 36)) (25.1.1)
-Requirement already satisfied: six==1.16.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 37)) (1.16.0)
-Requirement already satisfied: sniffio==1.3.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 38)) (1.3.0)
-Requirement already satisfied: stack-data==0.6.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 39)) (0.6.2)
-Requirement already satisfied: starlette==0.27.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 40)) (0.27.0)
-Requirement already satisfied: tornado==6.3.3 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 41)) (6.3.3)
-Requirement already satisfied: traitlets==5.9.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 42)) (5.9.0)
-Requirement already satisfied: typing_extensions==4.7.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 43)) (4.7.1)
-Requirement already satisfied: wcwidth==0.2.6 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 44)) (0.2.6)
-Requirement already satisfied: exceptiongroup in /home/onyxia/work/.venv/lib/python3.10/site-packages (from anyio==3.7.1->-r ../admin_requirements.txt (line 2)) (1.2.0)
-Collecting botocore<1.35.0,>=1.34.40 (from boto3->-r ../admin_requirements.txt (line 5))
-  Downloading botocore-1.34.40-py3-none-any.whl.metadata (5.7 kB)
-Collecting jmespath<2.0.0,>=0.7.1 (from boto3->-r ../admin_requirements.txt (line 5))
-  Downloading jmespath-1.0.1-py3-none-any.whl (20 kB)
-Collecting s3transfer<0.11.0,>=0.10.0 (from boto3->-r ../admin_requirements.txt (line 5))
-  Downloading s3transfer-0.10.0-py3-none-any.whl.metadata (1.7 kB)
-Collecting urllib3<2.1,>=1.25.4 (from botocore<1.35.0,>=1.34.40->boto3->-r ../admin_requirements.txt (line 5))
-  Downloading urllib3-2.0.7-py3-none-any.whl.metadata (6.6 kB)
-Downloading boto3-1.34.40-py3-none-any.whl (139 kB)
-   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 139.3/139.3 kB 8.2 MB/s eta 0:00:00
-Downloading botocore-1.34.40-py3-none-any.whl (12.0 MB)
-   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.0/12.0 MB 58.2 MB/s eta 0:00:0000:0100:01
-Downloading s3transfer-0.10.0-py3-none-any.whl (82 kB)
-   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 82.1/82.1 kB 3.2 MB/s eta 0:00:00
-Downloading urllib3-2.0.7-py3-none-any.whl (124 kB)
-   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 124.2/124.2 kB 25.4 MB/s eta 0:00:00
-Installing collected packages: urllib3, jmespath, botocore, s3transfer, boto3
-Successfully installed boto3-1.34.40 botocore-1.34.40 jmespath-1.0.1 s3transfer-0.10.0 urllib3-2.0.7
-
-
-

We should now have the required environment to interact with the admin database.

-
-

Preparing the database

-

You can visualise all the options offered by the database by running the command python mongodb_admin.py --help. We will go through through each of them in the rest of the notebook.

-
-
[8]:
-
-
-
!python mongodb_admin.py --help
-
-
-
-
-
-
-
-
-usage: MongoDB administration script for the user database [-h]
-                                                           {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}
-                                                           ...
-
-options:
-  -h, --help            show this help message and exit
-
-subcommands:
-  {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}
-                        user database administration operations
-    add_user            add user to users collection
-    add_user_with_budget
-                        add user with budget to users collection
-    del_user            delete user from users collection
-    add_dataset_to_user
-                        add dataset with initialized budget values for a user
-    del_dataset_to_user
-                        delete dataset for user in users collection
-    set_budget_field    set budget field to given value for given user and
-                        dataset
-    set_may_query       set may query field to given value for given user
-    show_user           show all metadata of user
-    create_users_collection
-                        create users collection from yaml file
-    add_dataset         set in which database the dataset is stored
-    add_datasets        create dataset to database type collection
-    drop_collection     delete collection from database
-    show_collection     print the users collection
-
-
-

Let’s first make sure the database is empty and in a clean state.

-
-
[48]:
-
-
-
!python mongodb_admin.py drop_collection --collection datasets
-!python mongodb_admin.py drop_collection --collection metadata
-!python mongodb_admin.py drop_collection --collection users
-
-
-
-
-
-
-
-
-Deleted collection datasets.
-Deleted collection metadata.
-Deleted collection users.
-
-
-
-
-

Datasets (add and drop)

-

We first need to set the dataset meta-information. For each dataset, 2 informations are required: - the type of database in which the dataset is stored - a path to the metadata of the dataset (stored as a yaml file).

-

To later perform query on the dataset, metadata are required. In this secure server the metadata information is expected to be in the same format as SmartnoiseSQL dictionary format, where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). It is also expected to be in a yaml file.

-

These information (dataset name, dataset type and metadata path) are stored in the datasets collection. Then for each dataset, its metadata is fetched from its yaml file and stored in a collection named metadata.

-

We then check that there is indeed no data in the dataset and metadata collections yet:

-
-
[12]:
-
-
-
!python mongodb_admin.py show_collection --collection datasets
-
-
-
-
-
-
-
-
-[]
-
-
-
-
[13]:
-
-
-
!python mongodb_admin.py show_collection --collection metadata
-
-
-
-
-
-
-
-
-[]
-
-
-

We can add one dataset with its name, and informations on where to load the dataset and metadata file.

-
-
[17]:
-
-
-
!python mongodb_admin.py add_dataset -d "PENGUIN" -db "REMOTE_HTTP_DB" -d_url "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv" -m_db "LOCAL_DB" -mp "../data/collections/metadata/penguin_metadata.yaml"
-
-
-
-
-
-
-
-
-Added dataset PENGUIN with database REMOTE_HTTP_DB and associated metadata.
-
-
-

We can also show an example of how to add a dataset and its metadata stored on an S3 server. The access key id and secret access id should be updated depending on the location of the file.

-
-
[45]:
-
-
-
!python mongodb_admin.py add_dataset -d "TITANIC" -db "S3_DB" -s3b "example" -s3k "data/titanic.csv" -s3_url "https://api-lomas-minio.lab.sspcloud.fr" -s3_ak "admin" -s3_sak "admin123" -m_db "S3_DB" -m_s3b "example" -m_s3k "metadata/titanic_metadata.yaml" -m_s3_url "https://api-lomas-minio.lab.sspcloud.fr" -m_s3_ak "admin" -m_s3_sak "admin123"
-
-
-
-
-
-
-
-
-Added dataset TITANIC with database S3_DB and associated metadata.
-
-
-

We can now see the dataset and metadata collection with the Iris dataset:

-
-
[46]:
-
-
-
!python mongodb_admin.py show_collection --collection datasets
-
-
-
-
-
-
-
-
-[{'dataset_name': 'TITANIC', 'database_type': 'S3_DB', 's3_bucket': 'example', 's3_key': 'data/titanic.csv', 'endpoint_url': 'https://api-sdd-minio.lab.sspcloud.fr', 'aws_access_key_id': 'admin', 'aws_secret_access_key': 'admin123'}]
-
-
-
-
[47]:
-
-
-
!python mongodb_admin.py show_collection --collection metadata
-
-
-
-
-
-
-
-
-[{'TITANIC': {'': {'Schema': {'Table': {'max_ids': 1, 'PassengerId': {'type': 'int', 'lower': 1}, 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3}, 'Name': {'type': 'string'}, 'Sex': {'type': 'string', 'cardinality': 2, 'categories': ['male', 'female']}, 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0}, 'SibSp': {'type': 'int', 'lower': 0}, 'Parch': {'type': 'int', 'lower': 0}, 'Ticket': {'type': 'string'}, 'Fare': {'type': 'float', 'lower': 0.0}, 'Cabin': {'type': 'string'}, 'Embarked': {'type': 'string', 'cardinality': 3, 'categories': ['C', 'Q', 'S']}, 'Survived': {'type': 'boolean'}, 'row_privacy': True}}}, 'engine': 'csv'}}]
-
-
-

Or a path to a yaml file which contains all these informations to do multiple datasets in one command:

-
-
[49]:
-
-
-
!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -c
-
-
-
-
-
-
-
-
-Cleaning done.
-
-Added datasets collection from yaml at ../data/collections/dataset_collection.yaml.
-Added metadata of IRIS dataset.
-Added metadata of PENGUIN dataset.
-Added metadata of TITANIC dataset.
-Added metadata of FSO_INCOME_SYNTHETIC dataset.
-
-
-

The argument -c or –clean allows you to clear the current dataset collection before adding your collection.

-

By default, add_datasets will only add new dataset found from the collection provided.

-
-
[22]:
-
-
-
!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml
-
-
-
-
-
-
-
-
-Metadata already exist. Use the command -om to overwrite with new values.
-Metadata already exist. Use the command -om to overwrite with new values.
-Metadata already exist. Use the command -om to overwrite with new values.
-Metadata already exist. Use the command -om to overwrite with new values.
-
-
-

Arguments :

-

-od / –overwrite_datasets : Overwrite the values for exisiting datasets with the values provided in the yaml.

-

-om / –overwrite_metadata : Overwrite the values for exisiting metadata with the values provided in the yaml.

-
-
[23]:
-
-
-
# Add new datasets/metadata, update existing datasets
-!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -od
-
-
-
-
-
-
-
-
-Datasets updated with values from yaml at ../data/collections/dataset_collection.yaml.
-Metadata already exist. Use the command -om to overwrite with new values.
-Metadata already exist. Use the command -om to overwrite with new values.
-Metadata already exist. Use the command -om to overwrite with new values.
-Metadata already exist. Use the command -om to overwrite with new values.
-
-
-
-
[24]:
-
-
-
# Add new datasets/metadata, update existing metadata
-!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -om
-
-
-
-
-
-
-
-
-Metadata updated for dataset : IRIS.
-Metadata updated for dataset : PENGUIN.
-Metadata updated for dataset : TITANIC.
-Metadata updated for dataset : FSO_INCOME_SYNTHETIC.
-
-
-
-
[25]:
-
-
-
# Add new datasets/metadata, update existing datasets & metadata
-!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -od -om
-
-
-
-
-
-
-
-
-Datasets updated with values from yaml at ../data/collections/dataset_collection.yaml.
-Metadata updated for dataset : IRIS.
-Metadata updated for dataset : PENGUIN.
-Metadata updated for dataset : TITANIC.
-Metadata updated for dataset : FSO_INCOME_SYNTHETIC.
-
-
-

Let’s see all the dataset collection:

-
-
[26]:
-
-
-
!python mongodb_admin.py show_collection --collection datasets
-
-
-
-
-
-
-
-
-[{'dataset_name': 'IRIS', 'database_type': 'REMOTE_HTTP_DB', 'dataset_url': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/iris_metadata.yaml'}}, {'dataset_name': 'PENGUIN', 'database_type': 'REMOTE_HTTP_DB', 'dataset_url': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/penguin_metadata.yaml'}}, {'dataset_name': 'TITANIC', 'database_type': 'S3_DB', 's3_bucket': 'example', 's3_key': 'data/titanic.csv', 'endpoint_url': 'https://api-sdd-minio.lab.sspcloud.fr', 'aws_access_key_id': 'admin', 'aws_secret_access_key': 'admin123', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/titanic_metadata.yaml'}}, {'dataset_name': 'FSO_INCOME_SYNTHETIC', 'database_type': 'LOCAL_DB', 'dataset_path': '../data/datasets/income_synthetic_data.csv', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/fso_income_synthetic_metadata.yaml'}}]
-
-
-

Finally let’s have a look at the stored metadata:

-
-
[20]:
-
-
-
!python mongodb_admin.py show_collection --collection metadata
-
-
-
-
-
-
-
-
-[{'IRIS': {'': {'Schema': {'Table': {'max_ids': 1, 'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0}, 'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0}, 'row_privacy': True, 'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0}, 'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0}, 'species': {'type': 'string', 'cardinality': 3, 'categories': ['setosa', 'versicolor', 'virginica']}}}}, 'engine': 'csv'}}, {'PENGUIN': {'': {'Schema': {'Table': {'max_ids': 1, 'row_privacy': True, 'censor_dims': False, 'species': {'type': 'string', 'cardinality': 3, 'categories': ['Adelie', 'Chinstrap', 'Gentoo']}, 'island': {'type': 'string', 'cardinality': 3, 'categories': ['Torgersen', 'Biscoe', 'Dream']}, 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0}, 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0}, 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0}, 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0}, 'sex': {'type': 'string', 'cardinality': 2, 'categories': ['MALE', 'FEMALE']}}}}, 'engine': 'csv'}}, {'TITANIC': {'': {'Schema': {'Table': {'max_ids': 1, 'PassengerId': {'type': 'int', 'lower': 1}, 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3}, 'Name': {'type': 'string'}, 'Sex': {'type': 'string', 'cardinality': 2, 'categories': ['male', 'female']}, 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0}, 'SibSp': {'type': 'int', 'lower': 0}, 'Parch': {'type': 'int', 'lower': 0}, 'Ticket': {'type': 'string'}, 'Fare': {'type': 'float', 'lower': 0.0}, 'Cabin': {'type': 'string'}, 'Embarked': {'type': 'string', 'cardinality': 3, 'categories': ['C', 'Q', 'S']}, 'Survived': {'type': 'boolean'}, 'row_privacy': True}}}, 'engine': 'csv'}}, {'FSO_INCOME_SYNTHETIC': {'': {'Schema': {'Table': {'max_ids': 1, 'region': {'type': 'int'}, 'eco_branch': {'type': 'int'}, 'profession': {'type': 'int'}, 'education': {'type': 'int'}, 'age': {'type': 'int'}, 'sex': {'type': 'int'}, 'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}}, 'engine': 'csv'}}]
-
-
-
-
-

Users

-
-

Adding users

-

Let’s see which users are alreay loaded:

-
-
[21]:
-
-
-
!python mongodb_admin.py show_collection --collection users
-
-
-
-
-
-
-
-
-[]
-
-
-

And now let’s add few users.

-
-
[22]:
-
-
-
!python mongodb_admin.py add_user_with_budget --user 'Mrs. Daisy' --dataset 'IRIS' --epsilon 10.0 --delta 0.001
-
-
-
-
-
-
-
-
-Added access to user Mrs. Daisy with dataset IRIS, budget epsilon 10.0 and delta 0.001.
-
-
-
-
[23]:
-
-
-
!python mongodb_admin.py add_user_with_budget --user 'Mr. Coldheart' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001
-
-
-
-
-
-
-
-
-Added access to user Mr. Coldheart with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.
-
-
-
-
[24]:
-
-
-
!python mongodb_admin.py add_user_with_budget --user 'Lord McFreeze' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001
-
-
-
-
-
-
-
-
-Added access to user Lord McFreeze with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.
-
-
-

Users must all have different names, otherwise you will have an error and nothing will be done:

-
-
[25]:
-
-
-
!python mongodb_admin.py add_user_with_budget --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 10.0 --delta 0.001
-
-
-
-
-
-
-
-
-Traceback (most recent call last):
-  File "/home/onyxia/work/sdd-poc-server/server/src/mongodb_admin.py", line 549, in <module>
-    args.func(args)
-  File "/home/onyxia/work/sdd-poc-server/server/src/mongodb_admin.py", line 47, in add_user_with_budget
-    raise ValueError("Cannot add user because already exists. ")
-ValueError: Cannot add user because already exists.
-
-
-

If you want to add another dataset access to an existing user, just use the function add_dataset_to_user command.

-
-
[26]:
-
-
-
!python mongodb_admin.py add_dataset_to_user --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 5.0 --delta 0.005
-
-
-
-
-
-
-
-
-Added access to dataset IRIS to user Lord McFreeze with budget epsilon 5.0 and delta 0.005.
-
-
-

Alternatively, you can create a user without assigned dataset and then add dataset in another command.

-
-
[27]:
-
-
-
!python mongodb_admin.py add_user --user 'Madame Frostina'
-
-
-
-
-
-
-
-
-Added user Madame Frostina.
-
-
-
-
[28]:
-
-
-
!python mongodb_admin.py add_dataset_to_user --user 'Madame Frostina' --dataset 'IRIS' --epsilon 5.0 --delta 0.005
-
-
-
-
-
-
-
-
-Added access to dataset IRIS to user Madame Frostina with budget epsilon 5.0 and delta 0.005.
-
-
-
-
[29]:
-
-
-
!python mongodb_admin.py add_dataset_to_user --user 'Madame Frostina' --dataset 'PENGUIN' --epsilon 5.0 --delta 0.005
-
-
-
-
-
-
-
-
-Added access to dataset PENGUIN to user Madame Frostina with budget epsilon 5.0 and delta 0.005.
-
-
-

And we can also modify existing the total budget of a user:

-
-
[30]:
-
-
-
!python mongodb_admin.py add_user_with_budget --user 'Dr. Antartica' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001
-
-
-
-
-
-
-
-
-Added access to user Dr. Antartica with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.
-
-
-
-
[31]:
-
-
-
!python mongodb_admin.py set_budget_field --user 'Dr. Antartica' --dataset 'PENGUIN' --field initial_epsilon --value 20.0
-
-
-
-
-
-
-
-
-Set budget of Dr. Antartica for dataset PENGUIN of initial_epsilon to 20.0.
-
-
-

Let’s see the current state of the database:

-
-
[32]:
-
-
-
!python mongodb_admin.py show_collection --collection users
-
-
-
-
-
-
-
-
-[{'user_name': 'Mrs. Daisy', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Mr. Coldheart', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Lord McFreeze', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 20.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}]
-
-
-

Do not hesitate to re-run this command after every other command to ensure that everything runs as expected.

-
-
-

Removing users

-

You have just heard that the penguin named Coldheart might have malicious intentions and decide to remove his access until an investigation has been carried out. To ensure that he is not allowed to do any more queries, run the following command:

-
-
[33]:
-
-
-
!python mongodb_admin.py set_may_query --user 'Mr. Coldheart' --value False
-
-
-
-
-
-
-
-
-Set user Mr. Coldheart may query.
-
-
-

Now, he won’t be able to do any query (unless you re-run the query with –value True).

-

A few days have passed and the investigation reveals that he was aiming to do unethical research, you can remove his dataset by doing:

-
-
[34]:
-
-
-
!python mongodb_admin.py del_dataset_to_user --user 'Mr. Coldheart' --dataset 'PENGUIN'
-
-
-
-
-
-
-
-
-Remove access to dataset PENGUIN from user Mr. Coldheart.
-
-
-

Or delete him completely from the codebase:

-
-
[35]:
-
-
-
!python mongodb_admin.py del_user --user 'Mr. Coldheart'
-
-
-
-
-
-
-
-
-Deleted user Mr. Coldheart.
-
-
-

Let’s see the resulting users:

-
-
[36]:
-
-
-
!python mongodb_admin.py show_collection --collection users
-
-
-
-
-
-
-
-
-[{'user_name': 'Mrs. Daisy', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Lord McFreeze', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 20.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}]
-
-
-
-
-

Changing the budget

-

You also change your mind about the budget allowed to Lord McFreeze and give him a bit more on the penguin dataset.

-
-
[37]:
-
-
-
!python mongodb_admin.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_epsilon --value 15.0
-
-
-
-
-
-
-
-
-Set budget of Lord McFreeze for dataset PENGUIN of initial_epsilon to 15.0.
-
-
-
-
[38]:
-
-
-
!python mongodb_admin.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_delta --value 0.005
-
-
-
-
-
-
-
-
-Set budget of Lord McFreeze for dataset PENGUIN of initial_delta to 0.005.
-
-
-

Let’s check all our changes by looking at the state of the database:

-
-
[39]:
-
-
-
!python mongodb_admin.py show_collection --collection users
-
-
-
-
-
-
-
-
-[{'user_name': 'Mrs. Daisy', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Lord McFreeze', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 15.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 20.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}]
-
-
-
-
-
-

Finally, everything can actually be loaded directly from a single file

-

Let’s delete the existing user collection first:

-
-
[40]:
-
-
-
!python mongodb_admin.py drop_collection --collection users
-
-
-
-
-
-
-
-
-Deleted collection users.
-
-
-

Is is now empty:

-
-
[41]:
-
-
-
!python mongodb_admin.py show_collection --collection users
-
-
-
-
-
-
-
-
-[]
-
-
-

We add the data based on a yaml file:

-
-
[42]:
-
-
-
!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml
-
-
-
-
-
-
-
-
-Added user data from yaml at ../data/collections/user_collection.yaml.
-
-
-

By default, create_users_collection will only add new users to the database.

-
-
[43]:
-
-
-
!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml
-
-
-
-
-
-
-
-
-No new users added, they already exist in the server
-
-
-

If you want to clean the current users collection and replace it, you can use the argument –clean.

-
-
[44]:
-
-
-
!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml --clean
-
-
-
-
-
-
-
-
-Cleaning done.
-
-Added user data from yaml at ../data/collections/user_collection.yaml.
-
-
-

If you want to add new users and update the existing ones in your collection, you can use the argument –overwrite. This will make sure to add new users if they do not exist and replace values from existing users with the collection provided.

-
-
[45]:
-
-
-
!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml --overwrite
-
-
-
-
-
-
-
-
-Existing users updated.
-No new users added, they already exist in the server
-
-
-

And let’s see the resulting collection:

-
-
[46]:
-
-
-
!python mongodb_admin.py show_collection --collection users
-
-
-
-
-
-
-
-
-[{'user_name': 'Alice', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10, 'initial_delta': 0.0001, 'total_spent_epsilon': 1, 'total_spent_delta': 1e-06}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5, 'initial_delta': 0.0005, 'total_spent_epsilon': 0.2, 'total_spent_delta': 1e-07}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 45, 'initial_delta': 0.005, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}, {'user_name': 'Dr. FSO', 'may_query': True, 'datasets_list': [{'dataset_name': 'FSO_INCOME_SYNTHETIC', 'initial_epsilon': 45, 'initial_delta': 0.005, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}, {'user_name': 'Bob', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10, 'initial_delta': 0.0001, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}, {'user_name': 'Jack', 'may_query': True, 'datasets_list': [{'dataset_name': 'TITANIC', 'initial_epsilon': 45, 'initial_delta': 0.2, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}]
-
-
-
-
-
-

Archives of queries

-
-
[48]:
-
-
-
!python mongodb_admin.py show_collection --collection queries_archives
-
-
-
-
-
-
-
-
-[]
-
-
-
-
-

Stopping the service: Let’s not do it right now!

-

To tear down the service, we simply execute the command helm uninstall lomas-service

-
-
- - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/kubernetes_admin_notebook.ipynb b/html/de/notebooks/kubernetes_admin_notebook.ipynb deleted file mode 100644 index 903af62d..00000000 --- a/html/de/notebooks/kubernetes_admin_notebook.ipynb +++ /dev/null @@ -1,1437 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Secure Data Disclosure on Kubernetes: Deployment and Server Administration" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "This notebook showcases how a data owner could set up the service on a kubernetes cluster, add and make their data available to certain user. We will do this in a step by step fashion." - ] - }, - { - "cell_type": "markdown", - "id": "de384c88-559e-4384-a49b-1664ffdd6692", - "metadata": {}, - "source": [ - "## Deploying the service" - ] - }, - { - "cell_type": "markdown", - "id": "91ba5946", - "metadata": {}, - "source": [ - "### Building the server image\n", - "The Lomas service is comprised of a fastapi server and a MongoDB database for keeping state and administration. While the database image is public, the server image must first be built and pushed to a registry.\n", - "\n", - "NOTE: For now, the server configuration file is copied and put into the server container. This is of course not practical (and not safe, since the configuration file contains passwords and secrets) and will be updated in future versions. The `config/example_config.yaml` is the one that is copied into the container. One has to change it and rebuild+push the server container in order to change the server configuration." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8f688134", - "metadata": {}, - "outputs": [], - "source": [ - "# !docker login (=> use personal token from dockerhub, has to be done only once)\n", - "\n", - "!cd .. && docker build --target lomas_server -t /lomas_serverer:latest .\n", - "!cd .. && docker push /lomas-server:latest" - ] - }, - { - "cell_type": "markdown", - "id": "92f3237b-6f13-4c52-a9f2-82d94f0b7e66", - "metadata": {}, - "source": [ - "### Deploying the service Helm chart\n", - "We use a Helm chart to deploy the service on a Kubernetes cluster. The lomas-server chart is located at `deploy/helm/charts/lomas_server`, let us change our working directory to this location." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e249d717", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../deploy/helm/charts/lomas_server')" - ] - }, - { - "cell_type": "markdown", - "id": "11075ea0", - "metadata": {}, - "source": [ - "The `values.yaml` file contains all the configuration values for the service. We must now update the `image.repository` field to the one we pushed the server container image to. One can also change the url to which the service will be published with `ingress.hosts[0].host` (or disable this feature by setting `ingress.enabled` to `False`).\n", - "\n", - " => Update `values.yaml` file\n", - "\n", - "As previously stated, the service is made up of a server and a MongoDB database. Before installing the chart, we must thus first download that dependency." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "fe550e12", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Saving 1 charts\n", - "Downloading mongodb from repo oci://registry-1.docker.io/bitnamicharts\n", - "Save error occurred: could not download oci://registry-1.docker.io/bitnamicharts/mongodb: failed to copy: httpReadSeeker: failed open: failed to do request: Get \"https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/95/953b9cef6799e942255a1d5edcb7cb7508230fb57e4d68d02e27aed4b1694eaf/data?verify=1701701776-w82MF1fkjhlDOYT4WuSJHicDe5c%3D\": dial tcp: lookup production.cloudflare.docker.com on 169.254.25.10:53: server misbehaving\n", - "Error: could not download oci://registry-1.docker.io/bitnamicharts/mongodb: failed to copy: httpReadSeeker: failed open: failed to do request: Get \"https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/95/953b9cef6799e942255a1d5edcb7cb7508230fb57e4d68d02e27aed4b1694eaf/data?verify=1701701776-w82MF1fkjhlDOYT4WuSJHicDe5c%3D\": dial tcp: lookup production.cloudflare.docker.com on 169.254.25.10:53: server misbehaving\n" - ] - } - ], - "source": [ - "!helm dependency update" - ] - }, - { - "cell_type": "markdown", - "id": "2913bce4", - "metadata": {}, - "source": [ - "Now the chart is ready to be installed, so let the magic happen!" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5ed0e2a9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error: INSTALLATION FAILED: cannot re-use a name that is still in use\n" - ] - } - ], - "source": [ - "!helm install -f values.yaml lomas-service ." - ] - }, - { - "cell_type": "markdown", - "id": "527d4837", - "metadata": {}, - "source": [ - "The installation notes show the url at which the server is exposed. One can have a look at the api docummentation by visiting `/docs`\n", - "\n", - "One can also check the whether the service started error free by using the `kubectl get all` command as well as inspecting the server logs with `kubectl logs `" - ] - }, - { - "cell_type": "markdown", - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "source": [ - "## Administering the service by accessing the mongoDB" - ] - }, - { - "cell_type": "markdown", - "id": "6d2ec36f", - "metadata": {}, - "source": [ - "Let's switch directory again and move to the administration script." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "d4ede728", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../../../../lomas_server/')" - ] - }, - { - "cell_type": "markdown", - "id": "4a8c8115", - "metadata": {}, - "source": [ - "To interact with the mongoDB, we will need to install a few libraries. Let's do so by creating a python virtual environment and installing the dependencies listed in `admin_requirements.txt`" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "f6863a6d", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: annotated-types==0.5.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 1)) (0.5.0)\n", - "Requirement already satisfied: anyio==3.7.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 2)) (3.7.1)\n", - "Requirement already satisfied: asttokens==2.4.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 3)) (2.4.0)\n", - "Requirement already satisfied: backcall==0.2.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 4)) (0.2.0)\n", - "Collecting boto3 (from -r ../admin_requirements.txt (line 5))\n", - " Downloading boto3-1.34.40-py3-none-any.whl.metadata (6.6 kB)\n", - "Requirement already satisfied: comm==0.1.4 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 6)) (0.1.4)\n", - "Requirement already satisfied: debugpy==1.7.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 7)) (1.7.0)\n", - "Requirement already satisfied: decorator==5.1.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 8)) (5.1.1)\n", - "Requirement already satisfied: dnspython==2.4.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 9)) (2.4.2)\n", - "Requirement already satisfied: executing==1.2.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 10)) (1.2.0)\n", - "Requirement already satisfied: fastapi==0.103.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 11)) (0.103.1)\n", - "Requirement already satisfied: idna==3.4 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 12)) (3.4)\n", - "Requirement already satisfied: ipykernel==6.25.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 13)) (6.25.2)\n", - "Requirement already satisfied: ipython==8.15.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 14)) (8.15.0)\n", - "Requirement already satisfied: jedi==0.19.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 15)) (0.19.0)\n", - "Requirement already satisfied: jupyter_client==8.3.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 16)) (8.3.1)\n", - "Requirement already satisfied: jupyter_core==5.3.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 17)) (5.3.1)\n", - "Requirement already satisfied: matplotlib-inline==0.1.6 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 18)) (0.1.6)\n", - "Requirement already satisfied: nest-asyncio==1.5.7 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 19)) (1.5.7)\n", - "Requirement already satisfied: packaging==23.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 20)) (23.1)\n", - "Requirement already satisfied: parso==0.8.3 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 21)) (0.8.3)\n", - "Requirement already satisfied: pexpect==4.8.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 22)) (4.8.0)\n", - "Requirement already satisfied: pickleshare==0.7.5 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 23)) (0.7.5)\n", - "Requirement already satisfied: platformdirs==3.10.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 24)) (3.10.0)\n", - "Requirement already satisfied: prompt-toolkit==3.0.39 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 25)) (3.0.39)\n", - "Requirement already satisfied: psutil==5.9.5 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 26)) (5.9.5)\n", - "Requirement already satisfied: ptyprocess==0.7.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 27)) (0.7.0)\n", - "Requirement already satisfied: pure-eval==0.2.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 28)) (0.2.2)\n", - "Requirement already satisfied: pyaml==23.9.5 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 29)) (23.9.5)\n", - "Requirement already satisfied: pydantic==2.3.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 30)) (2.3.0)\n", - "Requirement already satisfied: pydantic_core==2.6.3 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 31)) (2.6.3)\n", - "Requirement already satisfied: Pygments==2.16.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 32)) (2.16.1)\n", - "Requirement already satisfied: pymongo==4.5.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 33)) (4.5.0)\n", - "Requirement already satisfied: python-dateutil==2.8.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 34)) (2.8.2)\n", - "Requirement already satisfied: PyYAML==6.0.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 35)) (6.0.1)\n", - "Requirement already satisfied: pyzmq==25.1.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 36)) (25.1.1)\n", - "Requirement already satisfied: six==1.16.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 37)) (1.16.0)\n", - "Requirement already satisfied: sniffio==1.3.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 38)) (1.3.0)\n", - "Requirement already satisfied: stack-data==0.6.2 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 39)) (0.6.2)\n", - "Requirement already satisfied: starlette==0.27.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 40)) (0.27.0)\n", - "Requirement already satisfied: tornado==6.3.3 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 41)) (6.3.3)\n", - "Requirement already satisfied: traitlets==5.9.0 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 42)) (5.9.0)\n", - "Requirement already satisfied: typing_extensions==4.7.1 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 43)) (4.7.1)\n", - "Requirement already satisfied: wcwidth==0.2.6 in /home/onyxia/work/.venv/lib/python3.10/site-packages (from -r ../admin_requirements.txt (line 44)) (0.2.6)\n", - "Requirement already satisfied: exceptiongroup in /home/onyxia/work/.venv/lib/python3.10/site-packages (from anyio==3.7.1->-r ../admin_requirements.txt (line 2)) (1.2.0)\n", - "Collecting botocore<1.35.0,>=1.34.40 (from boto3->-r ../admin_requirements.txt (line 5))\n", - " Downloading botocore-1.34.40-py3-none-any.whl.metadata (5.7 kB)\n", - "Collecting jmespath<2.0.0,>=0.7.1 (from boto3->-r ../admin_requirements.txt (line 5))\n", - " Downloading jmespath-1.0.1-py3-none-any.whl (20 kB)\n", - "Collecting s3transfer<0.11.0,>=0.10.0 (from boto3->-r ../admin_requirements.txt (line 5))\n", - " Downloading s3transfer-0.10.0-py3-none-any.whl.metadata (1.7 kB)\n", - "Collecting urllib3<2.1,>=1.25.4 (from botocore<1.35.0,>=1.34.40->boto3->-r ../admin_requirements.txt (line 5))\n", - " Downloading urllib3-2.0.7-py3-none-any.whl.metadata (6.6 kB)\n", - "Downloading boto3-1.34.40-py3-none-any.whl (139 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m139.3/139.3 kB\u001b[0m \u001b[31m8.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading botocore-1.34.40-py3-none-any.whl (12.0 MB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m12.0/12.0 MB\u001b[0m \u001b[31m58.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n", - "\u001b[?25hDownloading s3transfer-0.10.0-py3-none-any.whl (82 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m82.1/82.1 kB\u001b[0m \u001b[31m3.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading urllib3-2.0.7-py3-none-any.whl (124 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m124.2/124.2 kB\u001b[0m \u001b[31m25.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hInstalling collected packages: urllib3, jmespath, botocore, s3transfer, boto3\n", - "Successfully installed boto3-1.34.40 botocore-1.34.40 jmespath-1.0.1 s3transfer-0.10.0 urllib3-2.0.7\n" - ] - } - ], - "source": [ - "!pip install -r ../admin_requirements.txt\n" - ] - }, - { - "cell_type": "markdown", - "id": "9f35fd20-715c-483b-88e4-449c287ba61d", - "metadata": {}, - "source": [ - "We should now have the required environment to interact with the admin database." - ] - }, - { - "cell_type": "markdown", - "id": "d368d6a6-f1fe-4f65-9ce1-38c0b39584d1", - "metadata": {}, - "source": [ - "### Preparing the database" - ] - }, - { - "cell_type": "markdown", - "id": "b37c19b8-303d-4fe8-b515-33ed1099c581", - "metadata": {}, - "source": [ - "You can visualise all the options offered by the database by running the command `python mongodb_admin.py --help`. We will go through through each of them in the rest of the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "8a749f4b-93cb-460c-bb40-4880df6e51d9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "usage: MongoDB administration script for the user database [-h]\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}\n", - " ...\n", - "\n", - "options:\n", - " -h, --help show this help message and exit\n", - "\n", - "subcommands:\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,create_users_collection,add_dataset,add_datasets,drop_collection,show_collection}\n", - " user database administration operations\n", - " add_user add user to users collection\n", - " add_user_with_budget\n", - " add user with budget to users collection\n", - " del_user delete user from users collection\n", - " add_dataset_to_user\n", - " add dataset with initialized budget values for a user\n", - " del_dataset_to_user\n", - " delete dataset for user in users collection\n", - " set_budget_field set budget field to given value for given user and\n", - " dataset\n", - " set_may_query set may query field to given value for given user\n", - " show_user show all metadata of user\n", - " create_users_collection\n", - " create users collection from yaml file\n", - " add_dataset set in which database the dataset is stored\n", - " add_datasets create dataset to database type collection\n", - " drop_collection delete collection from database\n", - " show_collection print the users collection\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py --help" - ] - }, - { - "cell_type": "markdown", - "id": "9579cbc3", - "metadata": {}, - "source": [ - "Let's first make sure the database is empty and in a clean state." - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "18a3681c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection datasets.\n", - "Deleted collection metadata.\n", - "Deleted collection users.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py drop_collection --collection datasets\n", - "!python mongodb_admin.py drop_collection --collection metadata\n", - "!python mongodb_admin.py drop_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "d7edd7d3-20f9-4546-afc8-25661f948d44", - "metadata": {}, - "source": [ - "### Datasets (add and drop)" - ] - }, - { - "cell_type": "markdown", - "id": "ed1597b3-767f-470c-a7d7-8fe41dd82da5", - "metadata": {}, - "source": [ - "We first need to set the dataset meta-information. For each dataset, 2 informations are required:\n", - "- the type of database in which the dataset is stored\n", - "- a path to the metadata of the dataset (stored as a yaml file).\n", - "\n", - "To later perform query on the dataset, metadata are required. In this secure server the metadata information is expected to be in the same format as [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). It is also expected to be in a `yaml` file.\n", - "\n", - "These information (dataset name, dataset type and metadata path) are stored in the `datasets` collection. Then for each dataset, its metadata is fetched from its `yaml` file and stored in a collection named `metadata`." - ] - }, - { - "cell_type": "markdown", - "id": "2678fb3f", - "metadata": {}, - "source": [ - "We then check that there is indeed no data in the dataset and metadata collections yet:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "9b7a7fae", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection datasets" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "d36e03ff", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection metadata" - ] - }, - { - "cell_type": "markdown", - "id": "d1d331ea", - "metadata": {}, - "source": [ - "We can add **one dataset** with its name, and informations on where to load the dataset and metadata file." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "53f5787d-e721-43d9-85ce-da842f173381", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added dataset PENGUIN with database REMOTE_HTTP_DB and associated metadata.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset -d \"PENGUIN\" -db \"REMOTE_HTTP_DB\" -d_url \"https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv\" -m_db \"LOCAL_DB\" -mp \"../data/collections/metadata/penguin_metadata.yaml\"" - ] - }, - { - "cell_type": "markdown", - "id": "783c7b53", - "metadata": {}, - "source": [ - "We can also show an example of how to add a dataset and its metadata stored on an S3 server. The access key id and secret access id should be updated depending on the location of the file." - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "5604d9bc", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added dataset TITANIC with database S3_DB and associated metadata.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset -d \"TITANIC\" -db \"S3_DB\" -s3b \"example\" -s3k \"data/titanic.csv\" -s3_url \"https://api-lomas-minio.lab.sspcloud.fr\" -s3_ak \"admin\" -s3_sak \"admin123\" -m_db \"S3_DB\" -m_s3b \"example\" -m_s3k \"metadata/titanic_metadata.yaml\" -m_s3_url \"https://api-lomas-minio.lab.sspcloud.fr\" -m_s3_ak \"admin\" -m_s3_sak \"admin123\"" - ] - }, - { - "cell_type": "markdown", - "id": "398f8990", - "metadata": {}, - "source": [ - "We can now see the dataset and metadata collection with the Iris dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "3005eda2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'dataset_name': 'TITANIC', 'database_type': 'S3_DB', 's3_bucket': 'example', 's3_key': 'data/titanic.csv', 'endpoint_url': 'https://api-sdd-minio.lab.sspcloud.fr', 'aws_access_key_id': 'admin', 'aws_secret_access_key': 'admin123'}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection datasets" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "7527f3f4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'TITANIC': {'': {'Schema': {'Table': {'max_ids': 1, 'PassengerId': {'type': 'int', 'lower': 1}, 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3}, 'Name': {'type': 'string'}, 'Sex': {'type': 'string', 'cardinality': 2, 'categories': ['male', 'female']}, 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0}, 'SibSp': {'type': 'int', 'lower': 0}, 'Parch': {'type': 'int', 'lower': 0}, 'Ticket': {'type': 'string'}, 'Fare': {'type': 'float', 'lower': 0.0}, 'Cabin': {'type': 'string'}, 'Embarked': {'type': 'string', 'cardinality': 3, 'categories': ['C', 'Q', 'S']}, 'Survived': {'type': 'boolean'}, 'row_privacy': True}}}, 'engine': 'csv'}}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection metadata" - ] - }, - { - "cell_type": "markdown", - "id": "a0a2076e", - "metadata": {}, - "source": [ - "Or a path to a yaml file which contains all these informations to do **multiple datasets** in one command:" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "0e42f9cb-3a02-45f5-baee-2e06edda739f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "Added datasets collection from yaml at ../data/collections/dataset_collection.yaml. \n", - "Added metadata of IRIS dataset. \n", - "Added metadata of PENGUIN dataset. \n", - "Added metadata of TITANIC dataset. \n", - "Added metadata of FSO_INCOME_SYNTHETIC dataset. \n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -c" - ] - }, - { - "cell_type": "markdown", - "id": "d1a69223", - "metadata": {}, - "source": [ - "The argument *-c* or *--clean* allows you to clear the current dataset collection before adding your collection.\n", - "\n", - "By default, *add_datasets* will only add new dataset found from the collection provided." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "7047e416", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml" - ] - }, - { - "cell_type": "markdown", - "id": "46b06b96", - "metadata": {}, - "source": [ - "Arguments :\n", - "\n", - "*-od* / *--overwrite_datasets* : Overwrite the values for **exisiting datasets** with the values provided in the yaml.\n", - "\n", - "*-om* / *--overwrite_metadata* : Overwrite the values for **exisiting metadata** with the values provided in the yaml." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "692adbde", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Datasets updated with values from yaml at ../data/collections/dataset_collection.yaml.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "Metadata already exist. Use the command -om to overwrite with new values.\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing datasets\n", - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -od" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "2547d3c7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Metadata updated for dataset : IRIS.\n", - "Metadata updated for dataset : PENGUIN.\n", - "Metadata updated for dataset : TITANIC.\n", - "Metadata updated for dataset : FSO_INCOME_SYNTHETIC.\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing metadata\n", - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -om" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "9bc8ea43", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Datasets updated with values from yaml at ../data/collections/dataset_collection.yaml.\n", - "Metadata updated for dataset : IRIS.\n", - "Metadata updated for dataset : PENGUIN.\n", - "Metadata updated for dataset : TITANIC.\n", - "Metadata updated for dataset : FSO_INCOME_SYNTHETIC.\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing datasets & metadata\n", - "!python mongodb_admin.py add_datasets --path ../data/collections/dataset_collection.yaml -od -om" - ] - }, - { - "cell_type": "markdown", - "id": "87d686ae", - "metadata": {}, - "source": [ - "Let's see all the dataset collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "536b5b35", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'dataset_name': 'IRIS', 'database_type': 'REMOTE_HTTP_DB', 'dataset_url': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/iris_metadata.yaml'}}, {'dataset_name': 'PENGUIN', 'database_type': 'REMOTE_HTTP_DB', 'dataset_url': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/penguin_metadata.yaml'}}, {'dataset_name': 'TITANIC', 'database_type': 'S3_DB', 's3_bucket': 'example', 's3_key': 'data/titanic.csv', 'endpoint_url': 'https://api-sdd-minio.lab.sspcloud.fr', 'aws_access_key_id': 'admin', 'aws_secret_access_key': 'admin123', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/titanic_metadata.yaml'}}, {'dataset_name': 'FSO_INCOME_SYNTHETIC', 'database_type': 'LOCAL_DB', 'dataset_path': '../data/datasets/income_synthetic_data.csv', 'metadata': {'database_type': 'LOCAL_DB', 'metadata_path': '../data/collections/metadata/fso_income_synthetic_metadata.yaml'}}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection datasets" - ] - }, - { - "cell_type": "markdown", - "id": "0746b382-8692-445f-9ca9-0d2407a25259", - "metadata": {}, - "source": [ - "Finally let's have a look at the stored metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "c667dda0-5d0f-48c8-956c-8d8a756b7ff7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'IRIS': {'': {'Schema': {'Table': {'max_ids': 1, 'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0}, 'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0}, 'row_privacy': True, 'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0}, 'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0}, 'species': {'type': 'string', 'cardinality': 3, 'categories': ['setosa', 'versicolor', 'virginica']}}}}, 'engine': 'csv'}}, {'PENGUIN': {'': {'Schema': {'Table': {'max_ids': 1, 'row_privacy': True, 'censor_dims': False, 'species': {'type': 'string', 'cardinality': 3, 'categories': ['Adelie', 'Chinstrap', 'Gentoo']}, 'island': {'type': 'string', 'cardinality': 3, 'categories': ['Torgersen', 'Biscoe', 'Dream']}, 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0}, 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0}, 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0}, 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0}, 'sex': {'type': 'string', 'cardinality': 2, 'categories': ['MALE', 'FEMALE']}}}}, 'engine': 'csv'}}, {'TITANIC': {'': {'Schema': {'Table': {'max_ids': 1, 'PassengerId': {'type': 'int', 'lower': 1}, 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3}, 'Name': {'type': 'string'}, 'Sex': {'type': 'string', 'cardinality': 2, 'categories': ['male', 'female']}, 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0}, 'SibSp': {'type': 'int', 'lower': 0}, 'Parch': {'type': 'int', 'lower': 0}, 'Ticket': {'type': 'string'}, 'Fare': {'type': 'float', 'lower': 0.0}, 'Cabin': {'type': 'string'}, 'Embarked': {'type': 'string', 'cardinality': 3, 'categories': ['C', 'Q', 'S']}, 'Survived': {'type': 'boolean'}, 'row_privacy': True}}}, 'engine': 'csv'}}, {'FSO_INCOME_SYNTHETIC': {'': {'Schema': {'Table': {'max_ids': 1, 'region': {'type': 'int'}, 'eco_branch': {'type': 'int'}, 'profession': {'type': 'int'}, 'education': {'type': 'int'}, 'age': {'type': 'int'}, 'sex': {'type': 'int'}, 'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}}, 'engine': 'csv'}}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection metadata" - ] - }, - { - "cell_type": "markdown", - "id": "0e0b85d5", - "metadata": {}, - "source": [ - "### Users" - ] - }, - { - "cell_type": "markdown", - "id": "14ab18db-4b6d-4663-bde0-b5d9d3d3d2ee", - "metadata": {}, - "source": [ - "#### Adding users\n", - "Let's see which users are alreay loaded:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "7f450145", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "2d2ae627", - "metadata": {}, - "source": [ - "And now let's add few users." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "0f6aa33c-6bd1-4d62-ba06-3533b064340d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Mrs. Daisy with dataset IRIS, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Mrs. Daisy' --dataset 'IRIS' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "7858f019-8783-4fed-acd8-ff0107d33465", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Mr. Coldheart with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Mr. Coldheart' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "231e7d93-05ba-424a-8329-d96b0bfb4fb9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Lord McFreeze with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Lord McFreeze' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "markdown", - "id": "51b0c274-880c-44f9-9182-6cb162a54c55", - "metadata": {}, - "source": [ - "Users must all have different names, otherwise you will have an error and nothing will be done:" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "6276730e-39c2-47f1-962f-342c1acb7944", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Traceback (most recent call last):\n", - " File \"/home/onyxia/work/sdd-poc-server/server/src/mongodb_admin.py\", line 549, in \n", - " args.func(args)\n", - " File \"/home/onyxia/work/sdd-poc-server/server/src/mongodb_admin.py\", line 47, in add_user_with_budget\n", - " raise ValueError(\"Cannot add user because already exists. \")\n", - "ValueError: Cannot add user because already exists. \n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "markdown", - "id": "49f81f7e-e086-412f-8467-89b665e5559a", - "metadata": {}, - "source": [ - "If you want to add another dataset access to an existing user, just use the function `add_dataset_to_user` command." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "82a5f498-aed1-4779-9d73-b2b71dde4ce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset IRIS to user Lord McFreeze with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset_to_user --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 5.0 --delta 0.005" - ] - }, - { - "cell_type": "markdown", - "id": "06170073-49ed-4329-8101-2debdd77eb98", - "metadata": {}, - "source": [ - "Alternatively, you can create a user without assigned dataset and then add dataset in another command." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "06839270-36cf-4de7-b93c-d143c4866bc8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added user Madame Frostina.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user --user 'Madame Frostina'" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "e83378fe", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset IRIS to user Madame Frostina with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset_to_user --user 'Madame Frostina' --dataset 'IRIS' --epsilon 5.0 --delta 0.005" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "919b2652", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset PENGUIN to user Madame Frostina with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_dataset_to_user --user 'Madame Frostina' --dataset 'PENGUIN' --epsilon 5.0 --delta 0.005" - ] - }, - { - "cell_type": "markdown", - "id": "0bed2714", - "metadata": {}, - "source": [ - "And we can also modify existing the total budget of a user:" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "ec2cce5e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Dr. Antartica with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py add_user_with_budget --user 'Dr. Antartica' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "87eecb9c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Dr. Antartica for dataset PENGUIN of initial_epsilon to 20.0.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py set_budget_field --user 'Dr. Antartica' --dataset 'PENGUIN' --field initial_epsilon --value 20.0" - ] - }, - { - "cell_type": "markdown", - "id": "bbeb5dc2-e91e-4440-8df5-3e9506bf4ee1", - "metadata": {}, - "source": [ - "Let's see the current state of the database:" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "3b3f61c6-65dc-4b1e-a32e-47cdd2729ab6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'user_name': 'Mrs. Daisy', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Mr. Coldheart', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Lord McFreeze', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 20.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "4e0ae62f-ff80-4234-8102-4dccec0b284f", - "metadata": {}, - "source": [ - "Do not hesitate to re-run this command after every other command to ensure that everything runs as expected." - ] - }, - { - "cell_type": "markdown", - "id": "9ab1f5ba-68bd-4c96-bacd-b81dfa5d6302", - "metadata": {}, - "source": [ - "#### Removing users\n", - "You have just heard that the penguin named Coldheart might have malicious intentions and decide to remove his access until an investigation has been carried out. To ensure that he is not allowed to do any more queries, run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "7f341b3d-5a88-4fd9-8c97-cc70145834f1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set user Mr. Coldheart may query.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py set_may_query --user 'Mr. Coldheart' --value False" - ] - }, - { - "cell_type": "markdown", - "id": "4cc56586-f9a9-4e88-abed-51ba36a6e4f1", - "metadata": {}, - "source": [ - "Now, he won't be able to do any query (unless you re-run the query with --value True).\n", - "\n", - "A few days have passed and the investigation reveals that he was aiming to do unethical research, you can remove his dataset by doing:" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "9153d9af-b4be-4496-9f80-d140870f60fe", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Remove access to dataset PENGUIN from user Mr. Coldheart.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py del_dataset_to_user --user 'Mr. Coldheart' --dataset 'PENGUIN'" - ] - }, - { - "cell_type": "markdown", - "id": "18d411ae-a211-4997-8984-81281c6275eb", - "metadata": {}, - "source": [ - "Or delete him completely from the codebase:" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "a54e89eb-1ee1-48ad-9e00-bace8516a3ef", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted user Mr. Coldheart.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py del_user --user 'Mr. Coldheart'" - ] - }, - { - "cell_type": "markdown", - "id": "06a7c17f-da34-472a-ad7f-3ae73a1beb7b", - "metadata": {}, - "source": [ - "Let's see the resulting users:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "79fa414a-f097-4207-a628-19fa434a1ad3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'user_name': 'Mrs. Daisy', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Lord McFreeze', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 20.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "90a46a59-70ed-4a26-88cd-6ca8f1d17318", - "metadata": {}, - "source": [ - "#### Changing the budget\n", - "You also change your mind about the budget allowed to Lord McFreeze and give him a bit more on the penguin dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "0909e6c4-141e-4d57-acd2-bdc0a2d92cea", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Lord McFreeze for dataset PENGUIN of initial_epsilon to 15.0.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_epsilon --value 15.0" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "c0e110fe-4297-4559-9a95-bc0ebdfa402c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Lord McFreeze for dataset PENGUIN of initial_delta to 0.005.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_delta --value 0.005" - ] - }, - { - "cell_type": "markdown", - "id": "952d7ed4-ce1d-4a87-9319-6b57968ef20e", - "metadata": {}, - "source": [ - "Let's check all our changes by looking at the state of the database:" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "2ab46c5d-1553-4925-bd25-61c9c205dc95", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'user_name': 'Mrs. Daisy', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Lord McFreeze', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 15.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5.0, 'initial_delta': 0.005, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 20.0, 'initial_delta': 0.001, 'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}]}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "ba7cfa86", - "metadata": {}, - "source": [ - "### Finally, everything can actually be loaded directly from a single file" - ] - }, - { - "cell_type": "markdown", - "id": "43340fc9", - "metadata": {}, - "source": [ - "Let's delete the existing user collection first:" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "597cb0b3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection users.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py drop_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "81661298", - "metadata": {}, - "source": [ - "Is is now empty:" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "e1638145", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "20b3cd2c", - "metadata": {}, - "source": [ - "We add the data based on a yaml file:" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "87b776f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added user data from yaml at ../data/collections/user_collection.yaml.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml" - ] - }, - { - "cell_type": "markdown", - "id": "c86ccbe8", - "metadata": {}, - "source": [ - "By default, *create_users_collection* will only add new users to the database." - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "a84c9392", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "No new users added, they already exist in the server\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml" - ] - }, - { - "cell_type": "markdown", - "id": "c34ca747", - "metadata": {}, - "source": [ - "If you want to clean the current users collection and replace it, you can use the argument *--clean*. " - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "7bb9a70b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "Added user data from yaml at ../data/collections/user_collection.yaml.\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml --clean" - ] - }, - { - "cell_type": "markdown", - "id": "371742a4", - "metadata": {}, - "source": [ - "If you want to add new users and update the existing ones in your collection, you can use the argument *--overwrite*. This will make sure to add new users if they do not exist and replace values from existing users with the collection provided." - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "ae4ce110", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Existing users updated. \n", - "No new users added, they already exist in the server\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py create_users_collection --path ../data/collections/user_collection.yaml --overwrite" - ] - }, - { - "cell_type": "markdown", - "id": "63853e73", - "metadata": {}, - "source": [ - "And let's see the resulting collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "77866f52", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'user_name': 'Alice', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10, 'initial_delta': 0.0001, 'total_spent_epsilon': 1, 'total_spent_delta': 1e-06}, {'dataset_name': 'PENGUIN', 'initial_epsilon': 5, 'initial_delta': 0.0005, 'total_spent_epsilon': 0.2, 'total_spent_delta': 1e-07}]}, {'user_name': 'Dr. Antartica', 'may_query': True, 'datasets_list': [{'dataset_name': 'PENGUIN', 'initial_epsilon': 45, 'initial_delta': 0.005, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}, {'user_name': 'Dr. FSO', 'may_query': True, 'datasets_list': [{'dataset_name': 'FSO_INCOME_SYNTHETIC', 'initial_epsilon': 45, 'initial_delta': 0.005, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}, {'user_name': 'Bob', 'may_query': True, 'datasets_list': [{'dataset_name': 'IRIS', 'initial_epsilon': 10, 'initial_delta': 0.0001, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}, {'user_name': 'Jack', 'may_query': True, 'datasets_list': [{'dataset_name': 'TITANIC', 'initial_epsilon': 45, 'initial_delta': 0.2, 'total_spent_epsilon': 0, 'total_spent_delta': 0}]}]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection users" - ] - }, - { - "cell_type": "markdown", - "id": "942b58d3", - "metadata": {}, - "source": [ - "## Archives of queries" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "0ed2df36", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "!python mongodb_admin.py show_collection --collection queries_archives" - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "## Stopping the service: Let's not do it right now!\n", - "\n", - "To tear down the service, we simply execute the command `helm uninstall lomas-service`" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/kubernetes_deployment_notebook.html b/html/de/notebooks/kubernetes_deployment_notebook.html deleted file mode 100644 index f2cdb08b..00000000 --- a/html/de/notebooks/kubernetes_deployment_notebook.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - Kubernetes Service Deployment — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Kubernetes Service Deployment

-

This notebook showcases how a data owner could set up the service on a kubernetes cluster, add and make their data available to certain user. In addition, it also shows how to set up user sessions.

-

We use helm charts to deploy the service on a kubernetes cluster.

-
-

Building the container images

-

The Lomas service is comprised of a fastapi server and a MongoDB database for keeping state about users and datasets. While the database image is public, the server image must first be built and pushed to a registry. This also holds true for the client session image.

-

As a preparation step, first make sure to have a docker registry at your disposal and log into that:

-

docker login (=> use personal token from dockerhub, has to be done only once)

-

This must be done only once, the local docker credential helper will store the token locally.

-

Let’s now build the server image and push it to the registry. For this, we need to move from the root of this repository to server/ and run docker build:

-
-
[ ]:
-
-
-
!cd .. && docker build --target lomas_server -t <your_registry>/lomas_server:latest .
-!cd .. && docker push <your_registry>/lomas_server:latest
-
-
-
-

This will copy the server code as well as some dummy datasets into the server image.

-

As a second step, let’s do the same for the client image:

-
-
[ ]:
-
-
-
!cd ../../client/ && docker build --target lomas_client -t <your_registry>/lomas_client:latest .
-!cd ../../client/ && docker push <your_registry>/lomas_client:latest
-
-
-
-
-
-

Starting the service

-

We use a Helm chart to deploy the service on a Kubernetes cluster. For the next part of this notebook to work correctly, one must have access to a Kubernetes cluster with sufficient rights and an environment with correctly configured helm and kubectl command line tools.

-

The lomas-server chart is located at server/deploy/helm/charts/lomas_server, let us change our working directory to this location.

-
-
[90]:
-
-
-
import os
-os.chdir('../deploy/helm/charts/lomas_server')
-
-
-
-

The values.yaml file contains all the configuration values for the service. We must now update the image.repository field to the one we pushed the server container image to. One can also change the url to which the service will be published with ingress.hosts[0].host and ingress.tls.hosts[0] (or disable this feature by setting ingress.enabled to False).

-
=> Update `values.yaml` file
-
-
-

Password and secrets will be deployed in kubernetes secrets and other service parameters will be deployed in configMaps.

-

As previously stated, the service is made up of a server and a MongoDB database. Before installing the chart, we must thus first download the MongoDB dependency.

-
-
[91]:
-
-
-
!helm dependency update
-
-
-
-
-
-
-
-
-Saving 1 charts
-Downloading mongodb from repo oci://registry-1.docker.io/bitnamicharts
-Pulled: registry-1.docker.io/bitnamicharts/mongodb:13.18.1
-Digest: sha256:f3b2a691537260044746bc4a8898e9ae68e8c29864639737b6da920f99aebe97
-Deleting outdated charts
-
-
-

Now the chart is ready to be installed, so let the magic happen!

-
-
[92]:
-
-
-
!helm install -f values.yaml lomas-service .
-
-
-
-
-
-
-
-
-W0918 07:49:26.628394  768420 warnings.go:70] annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
-NAME: lomas-service
-LAST DEPLOYED: Mon Sep 18 07:49:24 2023
-NAMESPACE: user-paulineml
-STATUS: deployed
-REVISION: 1
-TEST SUITE: None
-NOTES:
-1. Get the application URL by running these commands:
-  https://sdd-demo.lab.sspcloud.fr/
-
-
-

The installation notes show the url at which the server is exposed. One can have a look at the server state by checking <server_url>/state and the api docummentation by visiting <server_url>/docs in their browser.

-

One can also check the whether the service started without issues by using the kubectl get all command as well as inspecting the server logs with kubectl logs <server-pod-name>

-
-
-

Starting the client session

-

The client deployment Helm chart is located in another directory, so let’s again move our working directory

-
-
[ ]:
-
-
-
os.chdir("../../../../../client/deploy/helm/charts/lomas_client")
-
-
-
-

Again, one needs to update the values.yaml file with the desired values. The important fields are ingress.hosts[0].host and ingress.tls.hosts[0] for the url, password for the user session and image.repository for specifying the previously built image. Make sure to change the nameOverride, fullnameOverride and url when deploying multiple client images.

-

=> update values.yaml

-

Similarly to the server deployment, let’s install the client Helm chart:

-
-
[ ]:
-
-
-
!helm install -f values.yaml lomas-client .
-
-
-
-

The user session should now be available at the specified url, just type the password and you are in!

-

If the service was started with developper mode to false, move to the admin_notebook to learn how to administer your freshly deployed service.

-

Once users and datasets have been added to the service, one can start to experiment with it. The URL to use for the service is the one defined in the server values.yaml file.

-
-
-

Stopping the service

-

To tear down the service, we simply execute the command helm uninstall lomas-service. The same goes for the client session with helm uninstall lomas-client.

-
-
- - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/kubernetes_deployment_notebook.ipynb b/html/de/notebooks/kubernetes_deployment_notebook.ipynb deleted file mode 100644 index c9ef3b27..00000000 --- a/html/de/notebooks/kubernetes_deployment_notebook.ipynb +++ /dev/null @@ -1,266 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Kubernetes Service Deployment" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "This notebook showcases how a data owner could set up the service on a kubernetes cluster, add and make their data available to certain user. In addition, it also shows how to set up user sessions.\n", - "\n", - "We use helm charts to deploy the service on a kubernetes cluster." - ] - }, - { - "cell_type": "markdown", - "id": "91ba5946", - "metadata": {}, - "source": [ - "### Building the container images\n", - "The Lomas service is comprised of a fastapi server and a MongoDB database for keeping state about users and datasets. While the database image is public, the server image must first be built and pushed to a registry. This also holds true for the client session image.\n", - "\n", - "As a preparation step, first make sure to have a docker registry at your disposal and log into that:\n", - "\n", - "`docker login` (=> use personal token from dockerhub, has to be done only once)\n", - "\n", - "This must be done only once, the local docker credential helper will store the token locally.\n", - "\n", - "Let's now build the server image and push it to the registry. For this, we need to move from the root of this repository to `server/` and run `docker build`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4ec58145", - "metadata": {}, - "outputs": [], - "source": [ - "!cd .. && docker build --target lomas_server -t /lomas_server:latest .\n", - "!cd .. && docker push /lomas_server:latest" - ] - }, - { - "cell_type": "markdown", - "id": "5197dd0c", - "metadata": {}, - "source": [ - "This will copy the server code as well as some dummy datasets into the server image.\n", - "\n", - "As a second step, let's do the same for the client image:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "74a06cfd", - "metadata": {}, - "outputs": [], - "source": [ - "!cd ../../client/ && docker build --target lomas_client -t /lomas_client:latest .\n", - "!cd ../../client/ && docker push /lomas_client:latest" - ] - }, - { - "cell_type": "markdown", - "id": "0034a717", - "metadata": {}, - "source": [ - "### Starting the service" - ] - }, - { - "cell_type": "markdown", - "id": "92f3237b-6f13-4c52-a9f2-82d94f0b7e66", - "metadata": {}, - "source": [ - "We use a Helm chart to deploy the service on a Kubernetes cluster. For the next part of this notebook to work correctly, one must have access to a Kubernetes cluster with sufficient rights and an environment with correctly configured helm and kubectl command line tools.\n", - "\n", - "The lomas-server chart is located at `server/deploy/helm/charts/lomas_server`, let us change our working directory to this location." - ] - }, - { - "cell_type": "code", - "execution_count": 90, - "id": "e249d717", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir('../deploy/helm/charts/lomas_server')" - ] - }, - { - "cell_type": "markdown", - "id": "11075ea0", - "metadata": {}, - "source": [ - "The `values.yaml` file contains all the configuration values for the service. We must now update the `image.repository` field to the one we pushed the server container image to. One can also change the url to which the service will be published with `ingress.hosts[0].host` and `ingress.tls.hosts[0]` (or disable this feature by setting `ingress.enabled` to `False`).\n", - "\n", - " => Update `values.yaml` file\n", - "\n", - "Password and secrets will be deployed in kubernetes secrets and other service parameters will be deployed in configMaps.\n", - "\n", - "As previously stated, the service is made up of a server and a MongoDB database. Before installing the chart, we must thus first download the MongoDB dependency." - ] - }, - { - "cell_type": "code", - "execution_count": 91, - "id": "fe550e12", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Saving 1 charts\n", - "Downloading mongodb from repo oci://registry-1.docker.io/bitnamicharts\n", - "Pulled: registry-1.docker.io/bitnamicharts/mongodb:13.18.1\n", - "Digest: sha256:f3b2a691537260044746bc4a8898e9ae68e8c29864639737b6da920f99aebe97\n", - "Deleting outdated charts\n" - ] - } - ], - "source": [ - "!helm dependency update" - ] - }, - { - "cell_type": "markdown", - "id": "2913bce4", - "metadata": {}, - "source": [ - "Now the chart is ready to be installed, so let the magic happen!" - ] - }, - { - "cell_type": "code", - "execution_count": 92, - "id": "5ed0e2a9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "W0918 07:49:26.628394 768420 warnings.go:70] annotation \"kubernetes.io/ingress.class\" is deprecated, please use 'spec.ingressClassName' instead\n", - "NAME: lomas-service\n", - "LAST DEPLOYED: Mon Sep 18 07:49:24 2023\n", - "NAMESPACE: user-paulineml\n", - "STATUS: deployed\n", - "REVISION: 1\n", - "TEST SUITE: None\n", - "NOTES:\n", - "1. Get the application URL by running these commands:\n", - " https://sdd-demo.lab.sspcloud.fr/\n" - ] - } - ], - "source": [ - "!helm install -f values.yaml lomas-service ." - ] - }, - { - "cell_type": "markdown", - "id": "527d4837", - "metadata": {}, - "source": [ - "The installation notes show the url at which the server is exposed. One can have a look at the server state by checking `/state` and the api docummentation by visiting `/docs` in their browser.\n", - "\n", - "One can also check the whether the service started without issues by using the `kubectl get all` command as well as inspecting the server logs with `kubectl logs `" - ] - }, - { - "cell_type": "markdown", - "id": "fda13679", - "metadata": {}, - "source": [ - "### Starting the client session\n", - "\n", - "The client deployment Helm chart is located in another directory, so let's again move our working directory" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ad2a7805", - "metadata": {}, - "outputs": [], - "source": [ - "os.chdir(\"../../../../../client/deploy/helm/charts/lomas_client\")" - ] - }, - { - "cell_type": "markdown", - "id": "fb36b717", - "metadata": {}, - "source": [ - "Again, one needs to update the `values.yaml` file with the desired values. The important fields are `ingress.hosts[0].host` and `ingress.tls.hosts[0]` for the url, `password` for the user session and `image.repository` for specifying the previously built image. Make sure to change the `nameOverride`, `fullnameOverride` and url when deploying multiple client images.\n", - "\n", - "`=> update values.yaml`\n", - "\n", - "Similarly to the server deployment, let's install the client Helm chart:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "outputs": [], - "source": [ - "!helm install -f values.yaml lomas-client ." - ] - }, - { - "cell_type": "markdown", - "id": "370c1ab1", - "metadata": {}, - "source": [ - "The user session should now be available at the specified url, just type the password and you are in!\n", - "\n", - "If the service was started with developper mode to false, move to the `admin_notebook` to learn how to administer your freshly deployed service.\n", - "\n", - "Once users and datasets have been added to the service, one can start to experiment with it. The URL to use for the service is the one defined in the server `values.yaml` file. " - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "### Stopping the service\n", - "\n", - "To tear down the service, we simply execute the command `helm uninstall lomas-service`. The same goes for the client session with `helm uninstall lomas-client`." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/local_admin_notebook.html b/html/de/notebooks/local_admin_notebook.html deleted file mode 100644 index d9ad052b..00000000 --- a/html/de/notebooks/local_admin_notebook.html +++ /dev/null @@ -1,1461 +0,0 @@ - - - - - - - Lomas-server: CLI administration — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Lomas-server: CLI administration

-

This notebook showcases how data owner could set up the server, add make their data available to certain users. It explains the different steps required.

-
-
-

Start the server

-
-

Create a docker volume

-

The first step is to create a docker volume for mongodb, which will hold all the „admin“ data of the server. Docker volumes are persistent storage spaces that are managed by docker and can be mounted in containers. To create the volume use docker volume create mongodata. This must be done only once, and we use bind mounts for the server, so no need to create volumes for that.

-

In a terminal run: docker volume create mongodata. In output you should see mongodata written.

-
-
-

Start server

-

The second step is to start the server. Therefore the config file configs/example_config.yaml has to be adapted. The data owner must make sure to set the develop mode to False, specify the database type and ports. For this notebook, we will keep the default and use a mongodb on port 27017. Note: Keep in mind that if the configuration file is modified then the docker-compose has to be modified accordingly. This is out of scope for this notebook.

-

In a terminal run docker compose up. This will start the server and the mongodb, each running in its own Docker container. In addition, it will also start a client session container for demonstration purposes and a streamlit container, more on that later.

-

To check that all containers are indeed running, run docker ps. You should be able to see a container for the server (lomas_server_dev), for the client (lomas_client_dev), for the streamlit (lomas_streamlit_dev) and one for the mongo database (mongodb).

-
-
-

Access the server to administrate the mongoDB

-

To interact with the mongoDB, we first need to access the server Docker container from where we will run the commands. To do that from inside this Jupyter Notebook, we will need to use the Docker client library. Let’s first install it.

-
-
[ ]:
-
-
-
!pip install docker
-
-
-
-

We can now import the library, create the client allowing us to interact with Docker, and finally, access the server container.

-
-
[1]:
-
-
-
import docker
-client = docker.DockerClient()
-server_container = client.containers.get("lomas_server_dev")
-
-
-
-

To execute commands inside that Docker container, you can use the exec_run method which will return an ExecResult object, from which you can retrieve the output of the command. Let’s see in the following example:

-
-
[2]:
-
-
-
response = server_container.exec_run("ls")
-print(response.output.decode('utf-8'))
-
-
-
-
-
-
-
-
-__init__.py
-__pycache__
-admin_database
-administration
-app.py
-constants.py
-dataset_store
-dp_queries
-mongodb_admin.py
-mongodb_admin_cli.py
-private_dataset
-tests
-utils
-uvicorn_serve.py
-
-
-
-

Now, you are ready to interact with the database and add users.

-
-
-
-

Prepare the database

-
-

Visualise all options

-

You can visualise all the options offered by the database by running the command python mongodb_admin_cli.py --help. We will go through through each of them in the rest of the notebook.

-

We prepare the function run_command to have a cleaner output of the commands in the notebook.

-
-
[3]:
-
-
-
from ast import literal_eval
-
-def run(command, to_dict=False):
-    response = server_container.exec_run(command)
-    output = response.output.decode('utf-8').replace("'", '"')
-    if "] -" in output:
-        output = output.split("] -")[1].strip()
-    if to_dict:
-        if len(output):
-            output = literal_eval(output)
-            return output
-    return print(output)
-
-
-
-
-
[4]:
-
-
-
run("python mongodb_admin_cli.py --help")
-
-
-
-
-
-
-
-
-usage: mongodb_admin_cli.py [-h]
-                            {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,add_users_via_yaml,show_archives,get_users,get_user_datasets,add_dataset,add_datasets_via_yaml,del_dataset,show_dataset,show_metadata,get_datasets,drop_collection,show_collection}
-                            ...
-
-MongoDB administration script for the database
-
-options:
-  -h, --help            show this help message and exit
-
-subcommands:
-  {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,add_users_via_yaml,show_archives,get_users,get_user_datasets,add_dataset,add_datasets_via_yaml,del_dataset,show_dataset,show_metadata,get_datasets,drop_collection,show_collection}
-                        user database administration operations
-    add_user            add user to users collection
-    add_user_with_budget
-                        add user with budget to users collection
-    del_user            delete user from users collection
-    add_dataset_to_user
-                        add dataset with initialized budget values for a user
-    del_dataset_to_user
-                        delete dataset for user in users collection
-    set_budget_field    set budget field to given value for given user and
-                        dataset
-    set_may_query       set may query field to given value for given user
-    show_user           show all metadata of user
-    add_users_via_yaml  create users collection from yaml file
-    show_archives       show all previous queries from a user
-    get_users           get the list of all users in "users" collection
-    get_user_datasets   get the list of all datasets from a user
-    add_dataset         set in which database the dataset is stored
-    add_datasets_via_yaml
-                        create dataset to database type collection
-    del_dataset         delete dataset and metadata from datasets and metadata
-                        collection
-    show_dataset        show a dataset from the dataset collection
-    show_metadata       show metadata from the metadata collection
-    get_datasets        get the list of all datasets in "datasets" collection
-    drop_collection     delete collection from database
-    show_collection     print a collection
-
-
-
-

And finally, let’s delete all existing data from database to start clean:

-
-
[5]:
-
-
-
run("python mongodb_admin_cli.py drop_collection --collection datasets")
-run("python mongodb_admin_cli.py drop_collection --collection metadata")
-run("python mongodb_admin_cli.py drop_collection --collection users")
-
-
-
-
-
-
-
-
-Deleted collection datasets.
-Deleted collection metadata.
-Deleted collection users.
-
-
-
-
-

Datasets (add and drop)

-

We first need to set the dataset meta-information. For each dataset, 2 informations are required: - the type of database in which the dataset is stored - a path to the metadata of the dataset (stored as a yaml file).

-

To later perform query on the dataset, metadata are required. In this secure server the metadata information is expected to be in the same format as SmartnoiseSQL dictionary format, where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). It is also expected to be in a yaml file.

-

These information (dataset name, dataset type and metadata path) are stored in the datasets collection. Then for each dataset, its metadata is fetched from its yaml file and stored in a collection named metadata.

-

We then check that there is indeed no data in the dataset and metadata collections yet:

-
-
[6]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection datasets")
-
-
-
-
-
-
-
-
-[]
-
-
-
-
[7]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection metadata")
-
-
-
-
-
-
-
-
-[]
-
-
-

We can add one dataset with its name, database type and path to medata file:

-
-
[8]:
-
-
-
run("python mongodb_admin_cli.py add_dataset -d IRIS -db PATH_DB -d_path https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv -m_db PATH_DB -mp ../data/collections/metadata/iris_metadata.yaml")
-
-
-
-
-
-
-
-
-Added dataset IRIS with database PATH_DB and associated metadata.
-
-
-

We can now see the dataset and metadata collection with the Iris dataset:

-
-
[9]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection datasets", to_dict=True)
-
-
-
-
-
[9]:
-
-
-
-
-[{'dataset_name': 'IRIS',
-  'database_type': 'PATH_DB',
-  'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv',
-  'metadata': {'database_type': 'PATH_DB',
-   'metadata_path': '../data/collections/metadata/iris_metadata.yaml'}}]
-
-
-
-
[10]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection metadata", to_dict=True)
-
-
-
-
-
[10]:
-
-
-
-
-[{'IRIS': {'max_ids': 1,
-   'row_privacy': True,
-   'columns': {'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0},
-    'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0},
-    'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0},
-    'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0},
-    'species': {'type': 'string',
-     'cardinality': 3,
-     'categories': ['setosa', 'versicolor', 'virginica']}}}}]
-
-
-

Or a path to a yaml file which contains all these informations to do multiple datasets in one command:

-
-
[11]:
-
-
-
run("python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -c")
-
-
-
-
-
-
-
-
-Cleaning done.
-
-2024-06-05 09:59:46,703 - INFO -                 [mongodb_admin.py:710 - add_datasets_via_yaml()
-
-
-

The argument -c or –clean allow you to clear the current dataset collection before adding your collection.

-

By default, add_datasets will only add new dataset found from the collection provided.

-
-
[12]:
-
-
-
run("python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml")
-
-
-
-
-
-
-
-
-Metadata already exist. Use the command -om to overwrite with new values.
-2024-06-05 09:59:48,726 - INFO -                 [mongodb_admin.py:755 - add_datasets_via_yaml()
-
-
-

Arguments :

-

-od / –overwrite_datasets : Overwrite the values for exisiting datasets with the values provided in the yaml.

-

-om / –overwrite_metadata : Overwrite the values for exisiting metadata with the values provided in the yaml.

-
-
[13]:
-
-
-
# Add new datasets/metadata, update existing datasets
-run("python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -od")
-
-
-
-
-
-
-
-
-Existing datasets updated with new collection
-2024-06-05 09:59:50,917 - INFO -                 [mongodb_admin.py:755 - add_datasets_via_yaml()
-
-
-
-
[14]:
-
-
-
# Add new datasets/metadata, update existing metadata
-run("python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -om")
-
-
-
-
-
-
-
-
-Metadata updated for dataset : IRIS.
-2024-06-05 09:59:52,741 - INFO -                 [mongodb_admin.py:749 - add_datasets_via_yaml()
-
-
-
-
[15]:
-
-
-
# Add new datasets/metadata, update existing datasets & metadata
-run("python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -od -om")
-
-
-
-
-
-
-
-
-Existing datasets updated with new collection
-2024-06-05 09:59:54,418 - INFO -                 [mongodb_admin.py:749 - add_datasets_via_yaml()
-
-
-

Let’s see all the dataset collection:

-
-
[16]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection datasets", to_dict=True)
-
-
-
-
-
[16]:
-
-
-
-
-[{'dataset_name': 'IRIS',
-  'database_type': 'PATH_DB',
-  'metadata': {'database_type': 'PATH_DB',
-   'metadata_path': '../data/collections/metadata/iris_metadata.yaml'},
-  'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv'},
- {'dataset_name': 'PENGUIN',
-  'database_type': 'PATH_DB',
-  'metadata': {'database_type': 'PATH_DB',
-   'metadata_path': '../data/collections/metadata/penguin_metadata.yaml'},
-  'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv'},
- {'dataset_name': 'TITANIC',
-  'database_type': 'S3_DB',
-  'metadata': {'database_type': 'S3_DB',
-   's3_bucket': 'example',
-   's3_key': 'metadata/titanic_metadata.yaml',
-   'endpoint_url': 'https://api-lomas-minio.lab.sspcloud.fr',
-   'aws_access_key_id': 'admin',
-   'aws_secret_access_key': 'admin123'},
-  's3_bucket': 'example',
-  's3_key': 'data/titanic.csv',
-  'endpoint_url': 'https://api-lomas-minio.lab.sspcloud.fr',
-  'aws_access_key_id': 'admin',
-  'aws_secret_access_key': 'admin123'},
- {'dataset_name': 'FSO_INCOME_SYNTHETIC',
-  'database_type': 'PATH_DB',
-  'metadata': {'database_type': 'PATH_DB',
-   'metadata_path': '../data/collections/metadata/fso_income_synthetic_metadata.yaml'},
-  'dataset_path': '../data/datasets/income_synthetic_data.csv'}]
-
-
-

Finally let’s have a look at the stored metadata:

-
-
[17]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection metadata", to_dict=True)
-
-
-
-
-
[17]:
-
-
-
-
-[{'IRIS': {'max_ids': 1,
-   'row_privacy': True,
-   'columns': {'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0},
-    'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0},
-    'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0},
-    'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0},
-    'species': {'type': 'string',
-     'cardinality': 3,
-     'categories': ['setosa', 'versicolor', 'virginica']}}}},
- {'PENGUIN': {'max_ids': 1,
-   'row_privacy': True,
-   'censor_dims': False,
-   'columns': {'species': {'type': 'string',
-     'cardinality': 3,
-     'categories': ['Adelie', 'Chinstrap', 'Gentoo']},
-    'island': {'type': 'string',
-     'cardinality': 3,
-     'categories': ['Torgersen', 'Biscoe', 'Dream']},
-    'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},
-    'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},
-    'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},
-    'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},
-    'sex': {'type': 'string',
-     'cardinality': 2,
-     'categories': ['MALE', 'FEMALE']}}}},
- {'TITANIC': {'': {'Schema': {'Table': {'max_ids': 1,
-      'PassengerId': {'type': 'int', 'lower': 1},
-      'Pclass': {'type': 'int', 'lower': 1, 'upper': 3},
-      'Name': {'type': 'string'},
-      'Sex': {'type': 'string',
-       'cardinality': 2,
-       'categories': ['male', 'female']},
-      'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0},
-      'SibSp': {'type': 'int', 'lower': 0},
-      'Parch': {'type': 'int', 'lower': 0},
-      'Ticket': {'type': 'string'},
-      'Fare': {'type': 'float', 'lower': 0.0},
-      'Cabin': {'type': 'string'},
-      'Embarked': {'type': 'string',
-       'cardinality': 3,
-       'categories': ['C', 'Q', 'S']},
-      'Survived': {'type': 'boolean'},
-      'row_privacy': True}}},
-   'engine': 'csv'}},
- {'FSO_INCOME_SYNTHETIC': {'max_ids': 1,
-   'columns': {'region': {'type': 'int'},
-    'eco_branch': {'type': 'int'},
-    'profession': {'type': 'int'},
-    'education': {'type': 'int'},
-    'age': {'type': 'int'},
-    'sex': {'type': 'int'},
-    'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}}]
-
-
-

If we are interested in a specific dataset, we can also show its collection:

-
-
[18]:
-
-
-
run("python mongodb_admin_cli.py show_dataset --dataset IRIS", to_dict=True)
-
-
-
-
-
[18]:
-
-
-
-
-{'dataset_name': 'IRIS',
- 'database_type': 'PATH_DB',
- 'metadata': {'database_type': 'PATH_DB',
-  'metadata_path': '../data/collections/metadata/iris_metadata.yaml'},
- 'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv'}
-
-
-

And its associated metadata:

-
-
[19]:
-
-
-
run("python mongodb_admin_cli.py show_metadata --dataset IRIS", to_dict=True)
-
-
-
-
-
[19]:
-
-
-
-
-{'max_ids': 1,
- 'row_privacy': True,
- 'columns': {'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0},
-  'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0},
-  'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0},
-  'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0},
-  'species': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['setosa', 'versicolor', 'virginica']}}}
-
-
-

We can also get list of all datasets in the ‚datasets‘ collection:

-
-
[20]:
-
-
-
run("python mongodb_admin_cli.py get_datasets", to_dict=True)
-
-
-
-
-
[20]:
-
-
-
-
-['IRIS', 'PENGUIN', 'TITANIC', 'FSO_INCOME_SYNTHETIC']
-
-
-
-
-

Users

-
-

Add user

-

Let’s see which users are alreay loaded:

-
-
[21]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection users")
-
-
-
-
-
-
-
-
-[]
-
-
-

And now let’s add few users.

-
-
[22]:
-
-
-
run("python mongodb_admin_cli.py add_user_with_budget --user 'Mrs. Daisy' --dataset 'IRIS' --epsilon 10.0 --delta 0.001")
-
-
-
-
-
-
-
-
-Added access to user Mrs. Daisy with dataset IRIS, budget epsilon 10.0 and delta 0.001.
-
-
-
-
[23]:
-
-
-
run("python mongodb_admin_cli.py add_user_with_budget --user 'Mr. Coldheart' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001")
-
-
-
-
-
-
-
-
-Added access to user Mr. Coldheart with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.
-
-
-
-
[24]:
-
-
-
run("python mongodb_admin_cli.py add_user_with_budget --user 'Lord McFreeze' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001")
-
-
-
-
-
-
-
-
-Added access to user Lord McFreeze with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.
-
-
-

Users must all have different names, otherwise you will have an error and nothing will be done:

-
-
[25]:
-
-
-
run("python mongodb_admin_cli.py add_user_with_budget --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 10.0 --delta 0.001")
-
-
-
-
-
-
-
-
-Traceback (most recent call last):
-  File "/code/mongodb_admin_cli.py", line 461, in <module>
-    function_map[args.func.__name__](args)
-  File "/code/mongodb_admin_cli.py", line 396, in <lambda>
-    "add_user_with_budget": lambda args: add_user_with_budget(
-                                         ^^^^^^^^^^^^^^^^^^^^^
-  File "/code/mongodb_admin.py", line 50, in wrapper_decorator
-    raise ValueError(
-ValueError: User Lord McFreeze already exists in user collection
-
-
-
-

If you want to add another dataset access to an existing user, just use the function add_dataset_to_user command.

-
-
[26]:
-
-
-
run("python mongodb_admin_cli.py add_dataset_to_user --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 5.0 --delta 0.005")
-
-
-
-
-
-
-
-
-Added access to dataset IRIS to user Lord McFreeze with budget epsilon 5.0 and delta 0.005.
-
-
-

Alternatively, you can create a user without assigned dataset and then add dataset in another command.

-
-
[27]:
-
-
-
run("python mongodb_admin_cli.py add_user --user 'Madame Frostina'")
-
-
-
-
-
-
-
-
-Added user Madame Frostina.
-
-
-

Let’s see the default parameters after the user creation:

-
-
[28]:
-
-
-
run("python mongodb_admin_cli.py show_user --user 'Madame Frostina'", to_dict=True)
-
-
-
-
-
[28]:
-
-
-
-
-{'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': []}
-
-
-

Let’s give her access to a dataset with a budget:

-
-
[29]:
-
-
-
run("python mongodb_admin_cli.py add_dataset_to_user --user 'Madame Frostina' --dataset 'IRIS' --epsilon 5.0 --delta 0.005")
-
-
-
-
-
-
-
-
-Added access to dataset IRIS to user Madame Frostina with budget epsilon 5.0 and delta 0.005.
-
-
-
-
[30]:
-
-
-
run("python mongodb_admin_cli.py add_dataset_to_user --user 'Madame Frostina' --dataset 'PENGUIN' --epsilon 5.0 --delta 0.005")
-
-
-
-
-
-
-
-
-Added access to dataset PENGUIN to user Madame Frostina with budget epsilon 5.0 and delta 0.005.
-
-
-

Now let’s see the user Madame Frostina details to check all is in order:

-
-
[31]:
-
-
-
run("python mongodb_admin_cli.py show_user --user 'Madame Frostina'", to_dict=True)
-
-
-
-
-
[31]:
-
-
-
-
-{'user_name': 'Madame Frostina',
- 'may_query': True,
- 'datasets_list': [{'dataset_name': 'IRIS',
-   'initial_epsilon': 5.0,
-   'initial_delta': 0.005,
-   'total_spent_epsilon': 0.0,
-   'total_spent_delta': 0.0},
-  {'dataset_name': 'PENGUIN',
-   'initial_epsilon': 5.0,
-   'initial_delta': 0.005,
-   'total_spent_epsilon': 0.0,
-   'total_spent_delta': 0.0}]}
-
-
-

And we can also modify existing the total budget of a user:

-
-
[32]:
-
-
-
run("python mongodb_admin_cli.py add_user_with_budget --user 'Dr. Antartica' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001")
-
-
-
-
-
-
-
-
-Added access to user Dr. Antartica with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.
-
-
-
-
[33]:
-
-
-
run("python mongodb_admin_cli.py set_budget_field --user 'Dr. Antartica' --dataset 'PENGUIN' --field initial_epsilon --value 20.0")
-
-
-
-
-
-
-
-
-Set budget of Dr. Antartica for dataset PENGUIN of initial_epsilon to 20.0.
-
-
-

Let’s see the current state of the database:

-
-
[34]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection users", to_dict=True)
-
-
-
-
-
[34]:
-
-
-
-
-[{'user_name': 'Mrs. Daisy',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'IRIS',
-    'initial_epsilon': 10.0,
-    'initial_delta': 0.001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Mr. Coldheart',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'PENGUIN',
-    'initial_epsilon': 10.0,
-    'initial_delta': 0.001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Lord McFreeze',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'PENGUIN',
-    'initial_epsilon': 10.0,
-    'initial_delta': 0.001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0},
-   {'dataset_name': 'IRIS',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Madame Frostina',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'IRIS',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0},
-   {'dataset_name': 'PENGUIN',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Dr. Antartica',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'PENGUIN',
-    'initial_epsilon': 20.0,
-    'initial_delta': 0.001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]}]
-
-
-

Do not hesitate to re-run this command after every other command to ensure that everything runs as expected.

-
-
-

Remove user

-

You have just heard that the penguin named Coldheart might have malicious intentions and decide to remove his access until an investigation has been carried out. To ensure that he is not allowed to do any more queries, run the following command:

-
-
[35]:
-
-
-
run("python mongodb_admin_cli.py set_may_query --user 'Mr. Coldheart' --value False")
-
-
-
-
-
-
-
-
-Set user Mr. Coldheart may query to False.
-
-
-

Now, he won’t be able to do any query (unless you re-run the query with –value True).

-

A few days have passed and the investigation reveals that he was aiming to do unethical research, you can remove his dataset by doing:

-
-
[36]:
-
-
-
run("python mongodb_admin_cli.py del_dataset_to_user --user 'Mr. Coldheart' --dataset 'PENGUIN'")
-
-
-
-
-
-
-
-
-Remove access to dataset PENGUIN from user Mr. Coldheart.
-
-
-

Or delete him completely from the codebase:

-
-
[37]:
-
-
-
run("python mongodb_admin_cli.py del_user --user 'Mr. Coldheart'")
-
-
-
-
-
-
-
-
-Deleted user Mr. Coldheart.
-
-
-

Let’s see the resulting users:

-
-
[38]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection users", to_dict=True)
-
-
-
-
-
[38]:
-
-
-
-
-[{'user_name': 'Mrs. Daisy',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'IRIS',
-    'initial_epsilon': 10.0,
-    'initial_delta': 0.001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Lord McFreeze',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'PENGUIN',
-    'initial_epsilon': 10.0,
-    'initial_delta': 0.001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0},
-   {'dataset_name': 'IRIS',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Madame Frostina',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'IRIS',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0},
-   {'dataset_name': 'PENGUIN',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Dr. Antartica',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'PENGUIN',
-    'initial_epsilon': 20.0,
-    'initial_delta': 0.001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]}]
-
-
-
-
-

Change budget

-

You also change your mind about the budget allowed to Lord McFreeze and give him a bit more on the penguin dataset.

-
-
[39]:
-
-
-
run("python mongodb_admin_cli.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_epsilon --value 15.0")
-
-
-
-
-
-
-
-
-Set budget of Lord McFreeze for dataset PENGUIN of initial_epsilon to 15.0.
-
-
-
-
[40]:
-
-
-
run("python mongodb_admin_cli.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_delta --value 0.005")
-
-
-
-
-
-
-
-
-Set budget of Lord McFreeze for dataset PENGUIN of initial_delta to 0.005.
-
-
-

Let’s check all our changes by looking at the state of the database:

-
-
[41]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection users", to_dict=True)
-
-
-
-
-
[41]:
-
-
-
-
-[{'user_name': 'Mrs. Daisy',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'IRIS',
-    'initial_epsilon': 10.0,
-    'initial_delta': 0.001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Lord McFreeze',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'PENGUIN',
-    'initial_epsilon': 15.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0},
-   {'dataset_name': 'IRIS',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Madame Frostina',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'IRIS',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0},
-   {'dataset_name': 'PENGUIN',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Dr. Antartica',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'PENGUIN',
-    'initial_epsilon': 20.0,
-    'initial_delta': 0.001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]}]
-
-
-
-
-

Finally all can be loaded fom a file direcly

-

Let’s delete the existing user collection first:

-
-
[42]:
-
-
-
run("python mongodb_admin_cli.py drop_collection --collection users")
-
-
-
-
-
-
-
-
-Deleted collection users.
-
-
-

Is is now empty:

-
-
[43]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection users")
-
-
-
-
-
-
-
-
-[]
-
-
-

We add the data based on a yaml file:

-
-
[44]:
-
-
-
run("python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml")
-
-
-
-
-
-
-
-
-Added user data from yaml.
-
-
-

By default, add_users_via_yaml will only add new users to the database.

-
-
[45]:
-
-
-
run("python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml")
-
-
-
-
-
-
-
-
-No new users added, they already exist in the server
-
-
-

If you want to clean the current users collection and replace it, you can use the argument –clean.

-
-
[46]:
-
-
-
run("python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml --clean")
-
-
-
-
-
-
-
-
-Cleaning done.
-
-2024-06-05 10:00:45,678 - INFO -                 [mongodb_admin.py:464 - add_users_via_yaml()
-
-
-

If you want to add new users and update the existing ones in your collection, you can use the argument –overwrite. This will make sure to add new users if they do not exist and replace values from existing users with the collection provided.

-
-
[47]:
-
-
-
run("python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml --overwrite")
-
-
-
-
-
-
-
-
-Existing users updated.
-2024-06-05 10:00:47,300 - INFO -                 [mongodb_admin.py:466 - add_users_via_yaml()
-
-
-

And let’s see the resulting collection:

-
-
[48]:
-
-
-
run("python mongodb_admin_cli.py show_collection --collection users", to_dict=True)
-
-
-
-
-
[48]:
-
-
-
-
-[{'user_name': 'Alice',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'IRIS',
-    'initial_epsilon': 10.0,
-    'initial_delta': 0.0001,
-    'total_spent_epsilon': 1.0,
-    'total_spent_delta': 1e-06},
-   {'dataset_name': 'PENGUIN',
-    'initial_epsilon': 5.0,
-    'initial_delta': 0.0005,
-    'total_spent_epsilon': 0.2,
-    'total_spent_delta': 1e-07}]},
- {'user_name': 'Dr. Antartica',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'PENGUIN',
-    'initial_epsilon': 10.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Dr. FSO',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'FSO_INCOME_SYNTHETIC',
-    'initial_epsilon': 45.0,
-    'initial_delta': 0.005,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Bob',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'IRIS',
-    'initial_epsilon': 10.0,
-    'initial_delta': 0.0001,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]},
- {'user_name': 'Jack',
-  'may_query': True,
-  'datasets_list': [{'dataset_name': 'TITANIC',
-    'initial_epsilon': 45.0,
-    'initial_delta': 0.2,
-    'total_spent_epsilon': 0.0,
-    'total_spent_delta': 0.0}]}]
-
-
-

To get a list of all users in the ‚users‘ collection:

-
-
[49]:
-
-
-
run("python mongodb_admin_cli.py get_users")
-
-
-
-
-
-
-
-
-["Alice", "Dr. Antartica", "Dr. FSO", "Bob", "Jack"]
-
-
-

We can also get a list of all datasets allocated to an user:

-
-
[50]:
-
-
-
run("python mongodb_admin_cli.py get_user_datasets --user Alice")
-
-
-
-
-
-
-
-
-["IRIS", "PENGUIN"]
-
-
-
-
-
-

Archives of queries

-
-
[51]:
-
-
-
run("python mongodb_admin_cli.py show_archives --user Alice")
-
-
-
-
-
-
-
-
-[]
-
-
-
-
-

Stop the server: do not do it now !

-

To tear down the service, first do ctrl+C in the terminal where you had done docker compose up. Wait for the command to finish executing and then run docker compose down. This will also delete all the containers but the volume will stay in place.

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/local_admin_notebook.ipynb b/html/de/notebooks/local_admin_notebook.ipynb deleted file mode 100644 index b95e3de3..00000000 --- a/html/de/notebooks/local_admin_notebook.ipynb +++ /dev/null @@ -1,1840 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Lomas-server: CLI administration" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "This notebook showcases how data owner could set up the server, add make their data available to certain users. It explains the different steps required." - ] - }, - { - "cell_type": "markdown", - "id": "de384c88-559e-4384-a49b-1664ffdd6692", - "metadata": {}, - "source": [ - "# Start the server" - ] - }, - { - "cell_type": "markdown", - "id": "92f3237b-6f13-4c52-a9f2-82d94f0b7e66", - "metadata": {}, - "source": [ - "## Create a docker volume\n", - "The first step is to create a docker volume for mongodb, which will hold all the \"admin\" data of the server. Docker volumes are persistent storage spaces that are managed by docker and can be mounted in containers. To create the volume use `docker volume create mongodata`. This must be done only once, and we use bind mounts for the server, so no need to create volumes for that." - ] - }, - { - "cell_type": "markdown", - "id": "87093f8e-68b1-4f1e-9e66-97c3885b3e48", - "metadata": {}, - "source": [ - "In a terminal run: `docker volume create mongodata`. In output you should see `mongodata` written." - ] - }, - { - "cell_type": "markdown", - "id": "f6829afb-d822-48e4-ba49-5daf0d79db7e", - "metadata": {}, - "source": [ - "## Start server\n", - "The second step is to start the server. Therefore the config file `configs/example_config.yaml` has to be adapted. The data owner must make sure to set the develop mode to False, specify the database type and ports. For this notebook, we will keep the default and use a mongodb on port 27017. Note: Keep in mind that if the configuration file is modified then the `docker-compose` has to be modified accordingly. This is out of scope for this notebook." - ] - }, - { - "cell_type": "markdown", - "id": "2408425a-13c6-4b89-a1fe-88491850fe10", - "metadata": {}, - "source": [ - "In a terminal run `docker compose up`. This will start the server and the mongodb, each running in its own Docker container. In addition, it will also start a client session container for demonstration purposes and a streamlit container, more on that later." - ] - }, - { - "cell_type": "markdown", - "id": "244da0f8", - "metadata": {}, - "source": [ - "To check that all containers are indeed running, run `docker ps`. You should be able to see a container for the server (`lomas_server_dev`), for the client (`lomas_client_dev`), for the streamlit (`lomas_streamlit_dev`) and one for the mongo database (`mongodb`)." - ] - }, - { - "cell_type": "markdown", - "id": "8dbebd54-8deb-46e6-b811-73ac74028569", - "metadata": {}, - "source": [ - "## Access the server to administrate the mongoDB" - ] - }, - { - "cell_type": "markdown", - "id": "4a8c8115", - "metadata": {}, - "source": [ - "To interact with the mongoDB, we first need to access the server Docker container from where we will run the commands. To do that from inside this Jupyter Notebook, we will need to use the Docker client library. Let's first install it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f6863a6d", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "!pip install docker" - ] - }, - { - "cell_type": "markdown", - "id": "b12b414a", - "metadata": {}, - "source": [ - "We can now import the library, create the client allowing us to interact with Docker, and finally, access the server container." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "112e4156", - "metadata": {}, - "outputs": [], - "source": [ - "import docker\n", - "client = docker.DockerClient()\n", - "server_container = client.containers.get(\"lomas_server_dev\")" - ] - }, - { - "cell_type": "markdown", - "id": "36b475e6", - "metadata": {}, - "source": [ - "To execute commands inside that Docker container, you can use the `exec_run` method which will return an ExecResult object, from which you can retrieve the output of the command. Let's see in the following example:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "dc0349be", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "__init__.py\n", - "__pycache__\n", - "admin_database\n", - "administration\n", - "app.py\n", - "constants.py\n", - "dataset_store\n", - "dp_queries\n", - "mongodb_admin.py\n", - "mongodb_admin_cli.py\n", - "private_dataset\n", - "tests\n", - "utils\n", - "uvicorn_serve.py\n", - "\n" - ] - } - ], - "source": [ - "response = server_container.exec_run(\"ls\")\n", - "print(response.output.decode('utf-8'))" - ] - }, - { - "cell_type": "markdown", - "id": "9f35fd20-715c-483b-88e4-449c287ba61d", - "metadata": {}, - "source": [ - "Now, you are ready to interact with the database and add users." - ] - }, - { - "cell_type": "markdown", - "id": "d368d6a6-f1fe-4f65-9ce1-38c0b39584d1", - "metadata": {}, - "source": [ - "# Prepare the database" - ] - }, - { - "cell_type": "markdown", - "id": "b37c19b8-303d-4fe8-b515-33ed1099c581", - "metadata": {}, - "source": [ - "## Visualise all options\n", - "You can visualise all the options offered by the database by running the command `python mongodb_admin_cli.py --help`. We will go through through each of them in the rest of the notebook." - ] - }, - { - "cell_type": "markdown", - "id": "e70abf6d", - "metadata": {}, - "source": [ - "We prepare the function `run_command` to have a cleaner output of the commands in the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f9277a43", - "metadata": {}, - "outputs": [], - "source": [ - "from ast import literal_eval\n", - "\n", - "def run(command, to_dict=False):\n", - " response = server_container.exec_run(command)\n", - " output = response.output.decode('utf-8').replace(\"'\", '\"')\n", - " if \"] -\" in output:\n", - " output = output.split(\"] -\")[1].strip()\n", - " if to_dict:\n", - " if len(output):\n", - " output = literal_eval(output)\n", - " return output\n", - " return print(output)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "fafa4e34", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "usage: mongodb_admin_cli.py [-h]\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,add_users_via_yaml,show_archives,get_users,get_user_datasets,add_dataset,add_datasets_via_yaml,del_dataset,show_dataset,show_metadata,get_datasets,drop_collection,show_collection}\n", - " ...\n", - "\n", - "MongoDB administration script for the database\n", - "\n", - "options:\n", - " -h, --help show this help message and exit\n", - "\n", - "subcommands:\n", - " {add_user,add_user_with_budget,del_user,add_dataset_to_user,del_dataset_to_user,set_budget_field,set_may_query,show_user,add_users_via_yaml,show_archives,get_users,get_user_datasets,add_dataset,add_datasets_via_yaml,del_dataset,show_dataset,show_metadata,get_datasets,drop_collection,show_collection}\n", - " user database administration operations\n", - " add_user add user to users collection\n", - " add_user_with_budget\n", - " add user with budget to users collection\n", - " del_user delete user from users collection\n", - " add_dataset_to_user\n", - " add dataset with initialized budget values for a user\n", - " del_dataset_to_user\n", - " delete dataset for user in users collection\n", - " set_budget_field set budget field to given value for given user and\n", - " dataset\n", - " set_may_query set may query field to given value for given user\n", - " show_user show all metadata of user\n", - " add_users_via_yaml create users collection from yaml file\n", - " show_archives show all previous queries from a user\n", - " get_users get the list of all users in \"users\" collection\n", - " get_user_datasets get the list of all datasets from a user\n", - " add_dataset set in which database the dataset is stored\n", - " add_datasets_via_yaml\n", - " create dataset to database type collection\n", - " del_dataset delete dataset and metadata from datasets and metadata\n", - " collection\n", - " show_dataset show a dataset from the dataset collection\n", - " show_metadata show metadata from the metadata collection\n", - " get_datasets get the list of all datasets in \"datasets\" collection\n", - " drop_collection delete collection from database\n", - " show_collection print a collection\n", - "\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py --help\")" - ] - }, - { - "cell_type": "markdown", - "id": "579b9571", - "metadata": {}, - "source": [ - "And finally, let's delete all existing data from database to start clean:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "18a3681c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection datasets.\n", - "Deleted collection metadata.\n", - "Deleted collection users.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py drop_collection --collection datasets\")\n", - "run(\"python mongodb_admin_cli.py drop_collection --collection metadata\")\n", - "run(\"python mongodb_admin_cli.py drop_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "d7edd7d3-20f9-4546-afc8-25661f948d44", - "metadata": {}, - "source": [ - "## Datasets (add and drop)" - ] - }, - { - "cell_type": "markdown", - "id": "ed1597b3-767f-470c-a7d7-8fe41dd82da5", - "metadata": {}, - "source": [ - "We first need to set the dataset meta-information. For each dataset, 2 informations are required:\n", - "- the type of database in which the dataset is stored\n", - "- a path to the metadata of the dataset (stored as a yaml file).\n", - "\n", - "To later perform query on the dataset, metadata are required. In this secure server the metadata information is expected to be in the same format as [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). It is also expected to be in a `yaml` file.\n", - "\n", - "These information (dataset name, dataset type and metadata path) are stored in the `datasets` collection. Then for each dataset, its metadata is fetched from its `yaml` file and stored in a collection named `metadata`." - ] - }, - { - "cell_type": "markdown", - "id": "2678fb3f", - "metadata": {}, - "source": [ - "We then check that there is indeed no data in the dataset and metadata collections yet:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "9b7a7fae", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection datasets\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "d36e03ff", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection metadata\")" - ] - }, - { - "cell_type": "markdown", - "id": "d1d331ea", - "metadata": {}, - "source": [ - "We can add **one dataset** with its name, database type and path to medata file:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "53f5787d-e721-43d9-85ce-da842f173381", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added dataset IRIS with database PATH_DB and associated metadata.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_dataset -d IRIS -db PATH_DB -d_path https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv -m_db PATH_DB -mp ../data/collections/metadata/iris_metadata.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "398f8990", - "metadata": {}, - "source": [ - "We can now see the dataset and metadata collection with the Iris dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "3005eda2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'dataset_name': 'IRIS',\n", - " 'database_type': 'PATH_DB',\n", - " 'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/iris_metadata.yaml'}}]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection datasets\", to_dict=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "7527f3f4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'IRIS': {'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0},\n", - " 'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0},\n", - " 'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0},\n", - " 'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0},\n", - " 'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['setosa', 'versicolor', 'virginica']}}}}]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection metadata\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "a0a2076e", - "metadata": {}, - "source": [ - "Or a path to a yaml file which contains all these informations to do **multiple datasets** in one command:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "0e42f9cb-3a02-45f5-baee-2e06edda739f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "2024-06-05 09:59:46,703 - INFO - [mongodb_admin.py:710 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -c\")" - ] - }, - { - "cell_type": "markdown", - "id": "19b86f6a", - "metadata": {}, - "source": [ - "The argument *-c* or *--clean* allow you to clear the current dataset collection before adding your collection.\n", - "\n", - "By default, *add_datasets* will only add new dataset found from the collection provided." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "88bbdcf2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Metadata already exist. Use the command -om to overwrite with new values.\n", - "2024-06-05 09:59:48,726 - INFO - [mongodb_admin.py:755 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "3a922c76", - "metadata": {}, - "source": [ - "Arguments :\n", - "\n", - "*-od* / *--overwrite_datasets* : Overwrite the values for **exisiting datasets** with the values provided in the yaml.\n", - "\n", - "*-om* / *--overwrite_metadata* : Overwrite the values for **exisiting metadata** with the values provided in the yaml." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "240928ab", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Existing datasets updated with new collection\n", - "2024-06-05 09:59:50,917 - INFO - [mongodb_admin.py:755 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing datasets\n", - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -od\")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "80de6b9c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Metadata updated for dataset : IRIS.\n", - "2024-06-05 09:59:52,741 - INFO - [mongodb_admin.py:749 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing metadata\n", - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -om\")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "b1a9f413", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Existing datasets updated with new collection\n", - "2024-06-05 09:59:54,418 - INFO - [mongodb_admin.py:749 - add_datasets_via_yaml()\n" - ] - } - ], - "source": [ - "# Add new datasets/metadata, update existing datasets & metadata\n", - "run(\"python mongodb_admin_cli.py add_datasets_via_yaml -yf ../data/collections/dataset_collection.yaml -od -om\")" - ] - }, - { - "cell_type": "markdown", - "id": "87d686ae", - "metadata": {}, - "source": [ - "Let's see all the dataset collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "536b5b35", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'dataset_name': 'IRIS',\n", - " 'database_type': 'PATH_DB',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/iris_metadata.yaml'},\n", - " 'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv'},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'database_type': 'PATH_DB',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/penguin_metadata.yaml'},\n", - " 'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv'},\n", - " {'dataset_name': 'TITANIC',\n", - " 'database_type': 'S3_DB',\n", - " 'metadata': {'database_type': 'S3_DB',\n", - " 's3_bucket': 'example',\n", - " 's3_key': 'metadata/titanic_metadata.yaml',\n", - " 'endpoint_url': 'https://api-lomas-minio.lab.sspcloud.fr',\n", - " 'aws_access_key_id': 'admin',\n", - " 'aws_secret_access_key': 'admin123'},\n", - " 's3_bucket': 'example',\n", - " 's3_key': 'data/titanic.csv',\n", - " 'endpoint_url': 'https://api-lomas-minio.lab.sspcloud.fr',\n", - " 'aws_access_key_id': 'admin',\n", - " 'aws_secret_access_key': 'admin123'},\n", - " {'dataset_name': 'FSO_INCOME_SYNTHETIC',\n", - " 'database_type': 'PATH_DB',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/fso_income_synthetic_metadata.yaml'},\n", - " 'dataset_path': '../data/datasets/income_synthetic_data.csv'}]" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection datasets\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "0746b382-8692-445f-9ca9-0d2407a25259", - "metadata": {}, - "source": [ - "Finally let's have a look at the stored metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "c667dda0-5d0f-48c8-956c-8d8a756b7ff7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'IRIS': {'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0},\n", - " 'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0},\n", - " 'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0},\n", - " 'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0},\n", - " 'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['setosa', 'versicolor', 'virginica']}}}},\n", - " {'PENGUIN': {'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}},\n", - " {'TITANIC': {'': {'Schema': {'Table': {'max_ids': 1,\n", - " 'PassengerId': {'type': 'int', 'lower': 1},\n", - " 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3},\n", - " 'Name': {'type': 'string'},\n", - " 'Sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['male', 'female']},\n", - " 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0},\n", - " 'SibSp': {'type': 'int', 'lower': 0},\n", - " 'Parch': {'type': 'int', 'lower': 0},\n", - " 'Ticket': {'type': 'string'},\n", - " 'Fare': {'type': 'float', 'lower': 0.0},\n", - " 'Cabin': {'type': 'string'},\n", - " 'Embarked': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['C', 'Q', 'S']},\n", - " 'Survived': {'type': 'boolean'},\n", - " 'row_privacy': True}}},\n", - " 'engine': 'csv'}},\n", - " {'FSO_INCOME_SYNTHETIC': {'max_ids': 1,\n", - " 'columns': {'region': {'type': 'int'},\n", - " 'eco_branch': {'type': 'int'},\n", - " 'profession': {'type': 'int'},\n", - " 'education': {'type': 'int'},\n", - " 'age': {'type': 'int'},\n", - " 'sex': {'type': 'int'},\n", - " 'income': {'type': 'float', 'lower': 1000, 'upper': 100000}}}}]" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection metadata\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "509d0b26", - "metadata": {}, - "source": [ - "If we are interested in a specific dataset, we can also show its collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "3db07639", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'dataset_name': 'IRIS',\n", - " 'database_type': 'PATH_DB',\n", - " 'metadata': {'database_type': 'PATH_DB',\n", - " 'metadata_path': '../data/collections/metadata/iris_metadata.yaml'},\n", - " 'dataset_path': 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv'}" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_dataset --dataset IRIS\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "48550826", - "metadata": {}, - "source": [ - "And its associated metadata:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "efd9931f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'petal_length': {'type': 'float', 'lower': 0.5, 'upper': 10.0},\n", - " 'petal_width': {'type': 'float', 'lower': 0.05, 'upper': 5.0},\n", - " 'sepal_length': {'type': 'float', 'lower': 2.0, 'upper': 10.0},\n", - " 'sepal_width': {'type': 'float', 'lower': 1.0, 'upper': 6.0},\n", - " 'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['setosa', 'versicolor', 'virginica']}}}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_metadata --dataset IRIS\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "594b83a9", - "metadata": {}, - "source": [ - "We can also get list of all datasets in the 'datasets' collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "a6e21f16", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['IRIS', 'PENGUIN', 'TITANIC', 'FSO_INCOME_SYNTHETIC']" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py get_datasets\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "0e0b85d5", - "metadata": {}, - "source": [ - "## Users" - ] - }, - { - "cell_type": "markdown", - "id": "14ab18db-4b6d-4663-bde0-b5d9d3d3d2ee", - "metadata": {}, - "source": [ - "### Add user\n", - "Let's see which users are alreay loaded:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "7f450145", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "2d2ae627", - "metadata": {}, - "source": [ - "And now let's add few users." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "0f6aa33c-6bd1-4d62-ba06-3533b064340d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Mrs. Daisy with dataset IRIS, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Mrs. Daisy' --dataset 'IRIS' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "7858f019-8783-4fed-acd8-ff0107d33465", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Mr. Coldheart with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Mr. Coldheart' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "231e7d93-05ba-424a-8329-d96b0bfb4fb9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Lord McFreeze with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Lord McFreeze' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "markdown", - "id": "51b0c274-880c-44f9-9182-6cb162a54c55", - "metadata": {}, - "source": [ - "Users must all have different names, otherwise you will have an error and nothing will be done:" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "6276730e-39c2-47f1-962f-342c1acb7944", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Traceback (most recent call last):\n", - " File \"/code/mongodb_admin_cli.py\", line 461, in \n", - " function_map[args.func.__name__](args)\n", - " File \"/code/mongodb_admin_cli.py\", line 396, in \n", - " \"add_user_with_budget\": lambda args: add_user_with_budget(\n", - " ^^^^^^^^^^^^^^^^^^^^^\n", - " File \"/code/mongodb_admin.py\", line 50, in wrapper_decorator\n", - " raise ValueError(\n", - "ValueError: User Lord McFreeze already exists in user collection\n", - "\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "markdown", - "id": "49f81f7e-e086-412f-8467-89b665e5559a", - "metadata": {}, - "source": [ - "If you want to add another dataset access to an existing user, just use the function `add_dataset_to_user` command." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "82a5f498-aed1-4779-9d73-b2b71dde4ce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset IRIS to user Lord McFreeze with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_dataset_to_user --user 'Lord McFreeze' --dataset 'IRIS' --epsilon 5.0 --delta 0.005\")" - ] - }, - { - "cell_type": "markdown", - "id": "06170073-49ed-4329-8101-2debdd77eb98", - "metadata": {}, - "source": [ - "Alternatively, you can create a user without assigned dataset and then add dataset in another command." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "06839270-36cf-4de7-b93c-d143c4866bc8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added user Madame Frostina.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user --user 'Madame Frostina'\")" - ] - }, - { - "cell_type": "markdown", - "id": "df41cea4-8219-41a1-9ce3-fad5409db299", - "metadata": {}, - "source": [ - "Let's see the default parameters after the user creation:" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "1dbe0b34-ef3f-49b9-9153-dd5d09b00e4e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'user_name': 'Madame Frostina', 'may_query': True, 'datasets_list': []}" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_user --user 'Madame Frostina'\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "f4c62a55-92cb-47af-90be-80c7d13db1e6", - "metadata": {}, - "source": [ - "Let's give her access to a dataset with a budget:" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "e83378fe", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset IRIS to user Madame Frostina with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_dataset_to_user --user 'Madame Frostina' --dataset 'IRIS' --epsilon 5.0 --delta 0.005\")" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "919b2652", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to dataset PENGUIN to user Madame Frostina with budget epsilon 5.0 and delta 0.005.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_dataset_to_user --user 'Madame Frostina' --dataset 'PENGUIN' --epsilon 5.0 --delta 0.005\")" - ] - }, - { - "cell_type": "markdown", - "id": "2dab150b-4ad0-410a-b1eb-e448f8f0d79e", - "metadata": {}, - "source": [ - "Now let's see the user Madame Frostina details to check all is in order:" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "8833f27e-a342-400a-b868-facf9a44dc6f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'user_name': 'Madame Frostina',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_user --user 'Madame Frostina'\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "0bed2714", - "metadata": {}, - "source": [ - "And we can also modify existing the total budget of a user:" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "e3b75cca", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added access to user Dr. Antartica with dataset PENGUIN, budget epsilon 10.0 and delta 0.001.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_user_with_budget --user 'Dr. Antartica' --dataset 'PENGUIN' --epsilon 10.0 --delta 0.001\")" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "87eecb9c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Dr. Antartica for dataset PENGUIN of initial_epsilon to 20.0.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py set_budget_field --user 'Dr. Antartica' --dataset 'PENGUIN' --field initial_epsilon --value 20.0\")" - ] - }, - { - "cell_type": "markdown", - "id": "bbeb5dc2-e91e-4440-8df5-3e9506bf4ee1", - "metadata": {}, - "source": [ - "Let's see the current state of the database:" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "3b3f61c6-65dc-4b1e-a32e-47cdd2729ab6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Mrs. Daisy',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Mr. Coldheart',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Lord McFreeze',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Madame Frostina',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 20.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}]" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "4e0ae62f-ff80-4234-8102-4dccec0b284f", - "metadata": {}, - "source": [ - "Do not hesitate to re-run this command after every other command to ensure that everything runs as expected." - ] - }, - { - "cell_type": "markdown", - "id": "9ab1f5ba-68bd-4c96-bacd-b81dfa5d6302", - "metadata": {}, - "source": [ - "### Remove user\n", - "You have just heard that the penguin named Coldheart might have malicious intentions and decide to remove his access until an investigation has been carried out. To ensure that he is not allowed to do any more queries, run the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "7f341b3d-5a88-4fd9-8c97-cc70145834f1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set user Mr. Coldheart may query to False.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py set_may_query --user 'Mr. Coldheart' --value False\")" - ] - }, - { - "cell_type": "markdown", - "id": "4cc56586-f9a9-4e88-abed-51ba36a6e4f1", - "metadata": {}, - "source": [ - "Now, he won't be able to do any query (unless you re-run the query with --value True).\n", - "\n", - "A few days have passed and the investigation reveals that he was aiming to do unethical research, you can remove his dataset by doing:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "9153d9af-b4be-4496-9f80-d140870f60fe", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Remove access to dataset PENGUIN from user Mr. Coldheart.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py del_dataset_to_user --user 'Mr. Coldheart' --dataset 'PENGUIN'\")" - ] - }, - { - "cell_type": "markdown", - "id": "18d411ae-a211-4997-8984-81281c6275eb", - "metadata": {}, - "source": [ - "Or delete him completely from the codebase:" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "a54e89eb-1ee1-48ad-9e00-bace8516a3ef", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted user Mr. Coldheart.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py del_user --user 'Mr. Coldheart'\")" - ] - }, - { - "cell_type": "markdown", - "id": "06a7c17f-da34-472a-ad7f-3ae73a1beb7b", - "metadata": {}, - "source": [ - "Let's see the resulting users:" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "79fa414a-f097-4207-a628-19fa434a1ad3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Mrs. Daisy',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Lord McFreeze',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Madame Frostina',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 20.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}]" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "90a46a59-70ed-4a26-88cd-6ca8f1d17318", - "metadata": {}, - "source": [ - "### Change budget\n", - "You also change your mind about the budget allowed to Lord McFreeze and give him a bit more on the penguin dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "0909e6c4-141e-4d57-acd2-bdc0a2d92cea", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Lord McFreeze for dataset PENGUIN of initial_epsilon to 15.0.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_epsilon --value 15.0\")" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "c0e110fe-4297-4559-9a95-bc0ebdfa402c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set budget of Lord McFreeze for dataset PENGUIN of initial_delta to 0.005.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py set_budget_field --user 'Lord McFreeze' --dataset 'PENGUIN' --field initial_delta --value 0.005\")" - ] - }, - { - "cell_type": "markdown", - "id": "952d7ed4-ce1d-4a87-9319-6b57968ef20e", - "metadata": {}, - "source": [ - "Let's check all our changes by looking at the state of the database:" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "2ab46c5d-1553-4925-bd25-61c9c205dc95", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Mrs. Daisy',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Lord McFreeze',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 15.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Madame Frostina',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 20.0,\n", - " 'initial_delta': 0.001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}]" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "ba7cfa86", - "metadata": {}, - "source": [ - "### Finally all can be loaded fom a file direcly" - ] - }, - { - "cell_type": "markdown", - "id": "43340fc9", - "metadata": {}, - "source": [ - "Let's delete the existing user collection first:" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "597cb0b3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted collection users.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py drop_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "81661298", - "metadata": {}, - "source": [ - "Is is now empty:" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "e1638145", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\")" - ] - }, - { - "cell_type": "markdown", - "id": "20b3cd2c", - "metadata": {}, - "source": [ - "We add the data based on a yaml file:" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "87b776f2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added user data from yaml.\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "76263ebd", - "metadata": {}, - "source": [ - "By default, *add_users_via_yaml* will only add new users to the database." - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "7f597f68", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "No new users added, they already exist in the server\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "3df278ef", - "metadata": {}, - "source": [ - "If you want to clean the current users collection and replace it, you can use the argument *--clean*. " - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "5a610b9f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cleaning done. \n", - "\n", - "2024-06-05 10:00:45,678 - INFO - [mongodb_admin.py:464 - add_users_via_yaml()\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml --clean\")" - ] - }, - { - "cell_type": "markdown", - "id": "c933165a", - "metadata": {}, - "source": [ - "If you want to add new users and update the existing ones in your collection, you can use the argument *--overwrite*. This will make sure to add new users if they do not exist and replace values from existing users with the collection provided." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "fd621ac3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Existing users updated. \n", - "2024-06-05 10:00:47,300 - INFO - [mongodb_admin.py:466 - add_users_via_yaml()\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py add_users_via_yaml -yf ../data/collections/user_collection.yaml --overwrite\")" - ] - }, - { - "cell_type": "markdown", - "id": "63853e73", - "metadata": {}, - "source": [ - "And let's see the resulting collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "77866f52", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'user_name': 'Alice',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.0001,\n", - " 'total_spent_epsilon': 1.0,\n", - " 'total_spent_delta': 1e-06},\n", - " {'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 5.0,\n", - " 'initial_delta': 0.0005,\n", - " 'total_spent_epsilon': 0.2,\n", - " 'total_spent_delta': 1e-07}]},\n", - " {'user_name': 'Dr. Antartica',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'PENGUIN',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Dr. FSO',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'FSO_INCOME_SYNTHETIC',\n", - " 'initial_epsilon': 45.0,\n", - " 'initial_delta': 0.005,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Bob',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'IRIS',\n", - " 'initial_epsilon': 10.0,\n", - " 'initial_delta': 0.0001,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]},\n", - " {'user_name': 'Jack',\n", - " 'may_query': True,\n", - " 'datasets_list': [{'dataset_name': 'TITANIC',\n", - " 'initial_epsilon': 45.0,\n", - " 'initial_delta': 0.2,\n", - " 'total_spent_epsilon': 0.0,\n", - " 'total_spent_delta': 0.0}]}]" - ] - }, - "execution_count": 48, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_collection --collection users\", to_dict=True)" - ] - }, - { - "cell_type": "markdown", - "id": "b9510647", - "metadata": {}, - "source": [ - "To get a list of all users in the 'users' collection:" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "7e70e971", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[\"Alice\", \"Dr. Antartica\", \"Dr. FSO\", \"Bob\", \"Jack\"]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py get_users\")" - ] - }, - { - "cell_type": "markdown", - "id": "e559bc1e", - "metadata": {}, - "source": [ - "We can also get a list of all datasets allocated to an user:" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "81b73bd6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[\"IRIS\", \"PENGUIN\"]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py get_user_datasets --user Alice\")" - ] - }, - { - "cell_type": "markdown", - "id": "1a946132", - "metadata": {}, - "source": [ - "## Archives of queries" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "8025ef4d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "run(\"python mongodb_admin_cli.py show_archives --user Alice\")" - ] - }, - { - "cell_type": "markdown", - "id": "a27be3d3-77a2-43d3-9a7f-87c8466293fe", - "metadata": {}, - "source": [ - "## Stop the server: do not do it now !\n", - "To tear down the service, first do `ctrl+C` in the terminal where you had done `docker compose up`. Wait for the command to finish executing and then run `docker compose down`. This will also delete all the containers but the volume will stay in place. " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.0rc1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/local_deployment_notebook.html b/html/de/notebooks/local_deployment_notebook.html deleted file mode 100644 index 6eea0d36..00000000 --- a/html/de/notebooks/local_deployment_notebook.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - Local Service Deployment - How to — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Local Service Deployment - How to

-

This notebook showcases how to set up the service, add and make data available to users in a local environment. In addition, it also shows how to set up a user session for testing.

-

We use docker and docker compose files to automate the local deployment.

-
-

Docker volume and config setup

-

The first step is to create a docker volume for mongodb, which will hold all the „admin“ data of the server. Docker volumes are persistent storage spaces that are managed by docker and can be mounted in containers. To create the volume, use

-

docker volume create mongodata

-

and you should see mongodata printed to the console. This must be done only once, and we use bind mounts for the server, so no need to create volumes for that.

-

Secondly, one must adapt the yaml config files in server/configs/. The default values should be enough to start a demo version of the service though. Keep in mind that certain parameter modification require to also update the docker compose files accordingly. This is out of scope for this notebook.

-
-
-

Service start

-

In server/ you will find the docker-compose.yml file that is used for deploying the service locally. In a terminal, cd into server/and run

-

docker compose up

-

This will start the server, the mongodb as well as a user environment, each running in their own container. The first time this command is run, required containers images will automatically be built locally or downloaded from dockerhub, depending on the particular image. Note that container logs are all printed to your terminal.

-

To check that all containers are indeed running, run docker ps. You should be able to see a container for the server (lomas_server_dev), for the client (lomas_client_dev) and one for the mongo database (mongodb).

-
-
-

Server test

-

Appart from the logs in the console, we can also check the server state through our web browser. To get the server state, use the following url:

-

http://127.0.0.1/state

-

If everything went well, the page should show a few messages and the state should be „LIVE“.

-

For exploring and testing API endpoints of the server, one can also use the automatically generated Swagger docummentation page with

-

http://127.0.0.1/docs

-
-
-

User session

-

Now that we know the server is in „LIVE“ state, one can run one of our client demo notebooks from the user session. The default configuration starts a JupyterLab server at

-

http://127.0.0.1:8.8.8.8/

-

and the default password for that is „dprocks“. The root directory of the client session gives access to all the client demo notebooks, follow them for more!

-

For starting user sessions alone, there is an alternative docker-compose.yml file in the client directory of this repository. The configuration files for the user session are located in client/configs, regardless of the chosen docker compose file.

-
-
-

Service stop

-

For stopping the service, simply run

-

CTRL + c and then docker compose down

-

in your terminal. This will stop and remove all the service containers. Note that the mongodb volume will persist and must not be recreate on subsequent startups of the service.

-
-
- - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/local_deployment_notebook.ipynb b/html/de/notebooks/local_deployment_notebook.ipynb deleted file mode 100644 index 88d71e5d..00000000 --- a/html/de/notebooks/local_deployment_notebook.ipynb +++ /dev/null @@ -1,105 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "363c238d-5925-4b4b-8f68-8ad84ea4705b", - "metadata": {}, - "source": [ - "# Local Service Deployment - How to" - ] - }, - { - "cell_type": "markdown", - "id": "2db1363b-e87e-4d0e-bb3f-9af1a1b72b8d", - "metadata": {}, - "source": [ - "This notebook showcases how to set up the service, add and make data available to users in a local environment. In addition, it also shows how to set up a user session for testing.\n", - "\n", - "We use docker and docker compose files to automate the local deployment." - ] - }, - { - "cell_type": "markdown", - "id": "92f3237b-6f13-4c52-a9f2-82d94f0b7e66", - "metadata": {}, - "source": [ - "## Docker volume and config setup\n", - "The first step is to create a docker volume for mongodb, which will hold all the \"admin\" data of the server. Docker volumes are persistent storage spaces that are managed by docker and can be mounted in containers. To create the volume, use\n", - "\n", - "`docker volume create mongodata`\n", - "\n", - "and you should see `mongodata` printed to the console. This must be done only once, and we use bind mounts for the server, so no need to create volumes for that.\n", - "\n", - "Secondly, one must adapt the yaml config files in `server/configs/`. The default values should be enough to start a demo version of the service though. Keep in mind that certain parameter modification require to also update the docker compose files accordingly. This is out of scope for this notebook." - ] - }, - { - "cell_type": "markdown", - "id": "f6829afb-d822-48e4-ba49-5daf0d79db7e", - "metadata": {}, - "source": [ - "## Service start\n", - "\n", - "In `server/` you will find the `docker-compose.yml` file that is used for deploying the service locally. In a terminal, `cd` into `server/`and run\n", - "\n", - "`docker compose up`\n", - "\n", - "This will start the server, the mongodb as well as a user environment, each running in their own container. The first time this command is run, required containers images will automatically be built locally or downloaded from dockerhub, depending on the particular image. Note that container logs are all printed to your terminal.\n", - "\n", - "To check that all containers are indeed running, run `docker ps`. You should be able to see a container for the server (`lomas_server_dev`), for the client (`lomas_client_dev`) and one for the mongo database (`mongodb`).\n", - "\n", - "## Server test\n", - "\n", - "Appart from the logs in the console, we can also check the server state through our web browser. To get the server state, use the following url:\n", - "\n", - "http://127.0.0.1/state\n", - "\n", - "If everything went well, the page should show a few messages and the state should be \"LIVE\".\n", - "\n", - "For exploring and testing API endpoints of the server, one can also use the automatically generated Swagger docummentation page with \n", - "\n", - "http://127.0.0.1/docs\n", - "\n", - "## User session\n", - "\n", - "Now that we know the server is in \"LIVE\" state, one can run one of our client demo notebooks from the user session. The default configuration starts a JupyterLab server at \n", - "\n", - "http://127.0.0.1:8.8.8.8/\n", - "\n", - "and the default password for that is \"dprocks\". The root directory of the client session gives access to all the client demo notebooks, follow them for more!\n", - "\n", - "For starting user sessions alone, there is an alternative `docker-compose.yml` file in the `client` directory of this repository. The configuration files for the user session are located in `client/configs`, regardless of the chosen docker compose file.\n", - "\n", - "## Service stop\n", - "\n", - "For stopping the service, simply run \n", - "\n", - "`CTRL + c` and then `docker compose down`\n", - "\n", - "in your terminal. This will stop and remove all the service containers. Note that the mongodb volume will persist and must not be recreate on subsequent startups of the service.\n", - "\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/s3_example_notebook.html b/html/de/notebooks/s3_example_notebook.html deleted file mode 100644 index 3223087a..00000000 --- a/html/de/notebooks/s3_example_notebook.html +++ /dev/null @@ -1,892 +0,0 @@ - - - - - - - S3 example — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

S3 example

-
-

Step 1: Install the library

-

To interact with the secure server on which the data is stored, one first needs to install the library lomas-client on her local developping environment.

-

It can be installed via the pip command:

-
-
[ ]:
-
-
-
!pip install lomas-client
-
-
-
-
-
[1]:
-
-
-
from lomas_client.client import Client
-import numpy as np
-
-
-
-
-
-

Step 2: Initialise the client

-

Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server.

-

To create the client, one needs to give it a few parameters: - a url: the root application endpoint to the remote secure server. - user_name: her name as registered in the database (Jack) - dataset_name: the name of the dataset that we want to query (TITANIC)

-
-
[2]:
-
-
-
APP_URL = "http://localhost:80"
-USER_NAME = "Jack"
-DATASET_NAME = "TITANIC"
-client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)
-
-
-
-
-
-

Step 3: Understand the functionnalities of the library

-
-

Getting dataset metadata

-
-
[3]:
-
-
-
titanic_metadata = client.get_dataset_metadata()
-titanic_metadata
-
-
-
-
-
[3]:
-
-
-
-
-{'max_ids': 1,
- 'row_privacy': True,
- 'columns': {'PassengerId': {'type': 'int', 'lower': 1},
-  'Pclass': {'type': 'int', 'lower': 1, 'upper': 3},
-  'Name': {'type': 'string'},
-  'Sex': {'type': 'string',
-   'cardinality': 2,
-   'categories': ['male', 'female']},
-  'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0},
-  'SibSp': {'type': 'int', 'lower': 0},
-  'Parch': {'type': 'int', 'lower': 0},
-  'Ticket': {'type': 'string'},
-  'Fare': {'type': 'float', 'lower': 0.0},
-  'Cabin': {'type': 'string'},
-  'Embarked': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['C', 'Q', 'S']},
-  'Survived': {'type': 'boolean'}}}
-
-
-
-
-

Get a dummy dataset

-
-
[4]:
-
-
-
NB_ROWS = 200
-SEED = 0
-
-
-
-
-
[5]:
-
-
-
df_dummy = client.get_dummy_dataset(
-    nb_rows = NB_ROWS,
-    seed = SEED
-)
-
-print(df_dummy.shape)
-df_dummy.head()
-
-
-
-
-
-
-
-
-(200, 12)
-
-
-
-
[5]:
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PassengerIdPclassNameSexAgeSibSpParchTicketFareCabinEmbarkedSurvived
0273310male96.16085840784452X2421.785941hQFalse
198461Umale23.24699254056743O2503.9821292SFalse
232652Amale94.93695046119621a4833.935352cCFalse
348601qfemale94.14363382665630l399.928019pQFalse
492262Fmale79.94033866342562F6397.051061GCTrue
-
-
-
-
-

Query on dummy dataset

-
-

Average and number of rows with smartnoise-sql library on remote dummy

-
-
[6]:
-
-
-
# Average Age
-QUERY = "SELECT COUNT(*) AS nb_passengers, \
-        AVG(Age) AS avg_age \
-        FROM df"
-
-
-
-
-
[7]:
-
-
-
# On the remote server dummy dataframe
-dummy_res = client.smartnoise_query(
-    query = QUERY,
-    epsilon = 100.0, # make sure to select high values of epsilon and delta to have small differences
-    delta = 2.0,     # make sure to select high values of epsilon and delta to have small differences
-    dummy = True,
-    nb_rows = NB_ROWS,
-    seed = SEED
-)
-
-
-
-
-
[8]:
-
-
-
print(f"Average age in remote dummy: {np.round(dummy_res['query_response']['avg_age'][0], 2)} years old.")
-print(f"Number of rows in remote dummy: {np.round(dummy_res['query_response']['nb_passengers'][0], 2)}.")
-
-
-
-
-
-
-
-
-Average age in remote dummy: 49.01 years old.
-Number of rows in remote dummy: 200.
-
-
-
-
-
-

Get current budget

-
-
[9]:
-
-
-
client.get_initial_budget()
-
-
-
-
-
[9]:
-
-
-
-
-{'initial_epsilon': 45.0, 'initial_delta': 0.2}
-
-
-
-
[10]:
-
-
-
client.get_total_spent_budget()
-
-
-
-
-
[10]:
-
-
-
-
-{'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}
-
-
-

It will also be useful to know what the remaining budget is. Therefore, we call the function get_remaining_budget. It just substarcts the total spent budget from the initial budget.

-
-
[11]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[11]:
-
-
-
-
-{'remaining_epsilon': 45.0, 'remaining_delta': 0.2}
-
-
-

As expected, for now the remaining budget is equal to the inital budget.

-
-
-

Estimate cost of a query

-

Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The estimate cost function returns the estimated real cost of any query.

-

Again, of course, this will not impact the user’s budget.

-
-
[12]:
-
-
-
EPSILON = 0.5
-DELTA = 1e-4
-
-
-
-
-
[13]:
-
-
-
client.estimate_smartnoise_cost(
-    query = QUERY,
-    epsilon = EPSILON,
-    delta = DELTA
-)
-
-
-
-
-
[13]:
-
-
-
-
-{'epsilon_cost': 1.5, 'delta_cost': 0.00014999500000001387}
-
-
-
-
-

Query on real private dataset with smartnoise-sql.

-
-
[14]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[14]:
-
-
-
-
-{'remaining_epsilon': 45.0, 'remaining_delta': 0.2}
-
-
-
-
[15]:
-
-
-
response = client.smartnoise_query(
-    query = QUERY,
-    epsilon = EPSILON,
-    delta = DELTA,
-    dummy = False # Optionnal
-)
-
-
-
-
-
[ ]:
-
-
-

-
-
-
-
-
[16]:
-
-
-
nb_passengers = response['query_response']['nb_passengers'].iloc[0]
-print(f"Number of passengers in real data: {nb_passengers}.")
-
-avg_age = np.round(response['query_response']['avg_age'].iloc[0], 2)
-print(f"Average age in real data: {avg_age}.")
-
-
-
-
-
-
-
-
-Number of passengers in real data: 891.
-Average age in real data: 29.74.
-
-
-

After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:

-
-
[17]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[17]:
-
-
-
-
-{'remaining_epsilon': 43.5, 'remaining_delta': 0.199850005}
-
-
-

As can be seen in get_total_spent_budget(), it is the budget estimated with estimate_smartnoise_cost() that was spent.

-
-
[18]:
-
-
-
client.get_total_spent_budget()
-
-
-
-
-
[18]:
-
-
-
-
-{'total_spent_epsilon': 1.5, 'total_spent_delta': 0.00014999500000001387}
-
-
-
-
-
-

Step 4: Titanic statistics with opendp

-
-
[19]:
-
-
-
import opendp as dp
-import opendp.transformations as trans
-import opendp.measurements as meas
-
-
-
-
-

Confidence intervals for age over the whole population

-
-
[20]:
-
-
-
titanic_metadata
-
-
-
-
-
[20]:
-
-
-
-
-{'max_ids': 1,
- 'row_privacy': True,
- 'columns': {'PassengerId': {'type': 'int', 'lower': 1},
-  'Pclass': {'type': 'int', 'lower': 1, 'upper': 3},
-  'Name': {'type': 'string'},
-  'Sex': {'type': 'string',
-   'cardinality': 2,
-   'categories': ['male', 'female']},
-  'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0},
-  'SibSp': {'type': 'int', 'lower': 0},
-  'Parch': {'type': 'int', 'lower': 0},
-  'Ticket': {'type': 'string'},
-  'Fare': {'type': 'float', 'lower': 0.0},
-  'Cabin': {'type': 'string'},
-  'Embarked': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['C', 'Q', 'S']},
-  'Survived': {'type': 'boolean'}}}
-
-
-
-
[21]:
-
-
-
columns = ["PassengerId", "Pclass", "Name", "Sex", "Age", "SibSp", "Parch"]
-
-
-
-
-
[22]:
-
-
-
age_min = titanic_metadata['columns']['Age']['lower']
-age_max = titanic_metadata['columns']['Age']['upper']
-age_min, age_max
-
-
-
-
-
[22]:
-
-
-
-
-(0.1, 100.0)
-
-
-
-
[23]:
-
-
-
age_transformation_pipeline = (
-    trans.make_split_dataframe(separator=",", col_names=columns) >>
-    trans.make_select_column(key="Age", TOA=str) >>
-    trans.then_cast_default(TOA=float) >>
-    trans.then_clamp(bounds=(age_min, age_max)) >>
-    trans.then_resize(size=nb_passengers.tolist(), constant=avg_age) >>
-    trans.then_variance()
-)
-
-
-
-
-
[24]:
-
-
-
# Expect to fail !!!
-client.opendp_query(
-    opendp_pipeline = age_transformation_pipeline,
-    dummy=True
-)
-
-
-
-
-
-
-
-
-Server error status 400: {"InvalidQueryException":"The pipeline provided is not a measurement. It cannot be processed in this server."}
-
-
-

This is because the server will only allow measurement pipeline with differentially private results. We add Laplacian noise to the pipeline and should be able to instantiate the pipeline.

-
-
[25]:
-
-
-
var_age_transformation_pipeline = (
-    age_transformation_pipeline >>
-    meas.then_laplace(scale=5.0)
-)
-
-
-
-

Now that there is a measurement, one is able to apply the pipeline on the dummy dataset of the server.

-
-
[26]:
-
-
-
dummy_var_res = client.opendp_query(
-    opendp_pipeline = var_age_transformation_pipeline,
-    dummy=True
-)
-print(f"Dummy result for variance: {np.round(dummy_var_res['query_response'], 2)}")
-
-
-
-
-
-
-
-
-Dummy result for variance: 142.32
-
-
-

With opendp, the function estimate_opendp_cost is particularly useful to estimate the used epsilon and delta based on the scale value.

-
-
[27]:
-
-
-
cost_res = client.estimate_opendp_cost(
-    opendp_pipeline = var_age_transformation_pipeline
-)
-cost_res
-
-
-
-
-
[27]:
-
-
-
-
-{'epsilon_cost': 2.240181818190626, 'delta_cost': 0}
-
-
-

One can now execute the query on the real dataset.

-
-
[28]:
-
-
-
var_res = client.opendp_query(
-    opendp_pipeline = var_age_transformation_pipeline,
-)
-
-
-
-
-
[29]:
-
-
-
print(f"Number of passengers: {nb_passengers} (from previous smartnoise-sql query).")
-
-print(f"Average age: {np.round(avg_age, 2)} (from previous smartnoise-sql query).")
-
-var_age = var_res['query_response']
-print(f"Variance of age: {np.round(var_age, 3)} (from opendp query).")
-
-
-
-
-
-
-
-
-Number of passengers: 891 (from previous smartnoise-sql query).
-Average age: 29.74 (from previous smartnoise-sql query).
-Variance of age: 0.393 (from opendp query).
-
-
-
-
[30]:
-
-
-
# Get standard error
-standard_error = np.sqrt(var_age/nb_passengers)
-print(f"Standard error of age: {np.round(standard_error, 2)}.")
-
-
-
-
-
-
-
-
-Standard error of age: 0.02.
-
-
-
-
[31]:
-
-
-
 # Compute the 95% confidence interval
-ZSCORE = 1.96
-lower_bound = np.round(avg_age - ZSCORE*standard_error, 2)
-upper_bound = np.round(avg_age + ZSCORE*standard_error, 2)
-print(f"The 95% confidence interval of the age of all passengers is [{lower_bound}, {upper_bound}].")
-
-
-
-
-
-
-
-
-The 95% confidence interval of the age of all passengers is [29.7, 29.78].
-
-
-
-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/s3_example_notebook.ipynb b/html/de/notebooks/s3_example_notebook.ipynb deleted file mode 100644 index 4dcba100..00000000 --- a/html/de/notebooks/s3_example_notebook.ipynb +++ /dev/null @@ -1,968 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3f18d338", - "metadata": {}, - "source": [ - "# S3 example" - ] - }, - { - "cell_type": "markdown", - "id": "01ae30d2", - "metadata": {}, - "source": [ - "## Step 1: Install the library\n", - "To interact with the secure server on which the data is stored, one first needs to install the library `lomas-client` on her local developping environment. \n", - "\n", - "It can be installed via the pip command:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28fbdd79-8c15-49a9-bcf9-fcdeac09d2b5", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install lomas-client" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "6fb569fc", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client.client import Client\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "9c63718b", - "metadata": {}, - "source": [ - "## Step 2: Initialise the client\n", - "\n", - "Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server. \n", - "\n", - "To create the client, one needs to give it a few parameters:\n", - "- a url: the root application endpoint to the remote secure server.\n", - "- user_name: her name as registered in the database (Jack)\n", - "- dataset_name: the name of the dataset that we want to query (TITANIC)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "941991f7", - "metadata": {}, - "outputs": [], - "source": [ - "APP_URL = \"http://localhost:80\"\n", - "USER_NAME = \"Jack\"\n", - "DATASET_NAME = \"TITANIC\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "markdown", - "id": "9b9a5f13", - "metadata": {}, - "source": [ - "## Step 3: Understand the functionnalities of the library" - ] - }, - { - "cell_type": "markdown", - "id": "c7cb5531", - "metadata": {}, - "source": [ - "### Getting dataset metadata" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "d15cbe39", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'PassengerId': {'type': 'int', 'lower': 1},\n", - " 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3},\n", - " 'Name': {'type': 'string'},\n", - " 'Sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['male', 'female']},\n", - " 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0},\n", - " 'SibSp': {'type': 'int', 'lower': 0},\n", - " 'Parch': {'type': 'int', 'lower': 0},\n", - " 'Ticket': {'type': 'string'},\n", - " 'Fare': {'type': 'float', 'lower': 0.0},\n", - " 'Cabin': {'type': 'string'},\n", - " 'Embarked': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['C', 'Q', 'S']},\n", - " 'Survived': {'type': 'boolean'}}}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "titanic_metadata = client.get_dataset_metadata()\n", - "titanic_metadata" - ] - }, - { - "cell_type": "markdown", - "id": "5a3c899d", - "metadata": {}, - "source": [ - "### Get a dummy dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "01f4365a", - "metadata": {}, - "outputs": [], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "3f553b29", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 12)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
PassengerIdPclassNameSexAgeSibSpParchTicketFareCabinEmbarkedSurvived
0273310male96.16085840784452X2421.785941hQFalse
198461Umale23.24699254056743O2503.9821292SFalse
232652Amale94.93695046119621a4833.935352cCFalse
348601qfemale94.14363382665630l399.928019pQFalse
492262Fmale79.94033866342562F6397.051061GCTrue
\n", - "
" - ], - "text/plain": [ - " PassengerId Pclass Name Sex Age SibSp Parch Ticket \n", - "0 2733 1 0 male 96.160858 4078 4452 X \\\n", - "1 9846 1 U male 23.246992 5405 6743 O \n", - "2 3265 2 A male 94.936950 4611 9621 a \n", - "3 4860 1 q female 94.143633 8266 5630 l \n", - "4 9226 2 F male 79.940338 6634 2562 F \n", - "\n", - " Fare Cabin Embarked Survived \n", - "0 2421.785941 h Q False \n", - "1 2503.982129 2 S False \n", - "2 4833.935352 c C False \n", - "3 399.928019 p Q False \n", - "4 6397.051061 G C True " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset(\n", - " nb_rows = NB_ROWS, \n", - " seed = SEED\n", - ")\n", - "\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "98e6fda2-dde7-4f8b-a787-c9a1e3571ebe", - "metadata": {}, - "source": [ - "### Query on dummy dataset" - ] - }, - { - "cell_type": "markdown", - "id": "243c73e3-daec-45d6-a3c8-ae1d60439ec4", - "metadata": {}, - "source": [ - "#### Average and number of rows with smartnoise-sql library on remote dummy" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3946425d", - "metadata": {}, - "outputs": [], - "source": [ - "# Average Age\n", - "QUERY = \"SELECT COUNT(*) AS nb_passengers, \\\n", - " AVG(Age) AS avg_age \\\n", - " FROM df\"" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "90cf2a6d", - "metadata": {}, - "outputs": [], - "source": [ - "# On the remote server dummy dataframe\n", - "dummy_res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0, # make sure to select high values of epsilon and delta to have small differences\n", - " delta = 2.0, # make sure to select high values of epsilon and delta to have small differences\n", - " dummy = True, \n", - " nb_rows = NB_ROWS,\n", - " seed = SEED\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "a30f277e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average age in remote dummy: 49.01 years old.\n", - "Number of rows in remote dummy: 200.\n" - ] - } - ], - "source": [ - "print(f\"Average age in remote dummy: {np.round(dummy_res['query_response']['avg_age'][0], 2)} years old.\")\n", - "print(f\"Number of rows in remote dummy: {np.round(dummy_res['query_response']['nb_passengers'][0], 2)}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "324454ed", - "metadata": {}, - "source": [ - "### Get current budget" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "61a467f3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'initial_epsilon': 45.0, 'initial_delta': 0.2}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_initial_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "afd22f84", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "05daf5a4", - "metadata": {}, - "source": [ - "It will also be useful to know what the remaining budget is. Therefore, we call the function `get_remaining_budget`. It just substarcts the total spent budget from the initial budget." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "6260cf54", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 45.0, 'remaining_delta': 0.2}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "20298e00", - "metadata": {}, - "source": [ - "As expected, for now the remaining budget is equal to the inital budget." - ] - }, - { - "cell_type": "markdown", - "id": "b746374c", - "metadata": {}, - "source": [ - "### Estimate cost of a query\n", - "Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The `estimate cost` function returns the estimated real cost of any query.\n", - "\n", - "Again, of course, this will not impact the user's budget." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "fd5ed08a", - "metadata": {}, - "outputs": [], - "source": [ - "EPSILON = 0.5\n", - "DELTA = 1e-4" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "133020c6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 1.5, 'delta_cost': 0.00014999500000001387}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "e5379edf", - "metadata": {}, - "source": [ - "### Query on real private dataset with smartnoise-sql." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "19e60263", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 45.0, 'remaining_delta': 0.2}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "69767fac", - "metadata": {}, - "outputs": [], - "source": [ - "response = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA,\n", - " dummy = False # Optionnal\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a7a4e2d3-2922-4f95-bdc9-a35c160f157c", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "6dbbdf93", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of passengers in real data: 891.\n", - "Average age in real data: 29.74.\n" - ] - } - ], - "source": [ - "nb_passengers = response['query_response']['nb_passengers'].iloc[0]\n", - "print(f\"Number of passengers in real data: {nb_passengers}.\")\n", - "\n", - "avg_age = np.round(response['query_response']['avg_age'].iloc[0], 2)\n", - "print(f\"Average age in real data: {avg_age}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b2767e65", - "metadata": {}, - "source": [ - "After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "39701fe5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 43.5, 'remaining_delta': 0.199850005}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "e37c587f", - "metadata": {}, - "source": [ - "As can be seen in `get_total_spent_budget()`, it is the budget estimated with `estimate_smartnoise_cost()` that was spent." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "487f835f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 1.5, 'total_spent_delta': 0.00014999500000001387}" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "04929993", - "metadata": {}, - "source": [ - "## Step 4: Titanic statistics with opendp" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "b9685226", - "metadata": {}, - "outputs": [], - "source": [ - "import opendp as dp\n", - "import opendp.transformations as trans\n", - "import opendp.measurements as meas" - ] - }, - { - "cell_type": "markdown", - "id": "bbbca191", - "metadata": {}, - "source": [ - "### Confidence intervals for age over the whole population" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "4331d86f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'columns': {'PassengerId': {'type': 'int', 'lower': 1},\n", - " 'Pclass': {'type': 'int', 'lower': 1, 'upper': 3},\n", - " 'Name': {'type': 'string'},\n", - " 'Sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['male', 'female']},\n", - " 'Age': {'type': 'float', 'lower': 0.1, 'upper': 100.0},\n", - " 'SibSp': {'type': 'int', 'lower': 0},\n", - " 'Parch': {'type': 'int', 'lower': 0},\n", - " 'Ticket': {'type': 'string'},\n", - " 'Fare': {'type': 'float', 'lower': 0.0},\n", - " 'Cabin': {'type': 'string'},\n", - " 'Embarked': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['C', 'Q', 'S']},\n", - " 'Survived': {'type': 'boolean'}}}" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "titanic_metadata" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "ff8cb7b6", - "metadata": {}, - "outputs": [], - "source": [ - "columns = [\"PassengerId\", \"Pclass\", \"Name\", \"Sex\", \"Age\", \"SibSp\", \"Parch\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "70b2bdb1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(0.1, 100.0)" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "age_min = titanic_metadata['columns']['Age']['lower']\n", - "age_max = titanic_metadata['columns']['Age']['upper']\n", - "age_min, age_max" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "75e4933b", - "metadata": {}, - "outputs": [], - "source": [ - "age_transformation_pipeline = (\n", - " trans.make_split_dataframe(separator=\",\", col_names=columns) >>\n", - " trans.make_select_column(key=\"Age\", TOA=str) >>\n", - " trans.then_cast_default(TOA=float) >>\n", - " trans.then_clamp(bounds=(age_min, age_max)) >>\n", - " trans.then_resize(size=nb_passengers.tolist(), constant=avg_age) >>\n", - " trans.then_variance()\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "8041a647", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Server error status 400: {\"InvalidQueryException\":\"The pipeline provided is not a measurement. It cannot be processed in this server.\"}\n" - ] - } - ], - "source": [ - "# Expect to fail !!!\n", - "client.opendp_query(\n", - " opendp_pipeline = age_transformation_pipeline,\n", - " dummy=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d06c59dc", - "metadata": {}, - "source": [ - "This is because the server will only allow measurement pipeline with differentially private results. We add Laplacian noise to the pipeline and should be able to instantiate the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "b8162859", - "metadata": {}, - "outputs": [], - "source": [ - "var_age_transformation_pipeline = (\n", - " age_transformation_pipeline >>\n", - " meas.then_laplace(scale=5.0)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "fc7e0ecd", - "metadata": {}, - "source": [ - "Now that there is a measurement, one is able to apply the pipeline on the dummy dataset of the server." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "df61bce0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dummy result for variance: 142.32\n" - ] - } - ], - "source": [ - "dummy_var_res = client.opendp_query(\n", - " opendp_pipeline = var_age_transformation_pipeline, \n", - " dummy=True\n", - ")\n", - "print(f\"Dummy result for variance: {np.round(dummy_var_res['query_response'], 2)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "ded11ac4", - "metadata": {}, - "source": [ - "With opendp, the function `estimate_opendp_cost` is particularly useful to estimate the used `epsilon` and `delta` based on the `scale` value." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "7ae7f735", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 2.240181818190626, 'delta_cost': 0}" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_res = client.estimate_opendp_cost(\n", - " opendp_pipeline = var_age_transformation_pipeline\n", - ")\n", - "cost_res" - ] - }, - { - "cell_type": "markdown", - "id": "1c791d36", - "metadata": {}, - "source": [ - "One can now execute the query on the real dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "085555a5", - "metadata": {}, - "outputs": [], - "source": [ - "var_res = client.opendp_query(\n", - " opendp_pipeline = var_age_transformation_pipeline, \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "674332e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of passengers: 891 (from previous smartnoise-sql query).\n", - "Average age: 29.74 (from previous smartnoise-sql query).\n", - "Variance of age: 0.393 (from opendp query).\n" - ] - } - ], - "source": [ - "print(f\"Number of passengers: {nb_passengers} (from previous smartnoise-sql query).\")\n", - "\n", - "print(f\"Average age: {np.round(avg_age, 2)} (from previous smartnoise-sql query).\")\n", - "\n", - "var_age = var_res['query_response']\n", - "print(f\"Variance of age: {np.round(var_age, 3)} (from opendp query).\")" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "f72b19d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Standard error of age: 0.02.\n" - ] - } - ], - "source": [ - "# Get standard error\n", - "standard_error = np.sqrt(var_age/nb_passengers)\n", - "print(f\"Standard error of age: {np.round(standard_error, 2)}.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "62630a03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The 95% confidence interval of the age of all passengers is [29.7, 29.78].\n" - ] - } - ], - "source": [ - " # Compute the 95% confidence interval\n", - "ZSCORE = 1.96\n", - "lower_bound = np.round(avg_age - ZSCORE*standard_error, 2)\n", - "upper_bound = np.round(avg_age + ZSCORE*standard_error, 2)\n", - "print(f\"The 95% confidence interval of the age of all passengers is [{lower_bound}, {upper_bound}].\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.0rc1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/notebooks/smartnoise_client_notebook.html b/html/de/notebooks/smartnoise_client_notebook.html deleted file mode 100644 index 4c93a1b1..00000000 --- a/html/de/notebooks/smartnoise_client_notebook.html +++ /dev/null @@ -1,1323 +0,0 @@ - - - - - - - Secure Data Disclosure: Client side — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Secure Data Disclosure: Client side

-

This notebook showcases how researcher could use the Secure Data Disclosure system. It explains the different functionnalities provided by the dpserial client library to interact with the secure server.

-

The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.

-

Each user has access to one or multiple projects and for each dataset has a limited budget \(\epsilon\), \(\delta\).

-

🐧🐧🐧 In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.

-

Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.

-

This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library ofs_dpserial. 🐧🐧🐧

-
-

Step 1: Install the library

-

To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library fso_dpserial on her local developping environment.

-

It can be installed via the pip command:

-
-
[1]:
-
-
-
from lomas_client.client import Client
-import numpy as np
-
-
-
-
-
-

Step 2: Initialise the client

-

Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server.

-

To create the client, Dr. Antartica needs to give it a few parameters: - a url: the root application endpoint to the remote secure server. - user_name: her name as registered in the database (Dr. Alice Antartica) - dataset_name: the name of the dataset that she wants to query (PENGUIN)

-

She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit. (As is done in the Secure Data Disclosure Notebook: Server side).

-
-
[2]:
-
-
-
APP_URL = "http://lomas_server_dev:80"
-USER_NAME = "Dr. Antartica"
-DATASET_NAME = "PENGUIN"
-client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)
-
-
-
-

And that’s it for the preparation. She is now ready to use the various functionnalities offered by fso_dpserial.

-
-
-

Step 3: Understand the functionnalities of the library

-
-

Getting dataset metadata

-

Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the get_dataset_metadata() function of the client. As this is public information, this does not cost any budget.

-

This function returns metadata information in the same format as SmartnoiseSQL dictionary format, where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details).

-
-
[3]:
-
-
-
metadata = client.get_dataset_metadata()
-metadata
-
-
-
-
-
[3]:
-
-
-
-
-{'max_ids': 1,
- 'row_privacy': True,
- 'censor_dims': False,
- 'columns': {'species': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Adelie', 'Chinstrap', 'Gentoo']},
-  'island': {'type': 'string',
-   'cardinality': 3,
-   'categories': ['Torgersen', 'Biscoe', 'Dream']},
-  'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},
-  'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},
-  'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},
-  'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},
-  'sex': {'type': 'string',
-   'cardinality': 2,
-   'categories': ['MALE', 'FEMALE']}}}
-
-
-

Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds. She also knows based on the field max_ids: 1 that each penguin can only be once in the dataset and on the field row_privacy: True that each row represents a single penguin.

-
-
-

Get a dummy dataset

-

Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset.

-

Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets. Getting a dummy dataset does not affect the budget as there is no differential privacy here, it is not a synthetic dataset and all that could be learn here is already present in the public metadata.

-

Dr. Antartica first create a dummy dataset with the default options.

-
-
[4]:
-
-
-
df_dummy = client.get_dummy_dataset()
-print(df_dummy.shape)
-df_dummy.head()
-
-
-
-
-
-
-
-
-(100, 7)
-
-
-
-
[4]:
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0ChinstrapTorgersen43.10890413.314292214.2031652258.408606FEMALE
1AdelieDream63.27500119.364104158.4139964656.773158FEMALE
2AdelieDream55.61978816.143560166.1628714703.175608FEMALE
3AdelieBiscoe50.95304718.085707239.8554195187.149507MALE
4GentooTorgersen35.46065222.075665210.6429065630.456669MALE
-
-
-

However, she would prefer to have a dataset with 200 rows and chooses a seed of 0, hence:

-
-
[5]:
-
-
-
NB_ROWS = 200
-SEED = 0
-
-
-
-
-
[6]:
-
-
-
df_dummy = client.get_dummy_dataset(nb_rows = NB_ROWS, seed = SEED)
-print(df_dummy.shape)
-df_dummy.head()
-
-
-
-
-
-
-
-
-(200, 7)
-
-
-
-
[6]:
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0GentooBiscoe49.20847316.117959190.1259502873.291927FEMALE
1GentooTorgersen55.03162819.963435242.9291423639.940005FEMALE
2ChinstrapTorgersen51.09671816.777518159.9614935401.743330MALE
3AdelieBiscoe49.07091114.796037244.5301532316.038092MALE
4ChinstrapBiscoe44.82791813.246787236.9488535036.246870FEMALE
-
-
-
-
-

Query dummy dataset

-

Now that she has an idea of what the data looks like, she wants to start querying the real dataset to for her research. However, before this other tools are at her disposal to reduce potential error risks and avoid spending budget on irrelevant queries. Of course, this does not have any impact on the budget.

-

It is possible to specify the flag dummy=True in the various queries to perform the query on the dummy dataset instead of the real dataset and ensure that the queries are doing what is expected of them.

-

Therefore Dr. Antartica computes the results that she gets on the dummy dataframe that she created locally and on the same dummy dataframe in the server via a query and compare them to ensure that the query is well defined and works within the server.

-

She tests with an example on the average bill length on the dataframe.

-
-
[7]:
-
-
-
# On the local dummy dataframe
-result_local_dummy = round(df_dummy['bill_length_mm'].mean(), 5)
-result_local_dummy
-
-
-
-
-
[7]:
-
-
-
-
-47.51532
-
-
-

As the query on the server goes through the same workflow for dummies and real data, she still has to set values for theoratical budget to spend on the dummy query. Of course, this theoretical budget will NOT affect her real budget as this is on dummy data.

-

It is recommended to use very high values on the budget parameters here to have little noise and small difference between the exact local result and the ‚little noisy‘ server result.

-

Also, make sure to use the same values of number of rows and seed to have the same dummy datasets.

-
-
[8]:
-
-
-
# Average bill length in mm
-QUERY = "SELECT AVG(bill_length_mm) AS avg_bill_length_mm FROM df"
-
-
-
-
-
[9]:
-
-
-
# On the remote server dummy dataframe
-res = client.smartnoise_query(
-    query = QUERY,
-    epsilon = 100.0, # make sure to select high values of epsilon and delta to have small differences
-    delta = 2.0,    # make sure to select high values of epsilon and delta to have small differences
-    dummy = True,
-    nb_rows = NB_ROWS,
-    seed = SEED
-)
-res_server_dummy = res['query_response']["avg_bill_length_mm"][0]
-res_server_dummy
-
-
-
-
-
[9]:
-
-
-
-
-47.50783673123655
-
-
-

She then checks that the responses on the dummy locally and the dummy on the server are close enough (difference would be only due to small noise addition).

-
-
[10]:
-
-
-
np.testing.assert_almost_equal(
-    result_local_dummy,
-    res_server_dummy,
-    decimal=2,
-    err_msg="Responses are different, either try with a bigger budget or query is not doing what is intended."
-)
-
-
-
-

As you can see res_local and res_server are close. We can accept that the small difference is due to the small noise added due to the large values of \(\epsilon\) and \(\delta\).

-
-
-

Get current budget

-

It is the first time that Dr. Antartica connects to the server and she wants to know how much buget the queen assigned her. Therefore, she calls the fonction get_initial_budget.

-
-
[11]:
-
-
-
client.get_initial_budget()
-
-
-
-
-
[11]:
-
-
-
-
-{'initial_epsilon': 10, 'initial_delta': 0.005}
-
-
-

She sees that she has 10.0 epsilon and 0.0004 epsilon at her disposal.

-

Then she checks her total spent budget get_total_spent_budget. As she only did queries on metadata on dummy dataframes, this should still be 0.

-
-
[12]:
-
-
-
client.get_total_spent_budget()
-
-
-
-
-
[12]:
-
-
-
-
-{'total_spent_epsilon': 0, 'total_spent_delta': 0}
-
-
-

It will also be useful to know what the remaining budget is. Therefore, she calls the function get_remaining_budget. It just substarcts the total spent budget from the initial budget.

-
-
[13]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[13]:
-
-
-
-
-{'remaining_epsilon': 10, 'remaining_delta': 0.005}
-
-
-

As expected, for now the remaining budget is equal to the inital budget.

-
-
-

Estimate cost of a query

-

Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The estimate cost function returns the estimated real cost of any query.

-

Again, of course, this will not impact the user’s budget.

-

Dr. Antartica checks the budget that computing the average bill length will really cost her if she asks the query with an epsilon and a delta.

-
-
[14]:
-
-
-
client.estimate_smartnoise_cost(
-    query = QUERY,
-    epsilon = 1.0,
-    delta = 1e-4
-)
-
-
-
-
-
[14]:
-
-
-
-
-{'epsilon_cost': 2.0, 'delta_cost': 4.999999999999449e-05}
-
-
-

So this query would actually cost her 3.0 epsilon and a little 1.499e-4 delta. As she does not want to spend to much budget here she tries other values of budget.

-
-
[15]:
-
-
-
client.estimate_smartnoise_cost(
-    query = QUERY,
-    epsilon = 0.2,
-    delta = 1e-5
-)
-
-
-
-
-
[15]:
-
-
-
-
-{'epsilon_cost': 0.4, 'delta_cost': 5.000000000032756e-06}
-
-
-

This query would actually cost her 0.6 epsilon and a similar delta. She decides that it is good enough.

-
-
[16]:
-
-
-
EPSILON = 0.2
-DELTA = 1e-5
-
-
-
-
-
-

Query real dataset

-

Now that all the safeguard functions were tested, Dr. Antartica is ready to query on the real dataset and get a differentially private response of the average bill length. By default, the flag dummy is False so setting it is optional. She uses the values of epsilon and delta that she selected just before.

-

Careful: This command DOES spend the budget of the user and the remaining budget is updated for every query.

-
-
[17]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[17]:
-
-
-
-
-{'remaining_epsilon': 10, 'remaining_delta': 0.005}
-
-
-
-
[18]:
-
-
-
avg_bill_length_response = client.smartnoise_query(
-    query = QUERY,
-    epsilon = EPSILON,
-    delta = DELTA,
-    dummy = False
-)
-
-
-
-
-
[19]:
-
-
-
avg_bill_length = avg_bill_length_response['query_response']['avg_bill_length_mm'].iloc[0]
-print(f"Average bill length: {np.round(avg_bill_length, 2)}mm.")
-
-
-
-
-
-
-
-
-Average bill length: 44.34mm.
-
-
-

After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:

-
-
[20]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[20]:
-
-
-
-
-{'remaining_epsilon': 9.6, 'remaining_delta': 0.004994999999999967}
-
-
-

As can be seen in get_total_spent_budget(), it is the budget estimated with estimate_cost() that was spent.

-
-
[21]:
-
-
-
client.get_total_spent_budget()
-
-
-
-
-
[21]:
-
-
-
-
-{'total_spent_epsilon': 0.4, 'total_spent_delta': 5.000000000032756e-06}
-
-
-

Dr. Antartica has now a differentially private estimation of the bill length of all birds and is confident to use the library for the rest of her analyses.

-
-
-
-

Step 4: Penguin statistics

-
-

Confidence intervals for flipper length over the whole population

-

She is first interested to have a better idea of the distribution of flipper length of all species. She already has the mean from step 3, so she just need to compute the standard deviation and know the number of penguins in the dataset. As it is just an exploration step, she uses very little budget values.

-
-
[22]:
-
-
-
QUERY = "SELECT COUNT(bill_length_mm) AS nb_penguin, STD(bill_length_mm) AS std_bill_length_mm FROM df"
-
-
-
-

She again first verifies that her query works on the dummy dataset:

-
-
[23]:
-
-
-
dummy_res = client.smartnoise_query(
-    query = QUERY,
-    epsilon = 100.0,
-    delta = 10.0,
-    dummy = True
-)
-dummy_res['query_response']
-
-
-
-
-
[23]:
-
-
-
-
- - - - - - - - - - - - - - - - -
nb_penguinstd_bill_length_mm
09910.291426
-
-
-

The syntax of the query works, now she checks the budget:

-
-
[24]:
-
-
-
client.estimate_smartnoise_cost(
-    query = QUERY,
-    epsilon = 0.5,
-    delta = 1e-5
-)
-
-
-
-
-
[24]:
-
-
-
-
-{'epsilon_cost': 1.5, 'delta_cost': 5.000000000032756e-06}
-
-
-

It is a bit too much, she decides to test for less:

-
-
[25]:
-
-
-
client.estimate_smartnoise_cost(
-    query = QUERY,
-    epsilon = 0.25,
-    delta = 1e-5
-)
-
-
-
-
-
[25]:
-
-
-
-
-{'epsilon_cost': 0.75, 'delta_cost': 5.000000000032756e-06}
-
-
-

That’s fine, she is ready to query:

-
-
[26]:
-
-
-
response = client.smartnoise_query(query = QUERY, epsilon = 0.25, delta = 1e-5)
-response = response['query_response']
-response
-
-
-
-
-
[26]:
-
-
-
-
- - - - - - - - - - - - - - - - -
nb_penguinstd_bill_length_mm
03379.193759
-
-
-
-
[27]:
-
-
-
nb_penguin = response['nb_penguin'].iloc[0]
-print(f"Number of penguins: {nb_penguin}.")
-
-std_bill_length = response['std_bill_length_mm'].iloc[0]
-print(f"Standard deviation of bill length: {np.round(std_bill_length, 2)}.")
-
-
-
-
-
-
-
-
-Number of penguins: 337.
-Standard deviation of bill length: 9.19.
-
-
-

She can now do all the postprocessing that she wants with the returned data without adding any privacy risk.

-
-
[28]:
-
-
-
# Get standard error
-standard_error = std_bill_length/np.sqrt(nb_penguin)
-print(f"Standard error of bill length: {np.round(standard_error, 2)}.")
-
-
-
-
-
-
-
-
-Standard error of bill length: 0.5.
-
-
-
-
[29]:
-
-
-
 # Compute the 95% confidence interval
-ZSCORE = 1.96
-lower_bound, upper_bound = avg_bill_length - ZSCORE*standard_error, avg_bill_length + ZSCORE*standard_error
-print(f"The 95% confidence interval of the bill length of all penguins is [{np.round(lower_bound, 2)}, {np.round(upper_bound, 2)}].")
-
-
-
-
-
-
-
-
-The 95% confidence interval of the bill length of all penguins is [43.36, 45.32].
-
-
-
-
-

Hypothesis testing

-

So, Dr. Antartica has now the opportunity to study the various penguins dimensions and will do an hypotheses testing analysis to discover if flipper length differ between the penguins species.

-

She will do a two-tailed two-sample \(t\) test.

-
    -
  • The null hypothese \(H_0\) is flipper length does not differ between species.

  • -
  • The alternative hypothesis \(H_a\) is flipper length does differ between species.

  • -
-

She set the level of significance at 0.05.

-
-
[30]:
-
-
-
CRITICAL_VALUE = 0.05
-
-
-
-
-
[31]:
-
-
-
QUERY = "SELECT \
-        species AS species, \
-        COUNT(bill_length_mm) AS nb_penguin,  \
-        AVG(bill_length_mm) AS avg_bill_length_mm, \
-        STD(bill_length_mm) AS std_bill_length_mm \
-        FROM df  GROUP BY species"
-
-
-
-

She estimates how much budget it would really cost:

-
-
[32]:
-
-
-
client.estimate_smartnoise_cost(query = QUERY, epsilon = 1, delta = 1e-4)
-
-
-
-
-
[32]:
-
-
-
-
-{'epsilon_cost': 3.0, 'delta_cost': 4.999999999999449e-05}
-
-
-

The real cost seems to be 3 times the epsilon that she sets. It is a lot but she tries on the dummy dataset to verify all is working properly.

-
-
[33]:
-
-
-
dummy_res = client.smartnoise_query(query = QUERY, epsilon = 1, delta = 1.0, dummy = True)
-dummy_res
-
-
-
-
-
[33]:
-
-
-
-
-{'query_response':      species  nb_penguin  avg_bill_length_mm  std_bill_length_mm
- 0     Adelie          36           48.807157            8.785415
- 1  Chinstrap          32           46.193166           18.700943
- 2     Gentoo          30           41.849626            6.128719}
-
-
-

She did not give enough budget for the query to work. This is why there are ‚NANs‘ in the output. She has to spend more budget for the query to work.

-
-
[34]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[34]:
-
-
-
-
-{'remaining_epsilon': 8.85, 'remaining_delta': 0.004989999999999935}
-
-
-

The maximum she can do with all her remaining budget of 7.4 is around 7.4/4 = 1.85. Let’s check:

-
-
[45]:
-
-
-
client.estimate_smartnoise_cost(query = QUERY, epsilon = 12/4, delta = 1e-4)
-
-
-
-
-
[45]:
-
-
-
-
-{'epsilon_cost': 9.0, 'delta_cost': 4.999999999999449e-05}
-
-
-
-
[46]:
-
-
-
dummy_res = client.smartnoise_query(query = QUERY, epsilon = 12/4, delta = 1e-4, dummy = True)
-dummy_res
-
-
-
-
-
[46]:
-
-
-
-
-{'query_response':      species  nb_penguin  avg_bill_length_mm  std_bill_length_mm
- 0     Adelie          37           48.580124            2.255215
- 1  Chinstrap          33           46.983725            9.051034
- 2     Gentoo          28           43.886555           10.928323}
-
-
-

If it errors, she might need to re-run the query a few times until it works. The budget is not affected by dummy queries anyway.

-
-
[47]:
-
-
-
flipper_length_response = client.smartnoise_query(query = QUERY, epsilon = 12/4, delta = 1e-4)
-
-
-
-

And now she should not have any remaining budget:

-
-
[48]:
-
-
-
client.get_remaining_budget()
-
-
-
-
-
[48]:
-
-
-
-
-{'remaining_epsilon': 1.0, 'remaining_delta': 0.004950000000000006}
-
-
-

But she can do her post-processing and hypothesis analysis.

-
-
[49]:
-
-
-
df_flipper = flipper_length_response['query_response']
-df_flipper
-
-
-
-
-
[49]:
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
speciesnb_penguinavg_bill_length_mmstd_bill_length_mm
0Adelie15038.9029430.805783
1Chinstrap6749.2625606.123036
2Gentoo12247.4695632.851064
-
-
-

And finally the \(t\)-test:

-
-
[50]:
-
-
-
def t_test(avg_1, avg_2, std_1, std_2, nb_1, nb_2):
-    standard_error = (std_1 * (nb_1 - 1) + std_2 * (nb_2 - 1))/(nb_1 + nb_2 - 2)
-    return (avg_1 - avg_2)/np.sqrt(standard_error**2*(1/nb_1 + 1 /nb_2))
-
-
-
-
-
[51]:
-
-
-
nb_0, avg_0, std_0 = df_flipper[['nb_penguin', 'avg_bill_length_mm', 'std_bill_length_mm']].iloc[0]
-nb_1, avg_1, std_1 = df_flipper[['nb_penguin', 'avg_bill_length_mm', 'std_bill_length_mm']].iloc[1]
-nb_2, avg_2, std_2 = df_flipper[['nb_penguin', 'avg_bill_length_mm', 'std_bill_length_mm']].iloc[2]
-
-
-
-
-
[52]:
-
-
-
t_01 = t_test(avg_0, avg_1, std_0, std_1, nb_0, nb_1)
-t_02 = t_test(avg_0, avg_2, std_0, std_2, nb_0, nb_2)
-t_12 = t_test(avg_1, avg_2, std_1, std_2, nb_1, nb_2)
-
-print(f"T test between specie 0 and specie 1: {np.round(t_01, 2)}.  Reject null hypothesis: {abs(t_01) > CRITICAL_VALUE}.")
-print(f"T test between specie 0 and specie 2: {np.round(t_02, 2)}. Reject null hypothesis: {abs(t_02) > CRITICAL_VALUE}.")
-print(f"T test between specie 1 and specie 2: {np.round(t_12, 2)}.   Reject null hypothesis: {abs(t_12) > CRITICAL_VALUE}.")
-
-
-
-
-
-
-
-
-T test between specie 0 and specie 1: -28.92.  Reject null hypothesis: True.
-T test between specie 0 and specie 2: -40.8. Reject null hypothesis: True.
-T test between specie 1 and specie 2: 2.94.   Reject null hypothesis: True.
-
-
-

All t-tests are above the critical value of 0.5. She can reject the null hypothesis.

-

She finally computes the confidence intervals for the flipper length of each species

-
-
[53]:
-
-
-
ZSCORE = 1.96
-df_flipper['standard_error'] = df_flipper['std_bill_length_mm']/np.sqrt(df_flipper['nb_penguin'])
-df_flipper['ci_95_lower_bound'] = df_flipper['avg_bill_length_mm'] - ZSCORE * df_flipper['standard_error']
-df_flipper['ci_95_upper_bound'] = df_flipper['avg_bill_length_mm'] + ZSCORE * df_flipper['standard_error']
-df_flipper
-
-
-
-
-
[53]:
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
speciesnb_penguinavg_bill_length_mmstd_bill_length_mmstandard_errorci_95_lower_boundci_95_upper_bound
0Adelie15038.9029430.8057830.06579238.77399139.031895
1Chinstrap6749.2625606.1230360.74804847.79638650.728733
2Gentoo12247.4695632.8510640.25812346.96364247.975485
-
-
-

She can now go and present her findings to queen Icebergina.

-
-
[ ]:
-
-
-

-
-
-
-
-
-
- - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/notebooks/smartnoise_client_notebook.ipynb b/html/de/notebooks/smartnoise_client_notebook.ipynb deleted file mode 100644 index e317a8a0..00000000 --- a/html/de/notebooks/smartnoise_client_notebook.ipynb +++ /dev/null @@ -1,1730 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3f18d338", - "metadata": {}, - "source": [ - "# Secure Data Disclosure: Client side" - ] - }, - { - "cell_type": "markdown", - "id": "1582a2ae", - "metadata": {}, - "source": [ - "This notebook showcases how researcher could use the Secure Data Disclosure system. It explains the different functionnalities provided by the dpserial client library to interact with the secure server.\n", - "\n", - "The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.\n", - "\n", - "Each user has access to one or multiple projects and for each dataset has a limited budget $\\epsilon$, $\\delta$." - ] - }, - { - "cell_type": "markdown", - "id": "5b73135c", - "metadata": {}, - "source": [ - "🐧🐧🐧\n", - "In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.\n", - "\n", - "Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.\n", - "\n", - "This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library ofs_dpserial. \n", - "🐧🐧🐧" - ] - }, - { - "cell_type": "markdown", - "id": "01ae30d2", - "metadata": {}, - "source": [ - "## Step 1: Install the library\n", - "To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library `fso_dpserial` on her local developping environment. \n", - "\n", - "It can be installed via the pip command:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "9d96dcd7", - "metadata": {}, - "outputs": [], - "source": [ - "from lomas_client.client import Client\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "9c63718b", - "metadata": {}, - "source": [ - "## Step 2: Initialise the client\n", - "\n", - "Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server. \n", - "\n", - "To create the client, Dr. Antartica needs to give it a few parameters:\n", - "- a url: the root application endpoint to the remote secure server.\n", - "- user_name: her name as registered in the database (Dr. Alice Antartica)\n", - "- dataset_name: the name of the dataset that she wants to query (PENGUIN)\n", - "\n", - "She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit. (As is done in the Secure Data Disclosure Notebook: Server side)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "941991f7", - "metadata": {}, - "outputs": [], - "source": [ - "APP_URL = \"http://lomas_server_dev:80\"\n", - "USER_NAME = \"Dr. Antartica\"\n", - "DATASET_NAME = \"PENGUIN\"\n", - "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)" - ] - }, - { - "cell_type": "markdown", - "id": "0ec400c8", - "metadata": {}, - "source": [ - "And that's it for the preparation. She is now ready to use the various functionnalities offered by `fso_dpserial`." - ] - }, - { - "cell_type": "markdown", - "id": "9b9a5f13", - "metadata": {}, - "source": [ - "## Step 3: Understand the functionnalities of the library" - ] - }, - { - "cell_type": "markdown", - "id": "c7cb5531", - "metadata": {}, - "source": [ - "### Getting dataset metadata\n", - "\n", - "Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the `get_dataset_metadata()` function of the client. As this is public information, this does not cost any budget.\n", - "\n", - "This function returns metadata information in the same format as [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details)." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "d15cbe39", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'max_ids': 1,\n", - " 'row_privacy': True,\n", - " 'censor_dims': False,\n", - " 'columns': {'species': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n", - " 'island': {'type': 'string',\n", - " 'cardinality': 3,\n", - " 'categories': ['Torgersen', 'Biscoe', 'Dream']},\n", - " 'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n", - " 'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n", - " 'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n", - " 'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n", - " 'sex': {'type': 'string',\n", - " 'cardinality': 2,\n", - " 'categories': ['MALE', 'FEMALE']}}}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "metadata = client.get_dataset_metadata()\n", - "metadata" - ] - }, - { - "cell_type": "markdown", - "id": "d338ed96", - "metadata": {}, - "source": [ - "Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds. She also knows based on the field `max_ids: 1` that each penguin can only be once in the dataset and on the field `row_privacy: True` that each row represents a single penguin. " - ] - }, - { - "cell_type": "markdown", - "id": "5a3c899d", - "metadata": {}, - "source": [ - "### Get a dummy dataset\n", - "\n", - "Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset. \n", - "\n", - "Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets.\n", - "Getting a dummy dataset does not affect the budget as there is no differential privacy here, it is not a synthetic dataset and all that could be learn here is already present in the public metadata.\n", - "\n", - "Dr. Antartica first create a dummy dataset with the default options." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "be07091f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(100, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0ChinstrapTorgersen43.10890413.314292214.2031652258.408606FEMALE
1AdelieDream63.27500119.364104158.4139964656.773158FEMALE
2AdelieDream55.61978816.143560166.1628714703.175608FEMALE
3AdelieBiscoe50.95304718.085707239.8554195187.149507MALE
4GentooTorgersen35.46065222.075665210.6429065630.456669MALE
\n", - "
" - ], - "text/plain": [ - " species island bill_length_mm bill_depth_mm flipper_length_mm \n", - "0 Chinstrap Torgersen 43.108904 13.314292 214.203165 \\\n", - "1 Adelie Dream 63.275001 19.364104 158.413996 \n", - "2 Adelie Dream 55.619788 16.143560 166.162871 \n", - "3 Adelie Biscoe 50.953047 18.085707 239.855419 \n", - "4 Gentoo Torgersen 35.460652 22.075665 210.642906 \n", - "\n", - " body_mass_g sex \n", - "0 2258.408606 FEMALE \n", - "1 4656.773158 FEMALE \n", - "2 4703.175608 FEMALE \n", - "3 5187.149507 MALE \n", - "4 5630.456669 MALE " - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset()\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "4f85e950", - "metadata": {}, - "source": [ - "However, she would prefer to have a dataset with 200 rows and chooses a seed of 0, hence:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "01f4365a", - "metadata": {}, - "outputs": [], - "source": [ - "NB_ROWS = 200\n", - "SEED = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3f553b29", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(200, 7)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
0GentooBiscoe49.20847316.117959190.1259502873.291927FEMALE
1GentooTorgersen55.03162819.963435242.9291423639.940005FEMALE
2ChinstrapTorgersen51.09671816.777518159.9614935401.743330MALE
3AdelieBiscoe49.07091114.796037244.5301532316.038092MALE
4ChinstrapBiscoe44.82791813.246787236.9488535036.246870FEMALE
\n", - "
" - ], - "text/plain": [ - " species island bill_length_mm bill_depth_mm flipper_length_mm \n", - "0 Gentoo Biscoe 49.208473 16.117959 190.125950 \\\n", - "1 Gentoo Torgersen 55.031628 19.963435 242.929142 \n", - "2 Chinstrap Torgersen 51.096718 16.777518 159.961493 \n", - "3 Adelie Biscoe 49.070911 14.796037 244.530153 \n", - "4 Chinstrap Biscoe 44.827918 13.246787 236.948853 \n", - "\n", - " body_mass_g sex \n", - "0 2873.291927 FEMALE \n", - "1 3639.940005 FEMALE \n", - "2 5401.743330 MALE \n", - "3 2316.038092 MALE \n", - "4 5036.246870 FEMALE " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_dummy = client.get_dummy_dataset(nb_rows = NB_ROWS, seed = SEED)\n", - "print(df_dummy.shape)\n", - "df_dummy.head()" - ] - }, - { - "cell_type": "markdown", - "id": "69dac96e", - "metadata": {}, - "source": [ - "### Query dummy dataset\n", - "\n", - "Now that she has an idea of what the data looks like, she wants to start querying the real dataset to for her research. However, before this other tools are at her disposal to reduce potential error risks and avoid spending budget on irrelevant queries. Of course, this does not have any impact on the budget.\n", - "\n", - "It is possible to specify the flag `dummy=True` in the various queries to perform the query on the dummy dataset instead of the real dataset and ensure that the queries are doing what is expected of them. \n", - "\n", - "Therefore Dr. Antartica computes the results that she gets on the dummy dataframe that she created locally and on the same dummy dataframe in the server via a query and compare them to ensure that the query is well defined and works within the server.\n", - "\n", - "She tests with an example on the average bill length on the dataframe." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "b6caee55", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "47.51532" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# On the local dummy dataframe\n", - "result_local_dummy = round(df_dummy['bill_length_mm'].mean(), 5)\n", - "result_local_dummy" - ] - }, - { - "cell_type": "markdown", - "id": "c3a37d8d", - "metadata": {}, - "source": [ - "As the query on the server goes through the same workflow for dummies and real data, she still has to set values for theoratical budget to spend on the dummy query. Of course, this theoretical budget will NOT affect her real budget as this is on dummy data. \n", - "\n", - "It is recommended to use very high values on the budget parameters here to have little noise and small difference between the exact local result and the 'little noisy' server result. \n", - "\n", - "Also, make sure to use the same values of number of rows and seed to have the same dummy datasets." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3946425d", - "metadata": {}, - "outputs": [], - "source": [ - "# Average bill length in mm\n", - "QUERY = \"SELECT AVG(bill_length_mm) AS avg_bill_length_mm FROM df\"" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "90cf2a6d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "47.50783673123655" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# On the remote server dummy dataframe\n", - "res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0, # make sure to select high values of epsilon and delta to have small differences\n", - " delta = 2.0, # make sure to select high values of epsilon and delta to have small differences\n", - " dummy = True, \n", - " nb_rows = NB_ROWS,\n", - " seed = SEED\n", - ")\n", - "res_server_dummy = res['query_response'][\"avg_bill_length_mm\"][0]\n", - "res_server_dummy" - ] - }, - { - "cell_type": "markdown", - "id": "bb3fa8eb", - "metadata": {}, - "source": [ - "She then checks that the responses on the dummy locally and the dummy on the server are close enough (difference would be only due to small noise addition)." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "0f2fff82", - "metadata": {}, - "outputs": [], - "source": [ - "np.testing.assert_almost_equal(\n", - " result_local_dummy, \n", - " res_server_dummy,\n", - " decimal=2, \n", - " err_msg=\"Responses are different, either try with a bigger budget or query is not doing what is intended.\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "5a82abcd", - "metadata": {}, - "source": [ - "As you can see res_local and res_server are close. We can accept that the small difference is due to the small noise added due to the large values of $\\epsilon$ and $\\delta$." - ] - }, - { - "cell_type": "markdown", - "id": "324454ed", - "metadata": {}, - "source": [ - "### Get current budget\n", - "\n", - "It is the first time that Dr. Antartica connects to the server and she wants to know how much buget the queen assigned her.\n", - "Therefore, she calls the fonction `get_initial_budget`." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "61a467f3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'initial_epsilon': 10, 'initial_delta': 0.005}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_initial_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "bc8f7a74", - "metadata": {}, - "source": [ - "She sees that she has 10.0 epsilon and 0.0004 epsilon at her disposal.\n", - "\n", - "Then she checks her total spent budget `get_total_spent_budget`. As she only did queries on metadata on dummy dataframes, this should still be 0." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "afd22f84", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0, 'total_spent_delta': 0}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "05daf5a4", - "metadata": {}, - "source": [ - "It will also be useful to know what the remaining budget is. Therefore, she calls the function `get_remaining_budget`. It just substarcts the total spent budget from the initial budget." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "6260cf54", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "20298e00", - "metadata": {}, - "source": [ - "As expected, for now the remaining budget is equal to the inital budget." - ] - }, - { - "cell_type": "markdown", - "id": "b746374c", - "metadata": {}, - "source": [ - "### Estimate cost of a query\n", - "Another safeguard is the functionnality to estimate the cost of a query. As in OpenDP and SmartnoiseSQL, the budget that will by used by a query might be slightly different than what is asked by the user. The `estimate cost` function returns the estimated real cost of any query.\n", - "\n", - "Again, of course, this will not impact the user's budget.\n", - "\n", - "Dr. Antartica checks the budget that computing the average bill length will really cost her if she asks the query with an `epsilon` and a `delta`." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "133020c6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 2.0, 'delta_cost': 4.999999999999449e-05}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = 1.0, \n", - " delta = 1e-4\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "71580822", - "metadata": {}, - "source": [ - "So this query would actually cost her 3.0 epsilon and a little 1.499e-4 delta. As she does not want to spend to much budget here she tries other values of budget." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "df487c62", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 0.4, 'delta_cost': 5.000000000032756e-06}" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = 0.2, \n", - " delta = 1e-5\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "3c6a3a8c", - "metadata": {}, - "source": [ - "This query would actually cost her 0.6 epsilon and a similar delta. She decides that it is good enough." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "c9c8d3e7", - "metadata": {}, - "outputs": [], - "source": [ - "EPSILON = 0.2\n", - "DELTA = 1e-5" - ] - }, - { - "cell_type": "markdown", - "id": "e5379edf", - "metadata": {}, - "source": [ - "### Query real dataset\n", - "Now that all the safeguard functions were tested, Dr. Antartica is ready to query on the real dataset and get a differentially private response of the average bill length. By default, the flag `dummy` is False so setting it is optional. She uses the values of `epsilon` and `delta` that she selected just before.\n", - "\n", - "Careful: This command DOES spend the budget of the user and the remaining budget is updated for every query." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "19e60263", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 10, 'remaining_delta': 0.005}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "69767fac", - "metadata": {}, - "outputs": [], - "source": [ - "avg_bill_length_response = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = EPSILON, \n", - " delta = DELTA,\n", - " dummy = False\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "6dbbdf93", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average bill length: 44.34mm.\n" - ] - } - ], - "source": [ - "avg_bill_length = avg_bill_length_response['query_response']['avg_bill_length_mm'].iloc[0]\n", - "print(f\"Average bill length: {np.round(avg_bill_length, 2)}mm.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b2767e65", - "metadata": {}, - "source": [ - "After each query on the real dataset, the budget informations are also returned to the researcher. It is possible possible to check the remaining budget again afterwards:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "39701fe5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 9.6, 'remaining_delta': 0.004994999999999967}" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "e37c587f", - "metadata": {}, - "source": [ - "As can be seen in `get_total_spent_budget()`, it is the budget estimated with `estimate_cost()` that was spent." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "487f835f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'total_spent_epsilon': 0.4, 'total_spent_delta': 5.000000000032756e-06}" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_total_spent_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "eef4afcd", - "metadata": {}, - "source": [ - "Dr. Antartica has now a differentially private estimation of the bill length of all birds and is confident to use the library for the rest of her analyses." - ] - }, - { - "cell_type": "markdown", - "id": "04929993", - "metadata": {}, - "source": [ - "## Step 4: Penguin statistics" - ] - }, - { - "cell_type": "markdown", - "id": "bbbca191", - "metadata": {}, - "source": [ - "### Confidence intervals for flipper length over the whole population" - ] - }, - { - "cell_type": "markdown", - "id": "9d41bd58", - "metadata": {}, - "source": [ - "She is first interested to have a better idea of the distribution of flipper length of all species. She already has the mean from step 3, so she just need to compute the standard deviation and know the number of penguins in the dataset. As it is just an exploration step, she uses very little budget values." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "04b376ef", - "metadata": {}, - "outputs": [], - "source": [ - "QUERY = \"SELECT COUNT(bill_length_mm) AS nb_penguin, STD(bill_length_mm) AS std_bill_length_mm FROM df\"" - ] - }, - { - "cell_type": "markdown", - "id": "0b041c81", - "metadata": {}, - "source": [ - "She again first verifies that her query works on the dummy dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "5aa9c304", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nb_penguinstd_bill_length_mm
09910.291426
\n", - "
" - ], - "text/plain": [ - " nb_penguin std_bill_length_mm\n", - "0 99 10.291426" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dummy_res = client.smartnoise_query(\n", - " query = QUERY, \n", - " epsilon = 100.0, \n", - " delta = 10.0, \n", - " dummy = True\n", - ")\n", - "dummy_res['query_response']" - ] - }, - { - "cell_type": "markdown", - "id": "74f68994", - "metadata": {}, - "source": [ - "The syntax of the query works, now she checks the budget:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "a8fa2c49", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 1.5, 'delta_cost': 5.000000000032756e-06}" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = 0.5, \n", - " delta = 1e-5\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "bed840d3", - "metadata": {}, - "source": [ - "It is a bit too much, she decides to test for less:" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "edc97e73", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 0.75, 'delta_cost': 5.000000000032756e-06}" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(\n", - " query = QUERY, \n", - " epsilon = 0.25, \n", - " delta = 1e-5\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "da9f81c4", - "metadata": {}, - "source": [ - "That's fine, she is ready to query:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "534979fb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nb_penguinstd_bill_length_mm
03379.193759
\n", - "
" - ], - "text/plain": [ - " nb_penguin std_bill_length_mm\n", - "0 337 9.193759" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "response = client.smartnoise_query(query = QUERY, epsilon = 0.25, delta = 1e-5)\n", - "response = response['query_response']\n", - "response" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "674332e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of penguins: 337.\n", - "Standard deviation of bill length: 9.19.\n" - ] - } - ], - "source": [ - "nb_penguin = response['nb_penguin'].iloc[0]\n", - "print(f\"Number of penguins: {nb_penguin}.\")\n", - "\n", - "std_bill_length = response['std_bill_length_mm'].iloc[0]\n", - "print(f\"Standard deviation of bill length: {np.round(std_bill_length, 2)}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "367081be-1159-45d8-9129-88fba20fb697", - "metadata": {}, - "source": [ - "She can now do all the postprocessing that she wants with the returned data without adding any privacy risk. " - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "f72b19d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Standard error of bill length: 0.5.\n" - ] - } - ], - "source": [ - "# Get standard error\n", - "standard_error = std_bill_length/np.sqrt(nb_penguin)\n", - "print(f\"Standard error of bill length: {np.round(standard_error, 2)}.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "62630a03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The 95% confidence interval of the bill length of all penguins is [43.36, 45.32].\n" - ] - } - ], - "source": [ - " # Compute the 95% confidence interval\n", - "ZSCORE = 1.96\n", - "lower_bound, upper_bound = avg_bill_length - ZSCORE*standard_error, avg_bill_length + ZSCORE*standard_error\n", - "print(f\"The 95% confidence interval of the bill length of all penguins is [{np.round(lower_bound, 2)}, {np.round(upper_bound, 2)}].\")" - ] - }, - { - "cell_type": "markdown", - "id": "b5ee7ad2", - "metadata": {}, - "source": [ - "### Hypothesis testing" - ] - }, - { - "cell_type": "markdown", - "id": "0f1a3a8d", - "metadata": {}, - "source": [ - "So, Dr. Antartica has now the opportunity to study the various penguins dimensions and will do an hypotheses testing analysis to discover if flipper length differ between the penguins species.\n", - "\n", - "She will do a two-tailed two-sample $t$ test.\n", - "\n", - "- The null hypothese $H_0$ is flipper length does not differ between species.\n", - "- The alternative hypothesis $H_a$ is flipper length does differ between species.\n", - "\n", - "She set the level of significance at 0.05." - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "7d9ae766-4c0d-4dc5-9c9a-5f7eb99718f9", - "metadata": {}, - "outputs": [], - "source": [ - "CRITICAL_VALUE = 0.05" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "5006201d", - "metadata": {}, - "outputs": [], - "source": [ - "QUERY = \"SELECT \\\n", - " species AS species, \\\n", - " COUNT(bill_length_mm) AS nb_penguin, \\\n", - " AVG(bill_length_mm) AS avg_bill_length_mm, \\\n", - " STD(bill_length_mm) AS std_bill_length_mm \\\n", - " FROM df GROUP BY species\"" - ] - }, - { - "cell_type": "markdown", - "id": "e725eb3f-d12f-4f62-8f57-06fb00639f91", - "metadata": {}, - "source": [ - "She estimates how much budget it would really cost:" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "0255550b-7fd2-4244-a8eb-da809ddc6a5b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 3.0, 'delta_cost': 4.999999999999449e-05}" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(query = QUERY, epsilon = 1, delta = 1e-4)" - ] - }, - { - "cell_type": "markdown", - "id": "56bf804f-d877-48cb-b405-709b30cda3d1", - "metadata": {}, - "source": [ - "The real cost seems to be 3 times the epsilon that she sets. It is a lot but she tries on the dummy dataset to verify all is working properly." - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "80d9933b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'query_response': species nb_penguin avg_bill_length_mm std_bill_length_mm\n", - " 0 Adelie 36 48.807157 8.785415\n", - " 1 Chinstrap 32 46.193166 18.700943\n", - " 2 Gentoo 30 41.849626 6.128719}" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dummy_res = client.smartnoise_query(query = QUERY, epsilon = 1, delta = 1.0, dummy = True)\n", - "dummy_res" - ] - }, - { - "cell_type": "markdown", - "id": "5691680f-8716-4a99-999a-a2bd5ef6a679", - "metadata": {}, - "source": [ - "She did not give enough budget for the query to work. This is why there are 'NANs' in the output. She has to spend more budget for the query to work." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "6b014db4-acbd-4ae1-a3b6-397035851583", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 8.85, 'remaining_delta': 0.004989999999999935}" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "43d3488d-3987-4fec-a840-78385e956832", - "metadata": {}, - "source": [ - "The maximum she can do with all her remaining budget of 7.4 is around 7.4/4 = 1.85. Let's check:" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "99d7998d-daa1-4d5e-aa42-abc5aabdf2e3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'epsilon_cost': 9.0, 'delta_cost': 4.999999999999449e-05}" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.estimate_smartnoise_cost(query = QUERY, epsilon = 12/4, delta = 1e-4)" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "0e07fde9-9430-4a12-8337-0503ac162c26", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'query_response': species nb_penguin avg_bill_length_mm std_bill_length_mm\n", - " 0 Adelie 37 48.580124 2.255215\n", - " 1 Chinstrap 33 46.983725 9.051034\n", - " 2 Gentoo 28 43.886555 10.928323}" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dummy_res = client.smartnoise_query(query = QUERY, epsilon = 12/4, delta = 1e-4, dummy = True)\n", - "dummy_res" - ] - }, - { - "cell_type": "markdown", - "id": "e8dadd17", - "metadata": {}, - "source": [ - "If it errors, she might need to re-run the query a few times until it works. The budget is not affected by dummy queries anyway." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "59f2d665", - "metadata": {}, - "outputs": [], - "source": [ - "flipper_length_response = client.smartnoise_query(query = QUERY, epsilon = 12/4, delta = 1e-4)" - ] - }, - { - "cell_type": "markdown", - "id": "78d23e66-2c05-4e35-bba9-7b92710b872a", - "metadata": {}, - "source": [ - "And now she should not have any remaining budget:" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "6eb20cfb-fa53-496f-940d-9b17b05fa074", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'remaining_epsilon': 1.0, 'remaining_delta': 0.004950000000000006}" - ] - }, - "execution_count": 48, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.get_remaining_budget()" - ] - }, - { - "cell_type": "markdown", - "id": "cb96f406-d409-4531-ac86-05f1c9296705", - "metadata": {}, - "source": [ - "But she can do her post-processing and hypothesis analysis." - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "748f125f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesnb_penguinavg_bill_length_mmstd_bill_length_mm
0Adelie15038.9029430.805783
1Chinstrap6749.2625606.123036
2Gentoo12247.4695632.851064
\n", - "
" - ], - "text/plain": [ - " species nb_penguin avg_bill_length_mm std_bill_length_mm\n", - "0 Adelie 150 38.902943 0.805783\n", - "1 Chinstrap 67 49.262560 6.123036\n", - "2 Gentoo 122 47.469563 2.851064" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_flipper = flipper_length_response['query_response']\n", - "df_flipper" - ] - }, - { - "cell_type": "markdown", - "id": "099129cf", - "metadata": {}, - "source": [ - "And finally the $t$-test:" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "0a7d7d4d", - "metadata": {}, - "outputs": [], - "source": [ - "def t_test(avg_1, avg_2, std_1, std_2, nb_1, nb_2):\n", - " standard_error = (std_1 * (nb_1 - 1) + std_2 * (nb_2 - 1))/(nb_1 + nb_2 - 2)\n", - " return (avg_1 - avg_2)/np.sqrt(standard_error**2*(1/nb_1 + 1 /nb_2))" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "bc3ee48a", - "metadata": {}, - "outputs": [], - "source": [ - "nb_0, avg_0, std_0 = df_flipper[['nb_penguin', 'avg_bill_length_mm', 'std_bill_length_mm']].iloc[0]\n", - "nb_1, avg_1, std_1 = df_flipper[['nb_penguin', 'avg_bill_length_mm', 'std_bill_length_mm']].iloc[1]\n", - "nb_2, avg_2, std_2 = df_flipper[['nb_penguin', 'avg_bill_length_mm', 'std_bill_length_mm']].iloc[2]" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "id": "1717f9ea", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "T test between specie 0 and specie 1: -28.92. Reject null hypothesis: True.\n", - "T test between specie 0 and specie 2: -40.8. Reject null hypothesis: True.\n", - "T test between specie 1 and specie 2: 2.94. Reject null hypothesis: True.\n" - ] - } - ], - "source": [ - "t_01 = t_test(avg_0, avg_1, std_0, std_1, nb_0, nb_1)\n", - "t_02 = t_test(avg_0, avg_2, std_0, std_2, nb_0, nb_2)\n", - "t_12 = t_test(avg_1, avg_2, std_1, std_2, nb_1, nb_2)\n", - "\n", - "print(f\"T test between specie 0 and specie 1: {np.round(t_01, 2)}. Reject null hypothesis: {abs(t_01) > CRITICAL_VALUE}.\")\n", - "print(f\"T test between specie 0 and specie 2: {np.round(t_02, 2)}. Reject null hypothesis: {abs(t_02) > CRITICAL_VALUE}.\")\n", - "print(f\"T test between specie 1 and specie 2: {np.round(t_12, 2)}. Reject null hypothesis: {abs(t_12) > CRITICAL_VALUE}.\")" - ] - }, - { - "cell_type": "markdown", - "id": "a557b754-ae7a-490a-8c4b-3928de7101d1", - "metadata": {}, - "source": [ - "All t-tests are above the critical value of 0.5. She can reject the null hypothesis." - ] - }, - { - "cell_type": "markdown", - "id": "b4df475b", - "metadata": {}, - "source": [ - "She finally computes the confidence intervals for the flipper length of each species" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "id": "9289bc26", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
speciesnb_penguinavg_bill_length_mmstd_bill_length_mmstandard_errorci_95_lower_boundci_95_upper_bound
0Adelie15038.9029430.8057830.06579238.77399139.031895
1Chinstrap6749.2625606.1230360.74804847.79638650.728733
2Gentoo12247.4695632.8510640.25812346.96364247.975485
\n", - "
" - ], - "text/plain": [ - " species nb_penguin avg_bill_length_mm std_bill_length_mm \n", - "0 Adelie 150 38.902943 0.805783 \\\n", - "1 Chinstrap 67 49.262560 6.123036 \n", - "2 Gentoo 122 47.469563 2.851064 \n", - "\n", - " standard_error ci_95_lower_bound ci_95_upper_bound \n", - "0 0.065792 38.773991 39.031895 \n", - "1 0.748048 47.796386 50.728733 \n", - "2 0.258123 46.963642 47.975485 " - ] - }, - "execution_count": 53, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ZSCORE = 1.96\n", - "df_flipper['standard_error'] = df_flipper['std_bill_length_mm']/np.sqrt(df_flipper['nb_penguin'])\n", - "df_flipper['ci_95_lower_bound'] = df_flipper['avg_bill_length_mm'] - ZSCORE * df_flipper['standard_error']\n", - "df_flipper['ci_95_upper_bound'] = df_flipper['avg_bill_length_mm'] + ZSCORE * df_flipper['standard_error']\n", - "df_flipper" - ] - }, - { - "cell_type": "markdown", - "id": "f79e8333-1f06-4019-af3c-94ff2362d036", - "metadata": {}, - "source": [ - "She can now go and present her findings to queen Icebergina." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cce8dde4-0c9e-43d4-b6ce-890ac7c8efd2", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/html/de/objects.inv b/html/de/objects.inv deleted file mode 100644 index b8ae6d86..00000000 Binary files a/html/de/objects.inv and /dev/null differ diff --git a/html/de/py-modindex.html b/html/de/py-modindex.html deleted file mode 100644 index aa298b61..00000000 --- a/html/de/py-modindex.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - - - Python-Modulindex — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Python-Modulindex

- -
- l -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
- l
- lomas_client -
    - lomas_client.client -
- lomas_server -
    - lomas_server.admin_database -
    - lomas_server.admin_database.admin_database -
    - lomas_server.admin_database.mongodb_database -
    - lomas_server.admin_database.utils -
    - lomas_server.admin_database.yaml_database -
    - lomas_server.administration -
    - lomas_server.app -
    - lomas_server.constants -
    - lomas_server.dataset_store -
    - lomas_server.dataset_store.basic_dataset_store -
    - lomas_server.dataset_store.dataset_store -
    - lomas_server.dataset_store.lru_dataset_store -
    - lomas_server.dataset_store.private_dataset_observer -
    - lomas_server.dataset_store.utils -
    - lomas_server.dp_queries -
    - lomas_server.dp_queries.dp_libraries -
    - lomas_server.dp_queries.dp_libraries.opendp -
    - lomas_server.dp_queries.dp_libraries.smartnoise_sql -
    - lomas_server.dp_queries.dp_libraries.utils -
    - lomas_server.dp_queries.dp_logic -
    - lomas_server.dp_queries.dp_querier -
    - lomas_server.dp_queries.dummy_dataset -
    - lomas_server.mongodb_admin -
    - lomas_server.mongodb_admin_cli -
    - lomas_server.private_dataset -
    - lomas_server.private_dataset.in_memory_dataset -
    - lomas_server.private_dataset.path_dataset -
    - lomas_server.private_dataset.private_dataset -
    - lomas_server.private_dataset.s3_dataset -
    - lomas_server.private_dataset.utils -
    - lomas_server.tests -
    - lomas_server.tests.constants -
    - lomas_server.tests.test_api -
    - lomas_server.tests.test_dummy_generation -
    - lomas_server.tests.test_mongodb_admin -
    - lomas_server.utils -
    - lomas_server.utils.anti_timing_att -
    - lomas_server.utils.collections_models -
    - lomas_server.utils.config -
    - lomas_server.utils.error_handler -
    - lomas_server.utils.example_inputs -
    - lomas_server.utils.input_models -
    - lomas_server.utils.loggr -
    - lomas_server.utils.utils -
    - lomas_server.uvicorn_serve -
- - -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/search.html b/html/de/search.html deleted file mode 100644 index 86c0cd49..00000000 --- a/html/de/search.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - Suche — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - - - -
- -
- -
-
-
- -
- -
-

© Copyright 2024, DSCC.

-
- - Erstellt mit Sphinx mit einem - theme - bereitgestellt von Read the Docs. - - -
-
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - - - - - - \ No newline at end of file diff --git a/html/de/searchindex.js b/html/de/searchindex.js deleted file mode 100644 index 75a85260..00000000 --- a/html/de/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({"alltitles": {"API Documentation": [[1, "api-documentation"]], "Access the client environment through the url and use the password defined in the values file.": [[21, "Access-the-client-environment-through-the-url-and-use-the-password-defined-in-the-values-file."]], "Access the server to administrate the mongoDB": [[25, "Access-the-server-to-administrate-the-mongoDB"]], "Accessing the Helm Chart": [[33, "accessing-the-helm-chart"]], "Add one dataset": [[20, "Add-one-dataset"]], "Add user": [[25, "Add-user"]], "Adding users": [[20, "Adding-users"], [23, "Adding-users"]], "Additional services": [[0, "additional-services"]], "Administering the service by accessing the mongoDB": [[20, "Administering-the-service-by-accessing-the-mongoDB"], [23, "Administering-the-service-by-accessing-the-mongoDB"]], "Administration": [[29, "administration"]], "Apply pipeline on data": [[19, "Apply-pipeline-on-data"]], "Archives of queries": [[23, "Archives-of-queries"], [25, "Archives-of-queries"]], "Average and number of rows with smartnoise-sql library on remote dummy": [[18, "Average-and-number-of-rows-with-smartnoise-sql-library-on-remote-dummy"], [19, "Average-and-number-of-rows-with-smartnoise-sql-library-on-remote-dummy"], [27, "Average-and-number-of-rows-with-smartnoise-sql-library-on-remote-dummy"]], "Boxplot of income per partitions of the population": [[19, "Boxplot-of-income-per-partitions-of-the-population"]], "Building the container images": [[21, "Building-the-container-images"], [24, "Building-the-container-images"]], "Building the server image": [[23, "Building-the-server-image"]], "CLI": [[31, "cli"]], "Change budget": [[25, "Change-budget"]], "Changing the budget": [[23, "Changing-the-budget"]], "Check deployment with kubectl get all and by querying /state": [[21, "Check-deployment-with-kubectl-get-all-and-by-querying-/state"]], "Cleaning the database": [[20, "Cleaning-the-database"]], "Client": [[6, "client"], [7, "client"], [21, "Client"]], "Client API": [[2, "client-api"]], "Collections": [[31, "collections"]], "Column types and bounds": [[19, "Column-types-and-bounds"]], "Confidence intervals for age over the whole population": [[27, "Confidence-intervals-for-age-over-the-whole-population"]], "Confidence intervals for bill length over the whole population": [[18, "Confidence-intervals-for-bill-length-over-the-whole-population"], [19, "Confidence-intervals-for-bill-length-over-the-whole-population"]], "Confidence intervals for flipper length over the whole population": [[28, "Confidence-intervals-for-flipper-length-over-the-whole-population"]], "Count per species": [[18, "Count-per-species"], [19, "Count-per-species"]], "Counts per partition of the population": [[19, "Counts-per-partition-of-the-population"]], "Create a docker volume": [[25, "Create-a-docker-volume"]], "Data preparation": [[19, "Data-preparation"]], "Datasets": [[31, "datasets"]], "Datasets (add and drop)": [[20, "Datasets-(add-and-drop)"], [23, "Datasets-(add-and-drop)"], [25, "Datasets-(add-and-drop)"]], "Demo - Kubernetes Service Deployment": [[21, "Demo---Kubernetes-Service-Deployment"]], "Deploying Lomas on Onyxia": [[36, "deploying-lomas-on-onyxia"]], "Deploying the Service on Kubernetes": [[33, "deploying-the-service-on-kubernetes"]], "Deploying the service": [[23, "Deploying-the-service"]], "Deploying the service Helm chart": [[23, "Deploying-the-service-Helm-chart"]], "Deployment": [[32, "deployment"], [37, "deployment"]], "Disclaimer: Temporary Version of OpenDP with Polars": [[19, "Disclaimer:-Temporary-Version-of-OpenDP-with-Polars"]], "Docker volume and config setup": [[26, "Docker-volume-and-config-setup"]], "Documentation": [[0, "documentation"]], "Download Helm dependency for the MongoDB chart": [[21, "Download-Helm-dependency-for-the-MongoDB-chart"]], "Errors": [[3, "errors"]], "Estimate cost of a query": [[18, "Estimate-cost-of-a-query"], [19, "Estimate-cost-of-a-query"], [27, "Estimate-cost-of-a-query"], [28, "Estimate-cost-of-a-query"]], "Examples": [[4, "examples"], [31, "examples"]], "ExternalLibraryException": [[3, "externallibraryexception"]], "FSO Example: (Synthetic) Income dataset": [[19, "FSO-Example:-(Synthetic)-Income-dataset"]], "Finally all can be loaded fom a file direcly": [[25, "Finally-all-can-be-loaded-fom-a-file-direcly"]], "Finally, everything can actually be loaded directly from a single file": [[23, "Finally,-everything-can-actually-be-loaded-directly-from-a-single-file"]], "First steps": [[6, "first-steps"]], "For each dataset, 2 informations are required:": [[20, "For-each-dataset,-2-informations-are-required:"]], "Get a dummy dataset": [[18, "Get-a-dummy-dataset"], [19, "Get-a-dummy-dataset"], [27, "Get-a-dummy-dataset"], [28, "Get-a-dummy-dataset"]], "Get current budget": [[18, "Get-current-budget"], [19, "Get-current-budget"], [27, "Get-current-budget"], [28, "Get-current-budget"]], "Getting dataset metadata": [[18, "Getting-dataset-metadata"], [19, "Getting-dataset-metadata"], [27, "Getting-dataset-metadata"], [28, "Getting-dataset-metadata"]], "History": [[7, "history"]], "Hypothesis testing": [[28, "Hypothesis-testing"]], "Income distribution for partitions of the population:": [[19, "Income-distribution-for-partitions-of-the-population:"]], "Indices and tables": [[7, "indices-and-tables"]], "Install server chart": [[21, "Install-server-chart"]], "Install the client chart": [[21, "Install-the-client-chart"]], "Installation": [[6, "installation"]], "Installing the Helm Chart": [[33, "installing-the-helm-chart"]], "InternalServerException": [[3, "internalserverexception"]], "Introduction": [[36, "introduction"]], "InvalidQueryException": [[3, "invalidqueryexception"]], "Kubernetes": [[33, "kubernetes"]], "Kubernetes Service Deployment": [[24, "Kubernetes-Service-Deployment"]], "Linting and other checks": [[0, "linting-and-other-checks"]], "Local": [[34, "local"]], "Local Service Deployment - How to": [[26, "Local-Service-Deployment---How-to"]], "Lomas-server: CLI administration": [[25, "Lomas-server:-CLI-administration"]], "Lomas: Client demo": [[18, "Lomas:-Client-demo"]], "Lomas: Client demo with polar": [[19, "Lomas:-Client-demo-with-polar"]], "Metadata are expected to be in the same format as SmartnoiseSQL dictionary format, where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details).": [[20, "Metadata-are-expected-to-be-in-the-same-format-as-SmartnoiseSQL-dictionary-format,-where-among-other,-there-is-information-about-all-the-available-columns,-their-type,-bound-values-(see-Smartnoise-page-for-more-details)."]], "Minimal OpenDP example on the income dataset": [[22, "Minimal-OpenDP-example-on-the-income-dataset"]], "Modifying the ingress Section": [[33, "modifying-the-ingress-section"]], "Modifying values.yaml": [[33, "modifying-values-yaml"]], "Module contents": [[8, "module-lomas_client"], [9, "module-lomas_server"], [10, "module-lomas_server.admin_database"], [11, "module-lomas_server.administration"], [12, "module-lomas_server.dataset_store"], [13, "module-lomas_server.dp_queries"], [14, "module-lomas_server.dp_queries.dp_libraries"], [15, "module-lomas_server.private_dataset"], [16, "module-lomas_server.tests"], [17, "module-lomas_server.utils"]], "MongoDB Administration": [[31, "mongodb-administration"]], "MongoDB Connection": [[31, "mongodb-connection"]], "On Onyxia": [[0, "on-onyxia"]], "On a kubernetes cluster": [[0, "on-a-kubernetes-cluster"]], "On local machine": [[0, "on-local-machine"]], "Onyxia": [[36, "onyxia"], [37, "onyxia"]], "Overview": [[31, "overview"]], "Prepare the database": [[25, "Prepare-the-database"]], "Prepare the pipeline": [[19, "Prepare-the-pipeline"]], "Preparing the database": [[20, "Preparing-the-database"], [23, "Preparing-the-database"]], "Prerequisites": [[33, "prerequisites"], [34, "prerequisites"]], "Query dummy dataset": [[28, "Query-dummy-dataset"]], "Query on dummy dataset": [[18, "Query-on-dummy-dataset"], [19, "Query-on-dummy-dataset"], [27, "Query-on-dummy-dataset"]], "Query on real private dataset with smartnoise-sql": [[18, "Query-on-real-private-dataset-with-smartnoise-sql"], [19, "Query-on-real-private-dataset-with-smartnoise-sql"]], "Query on real private dataset with smartnoise-sql.": [[27, "Query-on-real-private-dataset-with-smartnoise-sql."]], "Query real dataset": [[28, "Query-real-dataset"]], "Quickstart": [[6, "quickstart"]], "Remove user": [[25, "Remove-user"]], "Removing users": [[23, "Removing-users"]], "S3 example": [[27, "S3-example"]], "Secure Data Disclosure on Kubernetes: Deployment and Server Administration": [[23, "Secure-Data-Disclosure-on-Kubernetes:-Deployment-and-Server-Administration"]], "Secure Data Disclosure on Kubernetes: Server Administration": [[20, "Secure-Data-Disclosure-on-Kubernetes:-Server-Administration"]], "Secure Data Disclosure: Client side": [[28, "Secure-Data-Disclosure:-Client-side"]], "Server": [[7, "server"], [21, "Server"]], "Server API": [[30, "server-api"]], "Server test": [[26, "Server-test"]], "Service start": [[26, "Service-start"]], "Service stop": [[26, "Service-stop"]], "Since the service has been deployed in the demo 1, the URL should be accessible.": [[20, "Since-the-service-has-been-deployed-in-the-demo-1,-the-URL-should-be-accessible."]], "Some existing options": [[20, "Some-existing-options"]], "Start of DEMO": [[20, "Start-of-DEMO"]], "Start server": [[25, "Start-server"]], "Start service": [[0, "start-service"]], "Start the server": [[25, "Start-the-server"]], "Starting the client session": [[21, "Starting-the-client-session"], [24, "Starting-the-client-session"]], "Starting the service": [[21, "Starting-the-service"], [24, "Starting-the-service"]], "Step 1: Install the library": [[18, "Step-1:-Install-the-library"], [19, "Step-1:-Install-the-library"], [27, "Step-1:-Install-the-library"], [28, "Step-1:-Install-the-library"]], "Step 2: Initialise the client": [[18, "Step-2:-Initialise-the-client"], [19, "Step-2:-Initialise-the-client"], [27, "Step-2:-Initialise-the-client"], [28, "Step-2:-Initialise-the-client"]], "Step 3: Understand the functionnalities of the library": [[18, "Step-3:-Understand-the-functionnalities-of-the-library"], [19, "Step-3:-Understand-the-functionnalities-of-the-library"], [27, "Step-3:-Understand-the-functionnalities-of-the-library"], [28, "Step-3:-Understand-the-functionnalities-of-the-library"]], "Step 4: Penguin statistics": [[28, "Step-4:-Penguin-statistics"]], "Step 4: Penguin statistics with opendp": [[18, "Step-4:-Penguin-statistics-with-opendp"], [19, "Step-4:-Penguin-statistics-with-opendp"]], "Step 4: Titanic statistics with opendp": [[27, "Step-4:-Titanic-statistics-with-opendp"]], "Step 5: See archives of queries": [[18, "Step-5:-See-archives-of-queries"], [19, "Step-5:-See-archives-of-queries"]], "Steps to Deploy Locally": [[34, "steps-to-deploy-locally"]], "Stop the server: do not do it now !": [[25, "Stop-the-server:-do-not-do-it-now-!"]], "Stopping the service": [[21, "Stopping-the-service"], [24, "Stopping-the-service"]], "Stopping the service: Let\u2019s not do it right now!": [[20, "Stopping-the-service:-Let's-not-do-it-right-now!"], [23, "Stopping-the-service:-Let's-not-do-it-right-now!"]], "Streamlit": [[37, "streamlit"]], "Submodules": [[8, "submodules"], [9, "submodules"], [10, "submodules"], [12, "submodules"], [13, "submodules"], [14, "submodules"], [15, "submodules"], [16, "submodules"], [17, "submodules"]], "Subpackages": [[9, "subpackages"], [13, "subpackages"]], "Switching user and exploring new dataset": [[19, "Switching-user-and-exploring-new-dataset"]], "Tests": [[0, "tests"]], "This notebook showcases how a data owner could add and make their data available to certain user. We will do this in a step by step fashion.": [[20, "This-notebook-showcases-how-a-data-owner-could-add-and-make-their-data-available-to-certain-user.-We-will-do-this-in-a-step-by-step-fashion."]], "Tips for developers": [[0, "tips-for-developers"]], "UnauthorizedAccessException": [[3, "unauthorizedaccessexception"]], "Update values.yaml file": [[21, "Update-values.yaml-file"], [21, "id1"]], "Use docker login to setup your credentials": [[21, "Use-docker-login-to-setup-your-credentials"]], "User session": [[26, "User-session"]], "Users": [[20, "Users"], [23, "Users"], [25, "Users"], [31, "users"]], "Visualise all options": [[25, "Visualise-all-options"]], "Visualise results": [[19, "Visualise-results"]], "We add the data based on a yaml file:": [[20, "We-add-the-data-based-on-a-yaml-file:"]], "We can add one dataset with its name, database type and path to medata file:": [[20, "We-can-add-one-dataset-with-its-name,-database-type-and-path-to-medata-file:"]], "Welcome to Lomas documentation": [[7, "welcome-to-lomas-documentation"]], "lomas_client": [[5, "lomas-client"]], "lomas_client package": [[8, "lomas-client-package"]], "lomas_client.client module": [[8, "module-lomas_client.client"]], "lomas_server": [[35, "lomas-server"]], "lomas_server package": [[9, "lomas-server-package"]], "lomas_server.admin_database package": [[10, "lomas-server-admin-database-package"]], "lomas_server.admin_database.admin_database module": [[10, "module-lomas_server.admin_database.admin_database"]], "lomas_server.admin_database.mongodb_database module": [[10, "module-lomas_server.admin_database.mongodb_database"]], "lomas_server.admin_database.utils module": [[10, "module-lomas_server.admin_database.utils"]], "lomas_server.admin_database.yaml_database module": [[10, "module-lomas_server.admin_database.yaml_database"]], "lomas_server.administration package": [[11, "lomas-server-administration-package"]], "lomas_server.app module": [[9, "module-lomas_server.app"]], "lomas_server.constants module": [[9, "module-lomas_server.constants"]], "lomas_server.dataset_store package": [[12, "lomas-server-dataset-store-package"]], "lomas_server.dataset_store.basic_dataset_store module": [[12, "module-lomas_server.dataset_store.basic_dataset_store"]], "lomas_server.dataset_store.dataset_store module": [[12, "module-lomas_server.dataset_store.dataset_store"]], "lomas_server.dataset_store.lru_dataset_store module": [[12, "module-lomas_server.dataset_store.lru_dataset_store"]], "lomas_server.dataset_store.private_dataset_observer module": [[12, "module-lomas_server.dataset_store.private_dataset_observer"]], "lomas_server.dataset_store.utils module": [[12, "module-lomas_server.dataset_store.utils"]], "lomas_server.dp_queries package": [[13, "lomas-server-dp-queries-package"]], "lomas_server.dp_queries.dp_libraries package": [[14, "lomas-server-dp-queries-dp-libraries-package"]], "lomas_server.dp_queries.dp_libraries.opendp module": [[14, "module-lomas_server.dp_queries.dp_libraries.opendp"]], "lomas_server.dp_queries.dp_libraries.smartnoise_sql module": [[14, "module-lomas_server.dp_queries.dp_libraries.smartnoise_sql"]], "lomas_server.dp_queries.dp_libraries.utils module": [[14, "module-lomas_server.dp_queries.dp_libraries.utils"]], "lomas_server.dp_queries.dp_logic module": [[13, "module-lomas_server.dp_queries.dp_logic"]], "lomas_server.dp_queries.dp_querier module": [[13, "module-lomas_server.dp_queries.dp_querier"]], "lomas_server.dp_queries.dummy_dataset module": [[13, "module-lomas_server.dp_queries.dummy_dataset"]], "lomas_server.mongodb_admin module": [[9, "module-lomas_server.mongodb_admin"]], "lomas_server.mongodb_admin_cli module": [[9, "module-lomas_server.mongodb_admin_cli"]], "lomas_server.private_dataset package": [[15, "lomas-server-private-dataset-package"]], "lomas_server.private_dataset.in_memory_dataset module": [[15, "module-lomas_server.private_dataset.in_memory_dataset"]], "lomas_server.private_dataset.path_dataset module": [[15, "module-lomas_server.private_dataset.path_dataset"]], "lomas_server.private_dataset.private_dataset module": [[15, "module-lomas_server.private_dataset.private_dataset"]], "lomas_server.private_dataset.s3_dataset module": [[15, "module-lomas_server.private_dataset.s3_dataset"]], "lomas_server.private_dataset.utils module": [[15, "module-lomas_server.private_dataset.utils"]], "lomas_server.tests package": [[16, "lomas-server-tests-package"]], "lomas_server.tests.constants module": [[16, "module-lomas_server.tests.constants"]], "lomas_server.tests.test_api module": [[16, "module-lomas_server.tests.test_api"]], "lomas_server.tests.test_dummy_generation module": [[16, "module-lomas_server.tests.test_dummy_generation"]], "lomas_server.tests.test_mongodb_admin module": [[16, "module-lomas_server.tests.test_mongodb_admin"]], "lomas_server.utils package": [[17, "lomas-server-utils-package"]], "lomas_server.utils.anti_timing_att module": [[17, "module-lomas_server.utils.anti_timing_att"]], "lomas_server.utils.collections_models module": [[17, "module-lomas_server.utils.collections_models"]], "lomas_server.utils.config module": [[17, "module-lomas_server.utils.config"]], "lomas_server.utils.error_handler module": [[17, "module-lomas_server.utils.error_handler"]], "lomas_server.utils.example_inputs module": [[17, "module-lomas_server.utils.example_inputs"]], "lomas_server.utils.input_models module": [[17, "module-lomas_server.utils.input_models"]], "lomas_server.utils.loggr module": [[17, "module-lomas_server.utils.loggr"]], "lomas_server.utils.utils module": [[17, "module-lomas_server.utils.utils"]], "lomas_server.uvicorn_serve module": [[9, "module-lomas_server.uvicorn_serve"]]}, "docnames": ["CONTRIBUTING", "api", "client_api", "client_errors", "client_examples", "client_modules", "client_quickstart", "index", "lomas_client", "lomas_server", "lomas_server.admin_database", "lomas_server.administration", "lomas_server.dataset_store", "lomas_server.dp_queries", "lomas_server.dp_queries.dp_libraries", "lomas_server.private_dataset", "lomas_server.tests", "lomas_server.utils", "notebooks/Demo_Client_Notebook", "notebooks/Demo_Client_Notebook_polars", "notebooks/demo_kubernetes_admin_notebook", "notebooks/demo_kubernetes_deployment_notebook", "notebooks/income_minimal", "notebooks/kubernetes_admin_notebook", "notebooks/kubernetes_deployment_notebook", "notebooks/local_admin_notebook", "notebooks/local_deployment_notebook", "notebooks/s3_example_notebook", "notebooks/smartnoise_client_notebook", "server_administration", "server_api", "server_cli", "server_deployment", "server_kubernetes", "server_local", "server_modules", "server_onyxia", "server_streamlit"], "envversion": {"nbsphinx": 4, "sphinx": 61, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1}, "filenames": ["CONTRIBUTING.md", "api.rst", "client_api.rst", "client_errors.rst", "client_examples.rst", "client_modules.rst", "client_quickstart.rst", "index.rst", "lomas_client.rst", "lomas_server.rst", "lomas_server.admin_database.rst", "lomas_server.administration.rst", "lomas_server.dataset_store.rst", "lomas_server.dp_queries.rst", "lomas_server.dp_queries.dp_libraries.rst", "lomas_server.private_dataset.rst", "lomas_server.tests.rst", "lomas_server.utils.rst", "notebooks/Demo_Client_Notebook.ipynb", "notebooks/Demo_Client_Notebook_polars.ipynb", "notebooks/demo_kubernetes_admin_notebook.ipynb", "notebooks/demo_kubernetes_deployment_notebook.ipynb", "notebooks/income_minimal.ipynb", "notebooks/kubernetes_admin_notebook.ipynb", "notebooks/kubernetes_deployment_notebook.ipynb", "notebooks/local_admin_notebook.ipynb", "notebooks/local_deployment_notebook.ipynb", "notebooks/s3_example_notebook.ipynb", "notebooks/smartnoise_client_notebook.ipynb", "server_administration.rst", "server_api.rst", "server_cli.rst", "server_deployment.rst", "server_kubernetes.rst", "server_local.rst", "server_modules.rst", "server_onyxia.rst", "server_streamlit.rst"], "indexentries": {"add_dataset() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.add_dataset", false]], "add_dataset_to_user() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.add_dataset_to_user", false]], "add_datasets_via_yaml() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.add_datasets_via_yaml", false]], "add_demo_data_to_admindb() (im modul lomas_server.utils.utils)": [[17, "lomas_server.utils.utils.add_demo_data_to_admindb", false]], "add_exception_handlers() (im modul lomas_server.utils.error_handler)": [[17, "lomas_server.utils.error_handler.add_exception_handlers", false]], "add_user() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.add_user", false]], "add_user_with_budget() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.add_user_with_budget", false]], "add_users_via_yaml() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.add_users_via_yaml", false]], "address (attribut von lomas_server.utils.config.mongodbconfig)": [[17, "lomas_server.utils.config.MongoDBConfig.address", false]], "admin_database (attribut von lomas_server.dataset_store.dataset_store.datasetstore)": [[12, "lomas_server.dataset_store.dataset_store.DatasetStore.admin_database", false]], "admin_database (attribut von lomas_server.dp_queries.dp_logic.queryhandler)": [[13, "lomas_server.dp_queries.dp_logic.QueryHandler.admin_database", false]], "admin_database (attribut von lomas_server.utils.config.config)": [[17, "lomas_server.utils.config.Config.admin_database", false]], "admindatabase (klasse in lomas_server.admin_database.admin_database)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase", false]], "admindbtype (klasse in lomas_server.constants)": [[9, "lomas_server.constants.AdminDBType", false]], "adminmongodatabase (klasse in lomas_server.admin_database.mongodb_database)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase", false]], "adminyamldatabase (klasse in lomas_server.admin_database.yaml_database)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase", false]], "anti_timing_att() (im modul lomas_server.utils.anti_timing_att)": [[17, "lomas_server.utils.anti_timing_att.anti_timing_att", false]], "aws_access_key_id (attribut von lomas_server.utils.collections_models.datasetofs3db)": [[17, "lomas_server.utils.collections_models.DatasetOfS3DB.aws_access_key_id", false]], "aws_access_key_id (attribut von lomas_server.utils.collections_models.metadataofs3db)": [[17, "lomas_server.utils.collections_models.MetadataOfS3DB.aws_access_key_id", false]], "aws_secret_access_key (attribut von lomas_server.utils.collections_models.datasetofs3db)": [[17, "lomas_server.utils.collections_models.DatasetOfS3DB.aws_secret_access_key", false]], "aws_secret_access_key (attribut von lomas_server.utils.collections_models.metadataofs3db)": [[17, "lomas_server.utils.collections_models.MetadataOfS3DB.aws_secret_access_key", false]], "basic (attribut von lomas_server.constants.datasetstoretype)": [[9, "lomas_server.constants.DatasetStoreType.BASIC", false]], "basicdatasetstore (klasse in lomas_server.dataset_store.basic_dataset_store)": [[12, "lomas_server.dataset_store.basic_dataset_store.BasicDatasetStore", false]], "check_dataset_and_metadata_exist() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.check_dataset_and_metadata_exist", false]], "check_result_acknowledged() (im modul lomas_server.admin_database.mongodb_database)": [[10, "lomas_server.admin_database.mongodb_database.check_result_acknowledged", false]], "check_user_exists() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.check_user_exists", false]], "check_user_has_dataset() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.check_user_has_dataset", false]], "client (klasse in lomas_client.client)": [[8, "lomas_client.client.Client", false]], "columns (attribut von lomas_server.utils.collections_models.metadata)": [[17, "lomas_server.utils.collections_models.Metadata.columns", false]], "config (klasse in lomas_server.utils.config)": [[17, "lomas_server.utils.config.Config", false]], "configkeys (klasse in lomas_server.constants)": [[9, "lomas_server.constants.ConfigKeys", false]], "configloader (klasse in lomas_server.utils.config)": [[17, "lomas_server.utils.config.ConfigLoader", false]], "cost() (methode von lomas_server.dp_queries.dp_libraries.opendp.opendpquerier)": [[14, "lomas_server.dp_queries.dp_libraries.opendp.OpenDPQuerier.cost", false]], "cost() (methode von lomas_server.dp_queries.dp_libraries.smartnoise_sql.smartnoisesqlquerier)": [[14, "lomas_server.dp_queries.dp_libraries.smartnoise_sql.SmartnoiseSQLQuerier.cost", false]], "cost() (methode von lomas_server.dp_queries.dp_querier.dpquerier)": [[13, "lomas_server.dp_queries.dp_querier.DPQuerier.cost", false]], "database_factory() (im modul lomas_server.admin_database.utils)": [[10, "lomas_server.admin_database.utils.database_factory", false]], "database_type (attribut von lomas_server.utils.collections_models.dataset)": [[17, "lomas_server.utils.collections_models.Dataset.database_type", false]], "database_type (attribut von lomas_server.utils.collections_models.metadataofdataset)": [[17, "lomas_server.utils.collections_models.MetadataOfDataset.database_type", false]], "dataset (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.Dataset", false]], "dataset_cache (attribut von lomas_server.dataset_store.lru_dataset_store.lrudatasetstore)": [[12, "lomas_server.dataset_store.lru_dataset_store.LRUDatasetStore.dataset_cache", false]], "dataset_must_exist() (im modul lomas_server.admin_database.admin_database)": [[10, "lomas_server.admin_database.admin_database.dataset_must_exist", false]], "dataset_name (attribut von lomas_server.utils.collections_models.dataset)": [[17, "lomas_server.utils.collections_models.Dataset.dataset_name", false]], "dataset_name (attribut von lomas_server.utils.collections_models.datasetofuser)": [[17, "lomas_server.utils.collections_models.DatasetOfUser.dataset_name", false]], "dataset_name (attribut von lomas_server.utils.input_models.dummyopendpinp)": [[17, "lomas_server.utils.input_models.DummyOpenDPInp.dataset_name", false]], "dataset_name (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.dataset_name", false]], "dataset_name (attribut von lomas_server.utils.input_models.getdbdata)": [[17, "lomas_server.utils.input_models.GetDbData.dataset_name", false]], "dataset_name (attribut von lomas_server.utils.input_models.getdummydataset)": [[17, "lomas_server.utils.input_models.GetDummyDataset.dataset_name", false]], "dataset_name (attribut von lomas_server.utils.input_models.opendpinp)": [[17, "lomas_server.utils.input_models.OpenDPInp.dataset_name", false]], "dataset_name (attribut von lomas_server.utils.input_models.snsqlinp)": [[17, "lomas_server.utils.input_models.SNSQLInp.dataset_name", false]], "dataset_name (attribut von lomas_server.utils.input_models.snsqlinpcost)": [[17, "lomas_server.utils.input_models.SNSQLInpCost.dataset_name", false]], "dataset_path (attribut von lomas_server.utils.collections_models.datasetofpathdb)": [[17, "lomas_server.utils.collections_models.DatasetOfPathDB.dataset_path", false]], "dataset_store (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.DATASET_STORE", false]], "dataset_store (attribut von lomas_server.dp_queries.dp_logic.queryhandler)": [[13, "lomas_server.dp_queries.dp_logic.QueryHandler.dataset_store", false]], "dataset_store (attribut von lomas_server.utils.config.config)": [[17, "lomas_server.utils.config.Config.dataset_store", false]], "dataset_store_factory() (im modul lomas_server.dataset_store.utils)": [[12, "lomas_server.dataset_store.utils.dataset_store_factory", false]], "dataset_store_type (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.DATASET_STORE_TYPE", false]], "datasetofpathdb (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.DatasetOfPathDB", false]], "datasetofs3db (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.DatasetOfS3DB", false]], "datasetofuser (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.DatasetOfUser", false]], "datasets (attribut von lomas_server.utils.collections_models.datasetscollection)": [[17, "lomas_server.utils.collections_models.DatasetsCollection.datasets", false]], "datasets_list (attribut von lomas_server.utils.collections_models.user)": [[17, "lomas_server.utils.collections_models.User.datasets_list", false]], "datasetscollection (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.DatasetsCollection", false]], "datasetstore (klasse in lomas_server.dataset_store.dataset_store)": [[12, "lomas_server.dataset_store.dataset_store.DatasetStore", false]], "datasetstoreconfig (klasse in lomas_server.utils.config)": [[17, "lomas_server.utils.config.DatasetStoreConfig", false]], "datasetstoretype (klasse in lomas_server.constants)": [[9, "lomas_server.constants.DatasetStoreType", false]], "db (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.DB", false]], "db_file (attribut von lomas_server.utils.config.yamldbconfig)": [[17, "lomas_server.utils.config.YamlDBConfig.db_file", false]], "db_name (attribut von lomas_server.utils.config.mongodbconfig)": [[17, "lomas_server.utils.config.MongoDBConfig.db_name", false]], "db_type (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.DB_TYPE", false]], "db_type (attribut von lomas_server.utils.config.dbconfig)": [[17, "lomas_server.utils.config.DBConfig.db_type", false]], "db_type_mongodb (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.DB_TYPE_MONGODB", false]], "dbconfig (klasse in lomas_server.utils.config)": [[17, "lomas_server.utils.config.DBConfig", false]], "del_dataset() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.del_dataset", false]], "del_dataset_to_user() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.del_dataset_to_user", false]], "del_user() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.del_user", false]], "delta (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.delta", false]], "delta (attribut von lomas_server.utils.input_models.snsqlinp)": [[17, "lomas_server.utils.input_models.SNSQLInp.delta", false]], "delta (attribut von lomas_server.utils.input_models.snsqlinpcost)": [[17, "lomas_server.utils.input_models.SNSQLInpCost.delta", false]], "develop_mode (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.DEVELOP_MODE", false]], "develop_mode (attribut von lomas_server.utils.config.config)": [[17, "lomas_server.utils.config.Config.develop_mode", false]], "df (attribut von lomas_server.private_dataset.private_dataset.privatedataset)": [[15, "lomas_server.private_dataset.private_dataset.PrivateDataset.df", false]], "does_dataset_exist() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.does_dataset_exist", false]], "does_dataset_exist() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.does_dataset_exist", false]], "does_dataset_exist() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.does_dataset_exist", false]], "does_user_exist() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.does_user_exist", false]], "does_user_exist() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.does_user_exist", false]], "does_user_exist() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.does_user_exist", false]], "dp_queriers (attribut von lomas_server.dataset_store.basic_dataset_store.basicdatasetstore)": [[12, "lomas_server.dataset_store.basic_dataset_store.BasicDatasetStore.dp_queriers", false]], "dplibraries (klasse in lomas_client.client)": [[8, "lomas_client.client.DPLibraries", false]], "dplibraries (klasse in lomas_server.constants)": [[9, "lomas_server.constants.DPLibraries", false]], "dpquerier (klasse in lomas_server.dp_queries.dp_querier)": [[13, "lomas_server.dp_queries.dp_querier.DPQuerier", false]], "drop_collection() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.drop_collection", false]], "ds_store_type (attribut von lomas_server.utils.config.datasetstoreconfig)": [[17, "lomas_server.utils.config.DatasetStoreConfig.ds_store_type", false]], "dummy_nb_rows (attribut von lomas_server.utils.input_models.dummyopendpinp)": [[17, "lomas_server.utils.input_models.DummyOpenDPInp.dummy_nb_rows", false]], "dummy_nb_rows (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.dummy_nb_rows", false]], "dummy_nb_rows (attribut von lomas_server.utils.input_models.getdummydataset)": [[17, "lomas_server.utils.input_models.GetDummyDataset.dummy_nb_rows", false]], "dummy_opendp_query_handler() (im modul lomas_server.app)": [[9, "lomas_server.app.dummy_opendp_query_handler", false]], "dummy_seed (attribut von lomas_server.utils.input_models.dummyopendpinp)": [[17, "lomas_server.utils.input_models.DummyOpenDPInp.dummy_seed", false]], "dummy_seed (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.dummy_seed", false]], "dummy_seed (attribut von lomas_server.utils.input_models.getdummydataset)": [[17, "lomas_server.utils.input_models.GetDummyDataset.dummy_seed", false]], "dummy_smartnoise_sql_handler() (im modul lomas_server.app)": [[9, "lomas_server.app.dummy_smartnoise_sql_handler", false]], "dummyopendpinp (klasse in lomas_server.utils.input_models)": [[17, "lomas_server.utils.input_models.DummyOpenDPInp", false]], "dummysnsqlinp (klasse in lomas_server.utils.input_models)": [[17, "lomas_server.utils.input_models.DummySNSQLInp", false]], "endpoint_url (attribut von lomas_server.utils.collections_models.datasetofs3db)": [[17, "lomas_server.utils.collections_models.DatasetOfS3DB.endpoint_url", false]], "endpoint_url (attribut von lomas_server.utils.collections_models.metadataofs3db)": [[17, "lomas_server.utils.collections_models.MetadataOfS3DB.endpoint_url", false]], "epsilon (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.epsilon", false]], "epsilon (attribut von lomas_server.utils.input_models.snsqlinp)": [[17, "lomas_server.utils.input_models.SNSQLInp.epsilon", false]], "epsilon (attribut von lomas_server.utils.input_models.snsqlinpcost)": [[17, "lomas_server.utils.input_models.SNSQLInpCost.epsilon", false]], "error_message() (im modul lomas_client.client)": [[8, "lomas_client.client.error_message", false]], "estimate_cost() (methode von lomas_server.dp_queries.dp_logic.queryhandler)": [[13, "lomas_server.dp_queries.dp_logic.QueryHandler.estimate_cost", false]], "estimate_opendp_cost() (im modul lomas_server.app)": [[9, "lomas_server.app.estimate_opendp_cost", false]], "estimate_opendp_cost() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.estimate_opendp_cost", false]], "estimate_smartnoise_cost() (im modul lomas_server.app)": [[9, "lomas_server.app.estimate_smartnoise_cost", false]], "estimate_smartnoise_cost() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.estimate_smartnoise_cost", false]], "externallibraryexception": [[17, "lomas_server.utils.error_handler.ExternalLibraryException", false]], "fixed_delta (attribut von lomas_server.utils.input_models.dummyopendpinp)": [[17, "lomas_server.utils.input_models.DummyOpenDPInp.fixed_delta", false]], "fixed_delta (attribut von lomas_server.utils.input_models.opendpinp)": [[17, "lomas_server.utils.input_models.OpenDPInp.fixed_delta", false]], "fixed_smoothed_max_divergence (attribut von lomas_server.constants.opendpmeasurement)": [[9, "lomas_server.constants.OpenDPMeasurement.FIXED_SMOOTHED_MAX_DIVERGENCE", false]], "get_and_set_may_user_query() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.get_and_set_may_user_query", false]], "get_and_set_may_user_query() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.get_and_set_may_user_query", false]], "get_and_set_may_user_query() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.get_and_set_may_user_query", false]], "get_config() (im modul lomas_server.utils.config)": [[17, "lomas_server.utils.config.get_config", false]], "get_config() (methode von lomas_server.utils.config.configloader)": [[17, "lomas_server.utils.config.ConfigLoader.get_config", false]], "get_dataset_field() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.get_dataset_field", false]], "get_dataset_field() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.get_dataset_field", false]], "get_dataset_field() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.get_dataset_field", false]], "get_dataset_metadata() (im modul lomas_server.app)": [[9, "lomas_server.app.get_dataset_metadata", false]], "get_dataset_metadata() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.get_dataset_metadata", false]], "get_dataset_metadata() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.get_dataset_metadata", false]], "get_dataset_metadata() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.get_dataset_metadata", false]], "get_dataset_metadata() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.get_dataset_metadata", false]], "get_dummy_dataset() (im modul lomas_server.app)": [[9, "lomas_server.app.get_dummy_dataset", false]], "get_dummy_dataset() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.get_dummy_dataset", false]], "get_dummy_dataset_for_query() (im modul lomas_server.dp_queries.dummy_dataset)": [[13, "lomas_server.dp_queries.dummy_dataset.get_dummy_dataset_for_query", false]], "get_epsilon_or_delta() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.get_epsilon_or_delta", false]], "get_epsilon_or_delta() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.get_epsilon_or_delta", false]], "get_epsilon_or_delta() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.get_epsilon_or_delta", false]], "get_initial_budget() (im modul lomas_server.app)": [[9, "lomas_server.app.get_initial_budget", false]], "get_initial_budget() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.get_initial_budget", false]], "get_initial_budget() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.get_initial_budget", false]], "get_list_of_datasets() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.get_list_of_datasets", false]], "get_list_of_datasets_from_user() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.get_list_of_datasets_from_user", false]], "get_list_of_users() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.get_list_of_users", false]], "get_memory_usage() (im modul lomas_server.app)": [[9, "lomas_server.app.get_memory_usage", false]], "get_memory_usage() (methode von lomas_server.private_dataset.private_dataset.privatedataset)": [[15, "lomas_server.private_dataset.private_dataset.PrivateDataset.get_memory_usage", false]], "get_metadata() (methode von lomas_server.private_dataset.private_dataset.privatedataset)": [[15, "lomas_server.private_dataset.private_dataset.PrivateDataset.get_metadata", false]], "get_mongodb() (im modul lomas_server.admin_database.utils)": [[10, "lomas_server.admin_database.utils.get_mongodb", false]], "get_mongodb_url() (im modul lomas_server.admin_database.utils)": [[10, "lomas_server.admin_database.utils.get_mongodb_url", false]], "get_output_measure() (im modul lomas_server.dp_queries.dp_libraries.opendp)": [[14, "lomas_server.dp_queries.dp_libraries.opendp.get_output_measure", false]], "get_pandas_df() (methode von lomas_server.private_dataset.in_memory_dataset.inmemorydataset)": [[15, "lomas_server.private_dataset.in_memory_dataset.InMemoryDataset.get_pandas_df", false]], "get_pandas_df() (methode von lomas_server.private_dataset.path_dataset.pathdataset)": [[15, "lomas_server.private_dataset.path_dataset.PathDataset.get_pandas_df", false]], "get_pandas_df() (methode von lomas_server.private_dataset.private_dataset.privatedataset)": [[15, "lomas_server.private_dataset.private_dataset.PrivateDataset.get_pandas_df", false]], "get_pandas_df() (methode von lomas_server.private_dataset.s3_dataset.s3dataset)": [[15, "lomas_server.private_dataset.s3_dataset.S3Dataset.get_pandas_df", false]], "get_previous_queries() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.get_previous_queries", false]], "get_querier() (methode von lomas_server.dataset_store.basic_dataset_store.basicdatasetstore)": [[12, "lomas_server.dataset_store.basic_dataset_store.BasicDatasetStore.get_querier", false]], "get_querier() (methode von lomas_server.dataset_store.dataset_store.datasetstore)": [[12, "lomas_server.dataset_store.dataset_store.DatasetStore.get_querier", false]], "get_querier() (methode von lomas_server.dataset_store.lru_dataset_store.lrudatasetstore)": [[12, "lomas_server.dataset_store.lru_dataset_store.LRUDatasetStore.get_querier", false]], "get_remaining_budget() (im modul lomas_server.app)": [[9, "lomas_server.app.get_remaining_budget", false]], "get_remaining_budget() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.get_remaining_budget", false]], "get_remaining_budget() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.get_remaining_budget", false]], "get_state() (im modul lomas_server.app)": [[9, "lomas_server.app.get_state", false]], "get_total_spent_budget() (im modul lomas_server.app)": [[9, "lomas_server.app.get_total_spent_budget", false]], "get_total_spent_budget() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.get_total_spent_budget", false]], "get_total_spent_budget() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.get_total_spent_budget", false]], "get_user_previous_queries() (im modul lomas_server.app)": [[9, "lomas_server.app.get_user_previous_queries", false]], "get_user_previous_queries() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.get_user_previous_queries", false]], "get_user_previous_queries() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.get_user_previous_queries", false]], "get_user_previous_queries() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.get_user_previous_queries", false]], "getdbdata (klasse in lomas_server.utils.input_models)": [[17, "lomas_server.utils.input_models.GetDbData", false]], "getdummydataset (klasse in lomas_server.utils.input_models)": [[17, "lomas_server.utils.input_models.GetDummyDataset", false]], "handle_query() (methode von lomas_server.dp_queries.dp_logic.queryhandler)": [[13, "lomas_server.dp_queries.dp_logic.QueryHandler.handle_query", false]], "has_user_access_to_dataset() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.has_user_access_to_dataset", false]], "has_user_access_to_dataset() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.has_user_access_to_dataset", false]], "has_user_access_to_dataset() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.has_user_access_to_dataset", false]], "host_ip (attribut von lomas_server.utils.config.server)": [[17, "lomas_server.utils.config.Server.host_ip", false]], "host_port (attribut von lomas_server.utils.config.server)": [[17, "lomas_server.utils.config.Server.host_port", false]], "initial_delta (attribut von lomas_server.utils.collections_models.datasetofuser)": [[17, "lomas_server.utils.collections_models.DatasetOfUser.initial_delta", false]], "initial_epsilon (attribut von lomas_server.utils.collections_models.datasetofuser)": [[17, "lomas_server.utils.collections_models.DatasetOfUser.initial_epsilon", false]], "inmemorydataset (klasse in lomas_server.private_dataset.in_memory_dataset)": [[15, "lomas_server.private_dataset.in_memory_dataset.InMemoryDataset", false]], "internalserverexception": [[17, "lomas_server.utils.error_handler.InternalServerException", false]], "invalidqueryexception": [[17, "lomas_server.utils.error_handler.InvalidQueryException", false]], "is_measurement() (im modul lomas_server.dp_queries.dp_libraries.opendp)": [[14, "lomas_server.dp_queries.dp_libraries.opendp.is_measurement", false]], "jitter (attribut von lomas_server.constants.timeattackmethod)": [[9, "lomas_server.constants.TimeAttackMethod.JITTER", false]], "lifespan() (im modul lomas_server.app)": [[9, "lomas_server.app.lifespan", false]], "load_config() (methode von lomas_server.utils.config.configloader)": [[17, "lomas_server.utils.config.ConfigLoader.load_config", false]], "log_level (attribut von lomas_server.utils.config.server)": [[17, "lomas_server.utils.config.Server.log_level", false]], "lomas_client": [[8, "module-lomas_client", false]], "lomas_client.client": [[8, "module-lomas_client.client", false]], "lomas_server": [[9, "module-lomas_server", false]], "lomas_server.admin_database": [[10, "module-lomas_server.admin_database", false]], "lomas_server.admin_database.admin_database": [[10, "module-lomas_server.admin_database.admin_database", false]], "lomas_server.admin_database.mongodb_database": [[10, "module-lomas_server.admin_database.mongodb_database", false]], "lomas_server.admin_database.utils": [[10, "module-lomas_server.admin_database.utils", false]], "lomas_server.admin_database.yaml_database": [[10, "module-lomas_server.admin_database.yaml_database", false]], "lomas_server.administration": [[11, "module-lomas_server.administration", false]], "lomas_server.app": [[9, "module-lomas_server.app", false]], "lomas_server.constants": [[9, "module-lomas_server.constants", false]], "lomas_server.dataset_store": [[12, "module-lomas_server.dataset_store", false]], "lomas_server.dataset_store.basic_dataset_store": [[12, "module-lomas_server.dataset_store.basic_dataset_store", false]], "lomas_server.dataset_store.dataset_store": [[12, "module-lomas_server.dataset_store.dataset_store", false]], "lomas_server.dataset_store.lru_dataset_store": [[12, "module-lomas_server.dataset_store.lru_dataset_store", false]], "lomas_server.dataset_store.private_dataset_observer": [[12, "module-lomas_server.dataset_store.private_dataset_observer", false]], "lomas_server.dataset_store.utils": [[12, "module-lomas_server.dataset_store.utils", false]], "lomas_server.dp_queries": [[13, "module-lomas_server.dp_queries", false]], "lomas_server.dp_queries.dp_libraries": [[14, "module-lomas_server.dp_queries.dp_libraries", false]], "lomas_server.dp_queries.dp_libraries.opendp": [[14, "module-lomas_server.dp_queries.dp_libraries.opendp", false]], "lomas_server.dp_queries.dp_libraries.smartnoise_sql": [[14, "module-lomas_server.dp_queries.dp_libraries.smartnoise_sql", false]], "lomas_server.dp_queries.dp_libraries.utils": [[14, "module-lomas_server.dp_queries.dp_libraries.utils", false]], "lomas_server.dp_queries.dp_logic": [[13, "module-lomas_server.dp_queries.dp_logic", false]], "lomas_server.dp_queries.dp_querier": [[13, "module-lomas_server.dp_queries.dp_querier", false]], "lomas_server.dp_queries.dummy_dataset": [[13, "module-lomas_server.dp_queries.dummy_dataset", false]], "lomas_server.mongodb_admin": [[9, "module-lomas_server.mongodb_admin", false]], "lomas_server.mongodb_admin_cli": [[9, "module-lomas_server.mongodb_admin_cli", false]], "lomas_server.private_dataset": [[15, "module-lomas_server.private_dataset", false]], "lomas_server.private_dataset.in_memory_dataset": [[15, "module-lomas_server.private_dataset.in_memory_dataset", false]], "lomas_server.private_dataset.path_dataset": [[15, "module-lomas_server.private_dataset.path_dataset", false]], "lomas_server.private_dataset.private_dataset": [[15, "module-lomas_server.private_dataset.private_dataset", false]], "lomas_server.private_dataset.s3_dataset": [[15, "module-lomas_server.private_dataset.s3_dataset", false]], "lomas_server.private_dataset.utils": [[15, "module-lomas_server.private_dataset.utils", false]], "lomas_server.tests": [[16, "module-lomas_server.tests", false]], "lomas_server.tests.constants": [[16, "module-lomas_server.tests.constants", false]], "lomas_server.tests.test_api": [[16, "module-lomas_server.tests.test_api", false]], "lomas_server.tests.test_dummy_generation": [[16, "module-lomas_server.tests.test_dummy_generation", false]], "lomas_server.tests.test_mongodb_admin": [[16, "module-lomas_server.tests.test_mongodb_admin", false]], "lomas_server.utils": [[17, "module-lomas_server.utils", false]], "lomas_server.utils.anti_timing_att": [[17, "module-lomas_server.utils.anti_timing_att", false]], "lomas_server.utils.collections_models": [[17, "module-lomas_server.utils.collections_models", false]], "lomas_server.utils.config": [[17, "module-lomas_server.utils.config", false]], "lomas_server.utils.error_handler": [[17, "module-lomas_server.utils.error_handler", false]], "lomas_server.utils.example_inputs": [[17, "module-lomas_server.utils.example_inputs", false]], "lomas_server.utils.input_models": [[17, "module-lomas_server.utils.input_models", false]], "lomas_server.utils.loggr": [[17, "module-lomas_server.utils.loggr", false]], "lomas_server.utils.utils": [[17, "module-lomas_server.utils.utils", false]], "lomas_server.uvicorn_serve": [[9, "module-lomas_server.uvicorn_serve", false]], "lru (attribut von lomas_server.constants.datasetstoretype)": [[9, "lomas_server.constants.DatasetStoreType.LRU", false]], "lru_dataset_store_max_size (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.LRU_DATASET_STORE_MAX_SIZE", false]], "lrudatasetstore (klasse in lomas_server.dataset_store.lru_dataset_store)": [[12, "lomas_server.dataset_store.lru_dataset_store.LRUDatasetStore", false]], "lrudatasetstoreconfig (klasse in lomas_server.utils.config)": [[17, "lomas_server.utils.config.LRUDatasetStoreConfig", false]], "magnitude (attribut von lomas_server.utils.config.timeattack)": [[17, "lomas_server.utils.config.TimeAttack.magnitude", false]], "make_dummy_dataset() (im modul lomas_server.dp_queries.dummy_dataset)": [[13, "lomas_server.dp_queries.dummy_dataset.make_dummy_dataset", false]], "max_divergence (attribut von lomas_server.constants.opendpmeasurement)": [[9, "lomas_server.constants.OpenDPMeasurement.MAX_DIVERGENCE", false]], "max_ids (attribut von lomas_server.utils.collections_models.metadata)": [[17, "lomas_server.utils.collections_models.Metadata.max_ids", false]], "max_memory_usage (attribut von lomas_server.utils.config.lrudatasetstoreconfig)": [[17, "lomas_server.utils.config.LRUDatasetStoreConfig.max_memory_usage", false]], "may_query (attribut von lomas_server.utils.collections_models.user)": [[17, "lomas_server.utils.collections_models.User.may_query", false]], "may_user_query() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.may_user_query", false]], "may_user_query() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.may_user_query", false]], "may_user_query() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.may_user_query", false]], "mechanisms (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.mechanisms", false]], "mechanisms (attribut von lomas_server.utils.input_models.snsqlinp)": [[17, "lomas_server.utils.input_models.SNSQLInp.mechanisms", false]], "mechanisms (attribut von lomas_server.utils.input_models.snsqlinpcost)": [[17, "lomas_server.utils.input_models.SNSQLInpCost.mechanisms", false]], "metadata (attribut von lomas_server.utils.collections_models.dataset)": [[17, "lomas_server.utils.collections_models.Dataset.metadata", false]], "metadata (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.Metadata", false]], "metadata_path (attribut von lomas_server.utils.collections_models.metadataofpathdb)": [[17, "lomas_server.utils.collections_models.MetadataOfPathDB.metadata_path", false]], "metadataofdataset (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.MetadataOfDataset", false]], "metadataofpathdb (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.MetadataOfPathDB", false]], "metadataofs3db (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.MetadataOfS3DB", false]], "method (attribut von lomas_server.utils.config.timeattack)": [[17, "lomas_server.utils.config.TimeAttack.method", false]], "middleware() (im modul lomas_server.app)": [[9, "lomas_server.app.middleware", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.dataset)": [[17, "lomas_server.utils.collections_models.Dataset.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.datasetofpathdb)": [[17, "lomas_server.utils.collections_models.DatasetOfPathDB.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.datasetofs3db)": [[17, "lomas_server.utils.collections_models.DatasetOfS3DB.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.datasetofuser)": [[17, "lomas_server.utils.collections_models.DatasetOfUser.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.datasetscollection)": [[17, "lomas_server.utils.collections_models.DatasetsCollection.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.metadata)": [[17, "lomas_server.utils.collections_models.Metadata.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.metadataofdataset)": [[17, "lomas_server.utils.collections_models.MetadataOfDataset.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.metadataofpathdb)": [[17, "lomas_server.utils.collections_models.MetadataOfPathDB.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.metadataofs3db)": [[17, "lomas_server.utils.collections_models.MetadataOfS3DB.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.user)": [[17, "lomas_server.utils.collections_models.User.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.collections_models.usercollection)": [[17, "lomas_server.utils.collections_models.UserCollection.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.config.config)": [[17, "lomas_server.utils.config.Config.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.config.datasetstoreconfig)": [[17, "lomas_server.utils.config.DatasetStoreConfig.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.config.dbconfig)": [[17, "lomas_server.utils.config.DBConfig.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.config.lrudatasetstoreconfig)": [[17, "lomas_server.utils.config.LRUDatasetStoreConfig.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.config.mongodbconfig)": [[17, "lomas_server.utils.config.MongoDBConfig.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.config.server)": [[17, "lomas_server.utils.config.Server.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.config.timeattack)": [[17, "lomas_server.utils.config.TimeAttack.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.config.yamldbconfig)": [[17, "lomas_server.utils.config.YamlDBConfig.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.input_models.dummyopendpinp)": [[17, "lomas_server.utils.input_models.DummyOpenDPInp.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.input_models.getdbdata)": [[17, "lomas_server.utils.input_models.GetDbData.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.input_models.getdummydataset)": [[17, "lomas_server.utils.input_models.GetDummyDataset.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.input_models.opendpinp)": [[17, "lomas_server.utils.input_models.OpenDPInp.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.input_models.snsqlinp)": [[17, "lomas_server.utils.input_models.SNSQLInp.model_computed_fields", false]], "model_computed_fields (attribut von lomas_server.utils.input_models.snsqlinpcost)": [[17, "lomas_server.utils.input_models.SNSQLInpCost.model_computed_fields", false]], "model_config (attribut von lomas_server.utils.collections_models.dataset)": [[17, "lomas_server.utils.collections_models.Dataset.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.datasetofpathdb)": [[17, "lomas_server.utils.collections_models.DatasetOfPathDB.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.datasetofs3db)": [[17, "lomas_server.utils.collections_models.DatasetOfS3DB.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.datasetofuser)": [[17, "lomas_server.utils.collections_models.DatasetOfUser.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.datasetscollection)": [[17, "lomas_server.utils.collections_models.DatasetsCollection.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.metadata)": [[17, "lomas_server.utils.collections_models.Metadata.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.metadataofdataset)": [[17, "lomas_server.utils.collections_models.MetadataOfDataset.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.metadataofpathdb)": [[17, "lomas_server.utils.collections_models.MetadataOfPathDB.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.metadataofs3db)": [[17, "lomas_server.utils.collections_models.MetadataOfS3DB.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.user)": [[17, "lomas_server.utils.collections_models.User.model_config", false]], "model_config (attribut von lomas_server.utils.collections_models.usercollection)": [[17, "lomas_server.utils.collections_models.UserCollection.model_config", false]], "model_config (attribut von lomas_server.utils.config.config)": [[17, "lomas_server.utils.config.Config.model_config", false]], "model_config (attribut von lomas_server.utils.config.datasetstoreconfig)": [[17, "lomas_server.utils.config.DatasetStoreConfig.model_config", false]], "model_config (attribut von lomas_server.utils.config.dbconfig)": [[17, "lomas_server.utils.config.DBConfig.model_config", false]], "model_config (attribut von lomas_server.utils.config.lrudatasetstoreconfig)": [[17, "lomas_server.utils.config.LRUDatasetStoreConfig.model_config", false]], "model_config (attribut von lomas_server.utils.config.mongodbconfig)": [[17, "lomas_server.utils.config.MongoDBConfig.model_config", false]], "model_config (attribut von lomas_server.utils.config.server)": [[17, "lomas_server.utils.config.Server.model_config", false]], "model_config (attribut von lomas_server.utils.config.timeattack)": [[17, "lomas_server.utils.config.TimeAttack.model_config", false]], "model_config (attribut von lomas_server.utils.config.yamldbconfig)": [[17, "lomas_server.utils.config.YamlDBConfig.model_config", false]], "model_config (attribut von lomas_server.utils.input_models.dummyopendpinp)": [[17, "lomas_server.utils.input_models.DummyOpenDPInp.model_config", false]], "model_config (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.model_config", false]], "model_config (attribut von lomas_server.utils.input_models.getdbdata)": [[17, "lomas_server.utils.input_models.GetDbData.model_config", false]], "model_config (attribut von lomas_server.utils.input_models.getdummydataset)": [[17, "lomas_server.utils.input_models.GetDummyDataset.model_config", false]], "model_config (attribut von lomas_server.utils.input_models.opendpinp)": [[17, "lomas_server.utils.input_models.OpenDPInp.model_config", false]], "model_config (attribut von lomas_server.utils.input_models.snsqlinp)": [[17, "lomas_server.utils.input_models.SNSQLInp.model_config", false]], "model_config (attribut von lomas_server.utils.input_models.snsqlinpcost)": [[17, "lomas_server.utils.input_models.SNSQLInpCost.model_config", false]], "model_fields (attribut von lomas_server.utils.collections_models.dataset)": [[17, "lomas_server.utils.collections_models.Dataset.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.datasetofpathdb)": [[17, "lomas_server.utils.collections_models.DatasetOfPathDB.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.datasetofs3db)": [[17, "lomas_server.utils.collections_models.DatasetOfS3DB.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.datasetofuser)": [[17, "lomas_server.utils.collections_models.DatasetOfUser.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.datasetscollection)": [[17, "lomas_server.utils.collections_models.DatasetsCollection.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.metadata)": [[17, "lomas_server.utils.collections_models.Metadata.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.metadataofdataset)": [[17, "lomas_server.utils.collections_models.MetadataOfDataset.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.metadataofpathdb)": [[17, "lomas_server.utils.collections_models.MetadataOfPathDB.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.metadataofs3db)": [[17, "lomas_server.utils.collections_models.MetadataOfS3DB.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.user)": [[17, "lomas_server.utils.collections_models.User.model_fields", false]], "model_fields (attribut von lomas_server.utils.collections_models.usercollection)": [[17, "lomas_server.utils.collections_models.UserCollection.model_fields", false]], "model_fields (attribut von lomas_server.utils.config.config)": [[17, "lomas_server.utils.config.Config.model_fields", false]], "model_fields (attribut von lomas_server.utils.config.datasetstoreconfig)": [[17, "lomas_server.utils.config.DatasetStoreConfig.model_fields", false]], "model_fields (attribut von lomas_server.utils.config.dbconfig)": [[17, "lomas_server.utils.config.DBConfig.model_fields", false]], "model_fields (attribut von lomas_server.utils.config.lrudatasetstoreconfig)": [[17, "lomas_server.utils.config.LRUDatasetStoreConfig.model_fields", false]], "model_fields (attribut von lomas_server.utils.config.mongodbconfig)": [[17, "lomas_server.utils.config.MongoDBConfig.model_fields", false]], "model_fields (attribut von lomas_server.utils.config.server)": [[17, "lomas_server.utils.config.Server.model_fields", false]], "model_fields (attribut von lomas_server.utils.config.timeattack)": [[17, "lomas_server.utils.config.TimeAttack.model_fields", false]], "model_fields (attribut von lomas_server.utils.config.yamldbconfig)": [[17, "lomas_server.utils.config.YamlDBConfig.model_fields", false]], "model_fields (attribut von lomas_server.utils.input_models.dummyopendpinp)": [[17, "lomas_server.utils.input_models.DummyOpenDPInp.model_fields", false]], "model_fields (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.model_fields", false]], "model_fields (attribut von lomas_server.utils.input_models.getdbdata)": [[17, "lomas_server.utils.input_models.GetDbData.model_fields", false]], "model_fields (attribut von lomas_server.utils.input_models.getdummydataset)": [[17, "lomas_server.utils.input_models.GetDummyDataset.model_fields", false]], "model_fields (attribut von lomas_server.utils.input_models.opendpinp)": [[17, "lomas_server.utils.input_models.OpenDPInp.model_fields", false]], "model_fields (attribut von lomas_server.utils.input_models.snsqlinp)": [[17, "lomas_server.utils.input_models.SNSQLInp.model_fields", false]], "model_fields (attribut von lomas_server.utils.input_models.snsqlinpcost)": [[17, "lomas_server.utils.input_models.SNSQLInpCost.model_fields", false]], "module": [[8, "module-lomas_client", false], [8, "module-lomas_client.client", false], [9, "module-lomas_server", false], [9, "module-lomas_server.app", false], [9, "module-lomas_server.constants", false], [9, "module-lomas_server.mongodb_admin", false], [9, "module-lomas_server.mongodb_admin_cli", false], [9, "module-lomas_server.uvicorn_serve", false], [10, "module-lomas_server.admin_database", false], [10, "module-lomas_server.admin_database.admin_database", false], [10, "module-lomas_server.admin_database.mongodb_database", false], [10, "module-lomas_server.admin_database.utils", false], [10, "module-lomas_server.admin_database.yaml_database", false], [11, "module-lomas_server.administration", false], [12, "module-lomas_server.dataset_store", false], [12, "module-lomas_server.dataset_store.basic_dataset_store", false], [12, "module-lomas_server.dataset_store.dataset_store", false], [12, "module-lomas_server.dataset_store.lru_dataset_store", false], [12, "module-lomas_server.dataset_store.private_dataset_observer", false], [12, "module-lomas_server.dataset_store.utils", false], [13, "module-lomas_server.dp_queries", false], [13, "module-lomas_server.dp_queries.dp_logic", false], [13, "module-lomas_server.dp_queries.dp_querier", false], [13, "module-lomas_server.dp_queries.dummy_dataset", false], [14, "module-lomas_server.dp_queries.dp_libraries", false], [14, "module-lomas_server.dp_queries.dp_libraries.opendp", false], [14, "module-lomas_server.dp_queries.dp_libraries.smartnoise_sql", false], [14, "module-lomas_server.dp_queries.dp_libraries.utils", false], [15, "module-lomas_server.private_dataset", false], [15, "module-lomas_server.private_dataset.in_memory_dataset", false], [15, "module-lomas_server.private_dataset.path_dataset", false], [15, "module-lomas_server.private_dataset.private_dataset", false], [15, "module-lomas_server.private_dataset.s3_dataset", false], [15, "module-lomas_server.private_dataset.utils", false], [16, "module-lomas_server.tests", false], [16, "module-lomas_server.tests.constants", false], [16, "module-lomas_server.tests.test_api", false], [16, "module-lomas_server.tests.test_dummy_generation", false], [16, "module-lomas_server.tests.test_mongodb_admin", false], [17, "module-lomas_server.utils", false], [17, "module-lomas_server.utils.anti_timing_att", false], [17, "module-lomas_server.utils.collections_models", false], [17, "module-lomas_server.utils.config", false], [17, "module-lomas_server.utils.error_handler", false], [17, "module-lomas_server.utils.example_inputs", false], [17, "module-lomas_server.utils.input_models", false], [17, "module-lomas_server.utils.loggr", false], [17, "module-lomas_server.utils.utils", false]], "mongodb (attribut von lomas_server.constants.admindbtype)": [[9, "lomas_server.constants.AdminDBType.MONGODB", false]], "mongodb_addr (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.MONGODB_ADDR", false]], "mongodb_port (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.MONGODB_PORT", false]], "mongodbconfig (klasse in lomas_server.utils.config)": [[17, "lomas_server.utils.config.MongoDBConfig", false]], "opendp (attribut von lomas_client.client.dplibraries)": [[8, "lomas_client.client.DPLibraries.OPENDP", false]], "opendp (attribut von lomas_server.constants.dplibraries)": [[9, "lomas_server.constants.DPLibraries.OPENDP", false]], "opendp_json (attribut von lomas_server.utils.input_models.dummyopendpinp)": [[17, "lomas_server.utils.input_models.DummyOpenDPInp.opendp_json", false]], "opendp_json (attribut von lomas_server.utils.input_models.opendpinp)": [[17, "lomas_server.utils.input_models.OpenDPInp.opendp_json", false]], "opendp_query() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.opendp_query", false]], "opendp_query_handler() (im modul lomas_server.app)": [[9, "lomas_server.app.opendp_query_handler", false]], "opendpinp (klasse in lomas_server.utils.input_models)": [[17, "lomas_server.utils.input_models.OpenDPInp", false]], "opendpmeasurement (klasse in lomas_server.constants)": [[9, "lomas_server.constants.OpenDPMeasurement", false]], "opendpquerier (klasse in lomas_server.dp_queries.dp_libraries.opendp)": [[14, "lomas_server.dp_queries.dp_libraries.opendp.OpenDPQuerier", false]], "password (attribut von lomas_server.utils.config.mongodbconfig)": [[17, "lomas_server.utils.config.MongoDBConfig.password", false]], "path (attribut von lomas_server.constants.privatedatabasetype)": [[9, "lomas_server.constants.PrivateDatabaseType.PATH", false]], "pathdataset (klasse in lomas_server.private_dataset.path_dataset)": [[15, "lomas_server.private_dataset.path_dataset.PathDataset", false]], "port (attribut von lomas_server.utils.config.mongodbconfig)": [[17, "lomas_server.utils.config.MongoDBConfig.port", false]], "postprocess (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.postprocess", false]], "postprocess (attribut von lomas_server.utils.input_models.snsqlinp)": [[17, "lomas_server.utils.input_models.SNSQLInp.postprocess", false]], "prepare_save_query() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.prepare_save_query", false]], "private_dataset_factory() (im modul lomas_server.private_dataset.utils)": [[15, "lomas_server.private_dataset.utils.private_dataset_factory", false]], "privatedatabasetype (klasse in lomas_server.constants)": [[9, "lomas_server.constants.PrivateDatabaseType", false]], "privatedataset (klasse in lomas_server.private_dataset.private_dataset)": [[15, "lomas_server.private_dataset.private_dataset.PrivateDataset", false]], "privatedatasetobserver (klasse in lomas_server.dataset_store.private_dataset_observer)": [[12, "lomas_server.dataset_store.private_dataset_observer.PrivateDatasetObserver", false]], "querier_factory() (im modul lomas_server.dp_queries.dp_libraries.utils)": [[14, "lomas_server.dp_queries.dp_libraries.utils.querier_factory", false]], "query() (methode von lomas_server.dp_queries.dp_libraries.opendp.opendpquerier)": [[14, "lomas_server.dp_queries.dp_libraries.opendp.OpenDPQuerier.query", false]], "query() (methode von lomas_server.dp_queries.dp_libraries.smartnoise_sql.smartnoisesqlquerier)": [[14, "lomas_server.dp_queries.dp_libraries.smartnoise_sql.SmartnoiseSQLQuerier.query", false]], "query() (methode von lomas_server.dp_queries.dp_querier.dpquerier)": [[13, "lomas_server.dp_queries.dp_querier.DPQuerier.query", false]], "query_str (attribut von lomas_server.utils.input_models.dummysnsqlinp)": [[17, "lomas_server.utils.input_models.DummySNSQLInp.query_str", false]], "query_str (attribut von lomas_server.utils.input_models.snsqlinp)": [[17, "lomas_server.utils.input_models.SNSQLInp.query_str", false]], "query_str (attribut von lomas_server.utils.input_models.snsqlinpcost)": [[17, "lomas_server.utils.input_models.SNSQLInpCost.query_str", false]], "queryhandler (klasse in lomas_server.dp_queries.dp_logic)": [[13, "lomas_server.dp_queries.dp_logic.QueryHandler", false]], "reconstruct_measurement_pipeline() (im modul lomas_server.dp_queries.dp_libraries.opendp)": [[14, "lomas_server.dp_queries.dp_libraries.opendp.reconstruct_measurement_pipeline", false]], "reload (attribut von lomas_server.utils.config.server)": [[17, "lomas_server.utils.config.Server.reload", false]], "row_privacy (attribut von lomas_server.utils.collections_models.metadata)": [[17, "lomas_server.utils.collections_models.Metadata.row_privacy", false]], "runtime_args (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.RUNTIME_ARGS", false]], "s3 (attribut von lomas_server.constants.privatedatabasetype)": [[9, "lomas_server.constants.PrivateDatabaseType.S3", false]], "s3_bucket (attribut von lomas_server.utils.collections_models.datasetofs3db)": [[17, "lomas_server.utils.collections_models.DatasetOfS3DB.s3_bucket", false]], "s3_bucket (attribut von lomas_server.utils.collections_models.metadataofs3db)": [[17, "lomas_server.utils.collections_models.MetadataOfS3DB.s3_bucket", false]], "s3_key (attribut von lomas_server.utils.collections_models.datasetofs3db)": [[17, "lomas_server.utils.collections_models.DatasetOfS3DB.s3_key", false]], "s3_key (attribut von lomas_server.utils.collections_models.metadataofs3db)": [[17, "lomas_server.utils.collections_models.MetadataOfS3DB.s3_key", false]], "s3dataset (klasse in lomas_server.private_dataset.s3_dataset)": [[15, "lomas_server.private_dataset.s3_dataset.S3Dataset", false]], "save_current_database() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.save_current_database", false]], "save_query() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.save_query", false]], "save_query() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.save_query", false]], "save_query() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.save_query", false]], "server (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.SERVER", false]], "server (attribut von lomas_server.utils.config.config)": [[17, "lomas_server.utils.config.Config.server", false]], "server (klasse in lomas_server.utils.config)": [[17, "lomas_server.utils.config.Server", false]], "server_live() (im modul lomas_server.utils.utils)": [[17, "lomas_server.utils.utils.server_live", false]], "set_budget_field() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.set_budget_field", false]], "set_config() (methode von lomas_server.utils.config.configloader)": [[17, "lomas_server.utils.config.ConfigLoader.set_config", false]], "set_may_query() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.set_may_query", false]], "set_may_user_query() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.set_may_user_query", false]], "set_may_user_query() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.set_may_user_query", false]], "set_may_user_query() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.set_may_user_query", false]], "set_mechanisms() (im modul lomas_server.dp_queries.dp_libraries.smartnoise_sql)": [[14, "lomas_server.dp_queries.dp_libraries.smartnoise_sql.set_mechanisms", false]], "settings (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.SETTINGS", false]], "setup() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.setUp", false]], "setupclass() (klassenmethode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.setUpClass", false]], "setupclass() (klassenmethode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.setUpClass", false]], "show_archives_of_user() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.show_archives_of_user", false]], "show_collection() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.show_collection", false]], "show_dataset() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.show_dataset", false]], "show_metadata_of_dataset() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.show_metadata_of_dataset", false]], "show_user() (im modul lomas_server.mongodb_admin)": [[9, "lomas_server.mongodb_admin.show_user", false]], "smartnoise_query() (methode von lomas_client.client.client)": [[8, "lomas_client.client.Client.smartnoise_query", false]], "smartnoise_sql (attribut von lomas_client.client.dplibraries)": [[8, "lomas_client.client.DPLibraries.SMARTNOISE_SQL", false]], "smartnoise_sql (attribut von lomas_server.constants.dplibraries)": [[9, "lomas_server.constants.DPLibraries.SMARTNOISE_SQL", false]], "smartnoise_sql_handler() (im modul lomas_server.app)": [[9, "lomas_server.app.smartnoise_sql_handler", false]], "smartnoisesqlquerier (klasse in lomas_server.dp_queries.dp_libraries.smartnoise_sql)": [[14, "lomas_server.dp_queries.dp_libraries.smartnoise_sql.SmartnoiseSQLQuerier", false]], "smoothed_max_divergence (attribut von lomas_server.constants.opendpmeasurement)": [[9, "lomas_server.constants.OpenDPMeasurement.SMOOTHED_MAX_DIVERGENCE", false]], "snsqlinp (klasse in lomas_server.utils.input_models)": [[17, "lomas_server.utils.input_models.SNSQLInp", false]], "snsqlinpcost (klasse in lomas_server.utils.input_models)": [[17, "lomas_server.utils.input_models.SNSQLInpCost", false]], "stall (attribut von lomas_server.constants.timeattackmethod)": [[9, "lomas_server.constants.TimeAttackMethod.STALL", false]], "stream_dataframe() (im modul lomas_server.utils.utils)": [[17, "lomas_server.utils.utils.stream_dataframe", false]], "submit_limit (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.SUBMIT_LIMIT", false]], "submit_limit (attribut von lomas_server.utils.config.config)": [[17, "lomas_server.utils.config.Config.submit_limit", false]], "subscribe_for_memory_usage_updates() (methode von lomas_server.private_dataset.private_dataset.privatedataset)": [[15, "lomas_server.private_dataset.private_dataset.PrivateDataset.subscribe_for_memory_usage_updates", false]], "teardown() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.tearDown", false]], "teardown() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.tearDown", false]], "teardownclass() (klassenmethode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.tearDownClass", false]], "test_add_dataset_to_user() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_add_dataset_to_user", false]], "test_add_datasets_via_yaml() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_add_datasets_via_yaml", false]], "test_add_local_dataset() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_add_local_dataset", false]], "test_add_user() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_add_user", false]], "test_add_user_wb() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_add_user_wb", false]], "test_add_users_via_yaml() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_add_users_via_yaml", false]], "test_boolean_column() (methode von lomas_server.tests.test_dummy_generation.testmakedummydataset)": [[16, "lomas_server.tests.test_dummy_generation.TestMakeDummyDataset.test_boolean_column", false]], "test_budget_over_limit() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_budget_over_limit", false]], "test_cardinality_column() (methode von lomas_server.tests.test_dummy_generation.testmakedummydataset)": [[16, "lomas_server.tests.test_dummy_generation.TestMakeDummyDataset.test_cardinality_column", false]], "test_datetime_column() (methode von lomas_server.tests.test_dummy_generation.testmakedummydataset)": [[16, "lomas_server.tests.test_dummy_generation.TestMakeDummyDataset.test_datetime_column", false]], "test_del_dataset() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_del_dataset", false]], "test_del_dataset_to_user() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_del_dataset_to_user", false]], "test_del_user() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_del_user", false]], "test_drop_collection() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_drop_collection", false]], "test_dummy_opendp_query() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_dummy_opendp_query", false]], "test_dummy_smartnoise_query() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_dummy_smartnoise_query", false]], "test_float_column() (methode von lomas_server.tests.test_dummy_generation.testmakedummydataset)": [[16, "lomas_server.tests.test_dummy_generation.TestMakeDummyDataset.test_float_column", false]], "test_get_dataset_metadata() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_get_dataset_metadata", false]], "test_get_dummy_dataset() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_get_dummy_dataset", false]], "test_get_initial_budget() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_get_initial_budget", false]], "test_get_previous_queries() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_get_previous_queries", false]], "test_get_remaining_budget() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_get_remaining_budget", false]], "test_get_total_spent_budget() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_get_total_spent_budget", false]], "test_int_column() (methode von lomas_server.tests.test_dummy_generation.testmakedummydataset)": [[16, "lomas_server.tests.test_dummy_generation.TestMakeDummyDataset.test_int_column", false]], "test_nullable_column() (methode von lomas_server.tests.test_dummy_generation.testmakedummydataset)": [[16, "lomas_server.tests.test_dummy_generation.TestMakeDummyDataset.test_nullable_column", false]], "test_opendp_cost() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_opendp_cost", false]], "test_opendp_query() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_opendp_query", false]], "test_seed() (methode von lomas_server.tests.test_dummy_generation.testmakedummydataset)": [[16, "lomas_server.tests.test_dummy_generation.TestMakeDummyDataset.test_seed", false]], "test_set_budget_field() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_set_budget_field", false]], "test_set_may_query() (methode von lomas_server.tests.test_mongodb_admin.testmongodbadmin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin.test_set_may_query", false]], "test_smartnoise_cost() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_smartnoise_cost", false]], "test_smartnoise_query() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_smartnoise_query", false]], "test_state() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_state", false]], "test_subsequent_budget_limit_logic() (methode von lomas_server.tests.test_api.testrootapiendpoint)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint.test_subsequent_budget_limit_logic", false]], "testmakedummydataset (klasse in lomas_server.tests.test_dummy_generation)": [[16, "lomas_server.tests.test_dummy_generation.TestMakeDummyDataset", false]], "testmongodbadmin (klasse in lomas_server.tests.test_mongodb_admin)": [[16, "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin", false]], "testrootapiendpoint (klasse in lomas_server.tests.test_api)": [[16, "lomas_server.tests.test_api.TestRootAPIEndpoint", false]], "time_attack (attribut von lomas_server.constants.configkeys)": [[9, "lomas_server.constants.ConfigKeys.TIME_ATTACK", false]], "time_attack (attribut von lomas_server.utils.config.server)": [[17, "lomas_server.utils.config.Server.time_attack", false]], "timeattack (klasse in lomas_server.utils.config)": [[17, "lomas_server.utils.config.TimeAttack", false]], "timeattackmethod (klasse in lomas_server.constants)": [[9, "lomas_server.constants.TimeAttackMethod", false]], "total_spent_delta (attribut von lomas_server.utils.collections_models.datasetofuser)": [[17, "lomas_server.utils.collections_models.DatasetOfUser.total_spent_delta", false]], "total_spent_epsilon (attribut von lomas_server.utils.collections_models.datasetofuser)": [[17, "lomas_server.utils.collections_models.DatasetOfUser.total_spent_epsilon", false]], "unauthorizedaccessexception": [[17, "lomas_server.utils.error_handler.UnauthorizedAccessException", false]], "update_budget() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.update_budget", false]], "update_delta() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.update_delta", false]], "update_epsilon() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.update_epsilon", false]], "update_epsilon_or_delta() (methode von lomas_server.admin_database.admin_database.admindatabase)": [[10, "lomas_server.admin_database.admin_database.AdminDatabase.update_epsilon_or_delta", false]], "update_epsilon_or_delta() (methode von lomas_server.admin_database.mongodb_database.adminmongodatabase)": [[10, "lomas_server.admin_database.mongodb_database.AdminMongoDatabase.update_epsilon_or_delta", false]], "update_epsilon_or_delta() (methode von lomas_server.admin_database.yaml_database.adminyamldatabase)": [[10, "lomas_server.admin_database.yaml_database.AdminYamlDatabase.update_epsilon_or_delta", false]], "update_memory_usage() (methode von lomas_server.dataset_store.lru_dataset_store.lrudatasetstore)": [[12, "lomas_server.dataset_store.lru_dataset_store.LRUDatasetStore.update_memory_usage", false]], "update_memory_usage() (methode von lomas_server.dataset_store.private_dataset_observer.privatedatasetobserver)": [[12, "lomas_server.dataset_store.private_dataset_observer.PrivateDatasetObserver.update_memory_usage", false]], "user (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.User", false]], "user_must_exist() (im modul lomas_server.admin_database.admin_database)": [[10, "lomas_server.admin_database.admin_database.user_must_exist", false]], "user_must_have_access_to_dataset() (im modul lomas_server.admin_database.admin_database)": [[10, "lomas_server.admin_database.admin_database.user_must_have_access_to_dataset", false]], "user_name (attribut von lomas_server.utils.collections_models.user)": [[17, "lomas_server.utils.collections_models.User.user_name", false]], "usercollection (klasse in lomas_server.utils.collections_models)": [[17, "lomas_server.utils.collections_models.UserCollection", false]], "username (attribut von lomas_server.utils.config.mongodbconfig)": [[17, "lomas_server.utils.config.MongoDBConfig.username", false]], "users (attribut von lomas_server.utils.collections_models.usercollection)": [[17, "lomas_server.utils.collections_models.UserCollection.users", false]], "workers (attribut von lomas_server.utils.config.server)": [[17, "lomas_server.utils.config.Server.workers", false]], "yaml (attribut von lomas_server.constants.admindbtype)": [[9, "lomas_server.constants.AdminDBType.YAML", false]], "yamldbconfig (klasse in lomas_server.utils.config)": [[17, "lomas_server.utils.config.YamlDBConfig", false]], "zero_concentrated_divergence (attribut von lomas_server.constants.opendpmeasurement)": [[9, "lomas_server.constants.OpenDPMeasurement.ZERO_CONCENTRATED_DIVERGENCE", false]]}, "objects": {"": [[8, 0, 0, "-", "lomas_client"], [9, 0, 0, "-", "lomas_server"]], "lomas_client": [[8, 0, 0, "-", "client"]], "lomas_client.client": [[8, 1, 1, "", "Client"], [8, 1, 1, "", "DPLibraries"], [8, 4, 1, "", "error_message"]], "lomas_client.client.Client": [[8, 2, 1, "", "estimate_opendp_cost"], [8, 2, 1, "", "estimate_smartnoise_cost"], [8, 2, 1, "", "get_dataset_metadata"], [8, 2, 1, "", "get_dummy_dataset"], [8, 2, 1, "", "get_initial_budget"], [8, 2, 1, "", "get_previous_queries"], [8, 2, 1, "", "get_remaining_budget"], [8, 2, 1, "", "get_total_spent_budget"], [8, 2, 1, "", "opendp_query"], [8, 2, 1, "", "smartnoise_query"]], "lomas_client.client.DPLibraries": [[8, 3, 1, "", "OPENDP"], [8, 3, 1, "", "SMARTNOISE_SQL"]], "lomas_server": [[10, 0, 0, "-", "admin_database"], [11, 0, 0, "-", "administration"], [9, 0, 0, "-", "app"], [9, 0, 0, "-", "constants"], [12, 0, 0, "-", "dataset_store"], [13, 0, 0, "-", "dp_queries"], [9, 0, 0, "-", "mongodb_admin"], [9, 0, 0, "-", "mongodb_admin_cli"], [15, 0, 0, "-", "private_dataset"], [16, 0, 0, "-", "tests"], [17, 0, 0, "-", "utils"], [9, 0, 0, "-", "uvicorn_serve"]], "lomas_server.admin_database": [[10, 0, 0, "-", "admin_database"], [10, 0, 0, "-", "mongodb_database"], [10, 0, 0, "-", "utils"], [10, 0, 0, "-", "yaml_database"]], "lomas_server.admin_database.admin_database": [[10, 1, 1, "", "AdminDatabase"], [10, 4, 1, "", "dataset_must_exist"], [10, 4, 1, "", "user_must_exist"], [10, 4, 1, "", "user_must_have_access_to_dataset"]], "lomas_server.admin_database.admin_database.AdminDatabase": [[10, 2, 1, "", "does_dataset_exist"], [10, 2, 1, "", "does_user_exist"], [10, 2, 1, "", "get_and_set_may_user_query"], [10, 2, 1, "", "get_dataset_field"], [10, 2, 1, "", "get_dataset_metadata"], [10, 2, 1, "", "get_epsilon_or_delta"], [10, 2, 1, "", "get_initial_budget"], [10, 2, 1, "", "get_remaining_budget"], [10, 2, 1, "", "get_total_spent_budget"], [10, 2, 1, "", "get_user_previous_queries"], [10, 2, 1, "", "has_user_access_to_dataset"], [10, 2, 1, "", "may_user_query"], [10, 2, 1, "", "prepare_save_query"], [10, 2, 1, "", "save_query"], [10, 2, 1, "", "set_may_user_query"], [10, 2, 1, "", "update_budget"], [10, 2, 1, "", "update_delta"], [10, 2, 1, "", "update_epsilon"], [10, 2, 1, "", "update_epsilon_or_delta"]], "lomas_server.admin_database.mongodb_database": [[10, 1, 1, "", "AdminMongoDatabase"], [10, 4, 1, "", "check_result_acknowledged"]], "lomas_server.admin_database.mongodb_database.AdminMongoDatabase": [[10, 2, 1, "", "does_dataset_exist"], [10, 2, 1, "", "does_user_exist"], [10, 2, 1, "", "get_and_set_may_user_query"], [10, 2, 1, "", "get_dataset_field"], [10, 2, 1, "", "get_dataset_metadata"], [10, 2, 1, "", "get_epsilon_or_delta"], [10, 2, 1, "", "get_user_previous_queries"], [10, 2, 1, "", "has_user_access_to_dataset"], [10, 2, 1, "", "may_user_query"], [10, 2, 1, "", "save_query"], [10, 2, 1, "", "set_may_user_query"], [10, 2, 1, "", "update_epsilon_or_delta"]], "lomas_server.admin_database.utils": [[10, 4, 1, "", "database_factory"], [10, 4, 1, "", "get_mongodb"], [10, 4, 1, "", "get_mongodb_url"]], "lomas_server.admin_database.yaml_database": [[10, 1, 1, "", "AdminYamlDatabase"]], "lomas_server.admin_database.yaml_database.AdminYamlDatabase": [[10, 2, 1, "", "does_dataset_exist"], [10, 2, 1, "", "does_user_exist"], [10, 2, 1, "", "get_and_set_may_user_query"], [10, 2, 1, "", "get_dataset_field"], [10, 2, 1, "", "get_dataset_metadata"], [10, 2, 1, "", "get_epsilon_or_delta"], [10, 2, 1, "", "get_user_previous_queries"], [10, 2, 1, "", "has_user_access_to_dataset"], [10, 2, 1, "", "may_user_query"], [10, 2, 1, "", "save_current_database"], [10, 2, 1, "", "save_query"], [10, 2, 1, "", "set_may_user_query"], [10, 2, 1, "", "update_epsilon_or_delta"]], "lomas_server.app": [[9, 4, 1, "", "dummy_opendp_query_handler"], [9, 4, 1, "", "dummy_smartnoise_sql_handler"], [9, 4, 1, "", "estimate_opendp_cost"], [9, 4, 1, "", "estimate_smartnoise_cost"], [9, 4, 1, "", "get_dataset_metadata"], [9, 4, 1, "", "get_dummy_dataset"], [9, 4, 1, "", "get_initial_budget"], [9, 4, 1, "", "get_memory_usage"], [9, 4, 1, "", "get_remaining_budget"], [9, 4, 1, "", "get_state"], [9, 4, 1, "", "get_total_spent_budget"], [9, 4, 1, "", "get_user_previous_queries"], [9, 4, 1, "", "lifespan"], [9, 4, 1, "", "middleware"], [9, 4, 1, "", "opendp_query_handler"], [9, 4, 1, "", "smartnoise_sql_handler"]], "lomas_server.constants": [[9, 1, 1, "", "AdminDBType"], [9, 1, 1, "", "ConfigKeys"], [9, 1, 1, "", "DPLibraries"], [9, 1, 1, "", "DatasetStoreType"], [9, 1, 1, "", "OpenDPMeasurement"], [9, 1, 1, "", "PrivateDatabaseType"], [9, 1, 1, "", "TimeAttackMethod"]], "lomas_server.constants.AdminDBType": [[9, 3, 1, "", "MONGODB"], [9, 3, 1, "", "YAML"]], "lomas_server.constants.ConfigKeys": [[9, 3, 1, "", "DATASET_STORE"], [9, 3, 1, "", "DATASET_STORE_TYPE"], [9, 3, 1, "", "DB"], [9, 3, 1, "", "DB_TYPE"], [9, 3, 1, "", "DB_TYPE_MONGODB"], [9, 3, 1, "", "DEVELOP_MODE"], [9, 3, 1, "", "LRU_DATASET_STORE_MAX_SIZE"], [9, 3, 1, "", "MONGODB_ADDR"], [9, 3, 1, "", "MONGODB_PORT"], [9, 3, 1, "", "RUNTIME_ARGS"], [9, 3, 1, "", "SERVER"], [9, 3, 1, "", "SETTINGS"], [9, 3, 1, "", "SUBMIT_LIMIT"], [9, 3, 1, "", "TIME_ATTACK"]], "lomas_server.constants.DPLibraries": [[9, 3, 1, "", "OPENDP"], [9, 3, 1, "", "SMARTNOISE_SQL"]], "lomas_server.constants.DatasetStoreType": [[9, 3, 1, "", "BASIC"], [9, 3, 1, "", "LRU"]], "lomas_server.constants.OpenDPMeasurement": [[9, 3, 1, "", "FIXED_SMOOTHED_MAX_DIVERGENCE"], [9, 3, 1, "", "MAX_DIVERGENCE"], [9, 3, 1, "", "SMOOTHED_MAX_DIVERGENCE"], [9, 3, 1, "", "ZERO_CONCENTRATED_DIVERGENCE"]], "lomas_server.constants.PrivateDatabaseType": [[9, 3, 1, "", "PATH"], [9, 3, 1, "", "S3"]], "lomas_server.constants.TimeAttackMethod": [[9, 3, 1, "", "JITTER"], [9, 3, 1, "", "STALL"]], "lomas_server.dataset_store": [[12, 0, 0, "-", "basic_dataset_store"], [12, 0, 0, "-", "dataset_store"], [12, 0, 0, "-", "lru_dataset_store"], [12, 0, 0, "-", "private_dataset_observer"], [12, 0, 0, "-", "utils"]], "lomas_server.dataset_store.basic_dataset_store": [[12, 1, 1, "", "BasicDatasetStore"]], "lomas_server.dataset_store.basic_dataset_store.BasicDatasetStore": [[12, 3, 1, "", "dp_queriers"], [12, 2, 1, "", "get_querier"]], "lomas_server.dataset_store.dataset_store": [[12, 1, 1, "", "DatasetStore"]], "lomas_server.dataset_store.dataset_store.DatasetStore": [[12, 3, 1, "", "admin_database"], [12, 2, 1, "", "get_querier"]], "lomas_server.dataset_store.lru_dataset_store": [[12, 1, 1, "", "LRUDatasetStore"]], "lomas_server.dataset_store.lru_dataset_store.LRUDatasetStore": [[12, 3, 1, "", "dataset_cache"], [12, 2, 1, "", "get_querier"], [12, 2, 1, "", "update_memory_usage"]], "lomas_server.dataset_store.private_dataset_observer": [[12, 1, 1, "", "PrivateDatasetObserver"]], "lomas_server.dataset_store.private_dataset_observer.PrivateDatasetObserver": [[12, 2, 1, "", "update_memory_usage"]], "lomas_server.dataset_store.utils": [[12, 4, 1, "", "dataset_store_factory"]], "lomas_server.dp_queries": [[14, 0, 0, "-", "dp_libraries"], [13, 0, 0, "-", "dp_logic"], [13, 0, 0, "-", "dp_querier"], [13, 0, 0, "-", "dummy_dataset"]], "lomas_server.dp_queries.dp_libraries": [[14, 0, 0, "-", "opendp"], [14, 0, 0, "-", "smartnoise_sql"], [14, 0, 0, "-", "utils"]], "lomas_server.dp_queries.dp_libraries.opendp": [[14, 1, 1, "", "OpenDPQuerier"], [14, 4, 1, "", "get_output_measure"], [14, 4, 1, "", "is_measurement"], [14, 4, 1, "", "reconstruct_measurement_pipeline"]], "lomas_server.dp_queries.dp_libraries.opendp.OpenDPQuerier": [[14, 2, 1, "", "cost"], [14, 2, 1, "", "query"]], "lomas_server.dp_queries.dp_libraries.smartnoise_sql": [[14, 1, 1, "", "SmartnoiseSQLQuerier"], [14, 4, 1, "", "set_mechanisms"]], "lomas_server.dp_queries.dp_libraries.smartnoise_sql.SmartnoiseSQLQuerier": [[14, 2, 1, "", "cost"], [14, 2, 1, "", "query"]], "lomas_server.dp_queries.dp_libraries.utils": [[14, 4, 1, "", "querier_factory"]], "lomas_server.dp_queries.dp_logic": [[13, 1, 1, "", "QueryHandler"]], "lomas_server.dp_queries.dp_logic.QueryHandler": [[13, 3, 1, "", "admin_database"], [13, 3, 1, "", "dataset_store"], [13, 2, 1, "", "estimate_cost"], [13, 2, 1, "", "handle_query"]], "lomas_server.dp_queries.dp_querier": [[13, 1, 1, "", "DPQuerier"]], "lomas_server.dp_queries.dp_querier.DPQuerier": [[13, 2, 1, "", "cost"], [13, 2, 1, "", "query"]], "lomas_server.dp_queries.dummy_dataset": [[13, 4, 1, "", "get_dummy_dataset_for_query"], [13, 4, 1, "", "make_dummy_dataset"]], "lomas_server.mongodb_admin": [[9, 4, 1, "", "add_dataset"], [9, 4, 1, "", "add_dataset_to_user"], [9, 4, 1, "", "add_datasets_via_yaml"], [9, 4, 1, "", "add_user"], [9, 4, 1, "", "add_user_with_budget"], [9, 4, 1, "", "add_users_via_yaml"], [9, 4, 1, "", "check_dataset_and_metadata_exist"], [9, 4, 1, "", "check_user_exists"], [9, 4, 1, "", "check_user_has_dataset"], [9, 4, 1, "", "del_dataset"], [9, 4, 1, "", "del_dataset_to_user"], [9, 4, 1, "", "del_user"], [9, 4, 1, "", "drop_collection"], [9, 4, 1, "", "get_list_of_datasets"], [9, 4, 1, "", "get_list_of_datasets_from_user"], [9, 4, 1, "", "get_list_of_users"], [9, 4, 1, "", "set_budget_field"], [9, 4, 1, "", "set_may_query"], [9, 4, 1, "", "show_archives_of_user"], [9, 4, 1, "", "show_collection"], [9, 4, 1, "", "show_dataset"], [9, 4, 1, "", "show_metadata_of_dataset"], [9, 4, 1, "", "show_user"]], "lomas_server.private_dataset": [[15, 0, 0, "-", "in_memory_dataset"], [15, 0, 0, "-", "path_dataset"], [15, 0, 0, "-", "private_dataset"], [15, 0, 0, "-", "s3_dataset"], [15, 0, 0, "-", "utils"]], "lomas_server.private_dataset.in_memory_dataset": [[15, 1, 1, "", "InMemoryDataset"]], "lomas_server.private_dataset.in_memory_dataset.InMemoryDataset": [[15, 2, 1, "", "get_pandas_df"]], "lomas_server.private_dataset.path_dataset": [[15, 1, 1, "", "PathDataset"]], "lomas_server.private_dataset.path_dataset.PathDataset": [[15, 2, 1, "", "get_pandas_df"]], "lomas_server.private_dataset.private_dataset": [[15, 1, 1, "", "PrivateDataset"]], "lomas_server.private_dataset.private_dataset.PrivateDataset": [[15, 3, 1, "", "df"], [15, 2, 1, "", "get_memory_usage"], [15, 2, 1, "", "get_metadata"], [15, 2, 1, "", "get_pandas_df"], [15, 2, 1, "", "subscribe_for_memory_usage_updates"]], "lomas_server.private_dataset.s3_dataset": [[15, 1, 1, "", "S3Dataset"]], "lomas_server.private_dataset.s3_dataset.S3Dataset": [[15, 2, 1, "", "get_pandas_df"]], "lomas_server.private_dataset.utils": [[15, 4, 1, "", "private_dataset_factory"]], "lomas_server.tests": [[16, 0, 0, "-", "constants"], [16, 0, 0, "-", "test_api"], [16, 0, 0, "-", "test_dummy_generation"], [16, 0, 0, "-", "test_mongodb_admin"]], "lomas_server.tests.test_api": [[16, 1, 1, "", "TestRootAPIEndpoint"]], "lomas_server.tests.test_api.TestRootAPIEndpoint": [[16, 2, 1, "", "setUp"], [16, 2, 1, "", "setUpClass"], [16, 2, 1, "", "tearDown"], [16, 2, 1, "", "tearDownClass"], [16, 2, 1, "", "test_budget_over_limit"], [16, 2, 1, "", "test_dummy_opendp_query"], [16, 2, 1, "", "test_dummy_smartnoise_query"], [16, 2, 1, "", "test_get_dataset_metadata"], [16, 2, 1, "", "test_get_dummy_dataset"], [16, 2, 1, "", "test_get_initial_budget"], [16, 2, 1, "", "test_get_previous_queries"], [16, 2, 1, "", "test_get_remaining_budget"], [16, 2, 1, "", "test_get_total_spent_budget"], [16, 2, 1, "", "test_opendp_cost"], [16, 2, 1, "", "test_opendp_query"], [16, 2, 1, "", "test_smartnoise_cost"], [16, 2, 1, "", "test_smartnoise_query"], [16, 2, 1, "", "test_state"], [16, 2, 1, "", "test_subsequent_budget_limit_logic"]], "lomas_server.tests.test_dummy_generation": [[16, 1, 1, "", "TestMakeDummyDataset"]], "lomas_server.tests.test_dummy_generation.TestMakeDummyDataset": [[16, 2, 1, "", "test_boolean_column"], [16, 2, 1, "", "test_cardinality_column"], [16, 2, 1, "", "test_datetime_column"], [16, 2, 1, "", "test_float_column"], [16, 2, 1, "", "test_int_column"], [16, 2, 1, "", "test_nullable_column"], [16, 2, 1, "", "test_seed"]], "lomas_server.tests.test_mongodb_admin": [[16, 1, 1, "", "TestMongoDBAdmin"]], "lomas_server.tests.test_mongodb_admin.TestMongoDBAdmin": [[16, 2, 1, "", "setUpClass"], [16, 2, 1, "", "tearDown"], [16, 2, 1, "", "test_add_dataset_to_user"], [16, 2, 1, "", "test_add_datasets_via_yaml"], [16, 2, 1, "", "test_add_local_dataset"], [16, 2, 1, "", "test_add_user"], [16, 2, 1, "", "test_add_user_wb"], [16, 2, 1, "", "test_add_users_via_yaml"], [16, 2, 1, "", "test_del_dataset"], [16, 2, 1, "", "test_del_dataset_to_user"], [16, 2, 1, "", "test_del_user"], [16, 2, 1, "", "test_drop_collection"], [16, 2, 1, "", "test_set_budget_field"], [16, 2, 1, "", "test_set_may_query"]], "lomas_server.utils": [[17, 0, 0, "-", "anti_timing_att"], [17, 0, 0, "-", "collections_models"], [17, 0, 0, "-", "config"], [17, 0, 0, "-", "error_handler"], [17, 0, 0, "-", "example_inputs"], [17, 0, 0, "-", "input_models"], [17, 0, 0, "-", "loggr"], [17, 0, 0, "-", "utils"]], "lomas_server.utils.anti_timing_att": [[17, 4, 1, "", "anti_timing_att"]], "lomas_server.utils.collections_models": [[17, 1, 1, "", "Dataset"], [17, 1, 1, "", "DatasetOfPathDB"], [17, 1, 1, "", "DatasetOfS3DB"], [17, 1, 1, "", "DatasetOfUser"], [17, 1, 1, "", "DatasetsCollection"], [17, 1, 1, "", "Metadata"], [17, 1, 1, "", "MetadataOfDataset"], [17, 1, 1, "", "MetadataOfPathDB"], [17, 1, 1, "", "MetadataOfS3DB"], [17, 1, 1, "", "User"], [17, 1, 1, "", "UserCollection"]], "lomas_server.utils.collections_models.Dataset": [[17, 3, 1, "", "database_type"], [17, 3, 1, "", "dataset_name"], [17, 3, 1, "", "metadata"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.collections_models.DatasetOfPathDB": [[17, 3, 1, "", "dataset_path"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.collections_models.DatasetOfS3DB": [[17, 3, 1, "", "aws_access_key_id"], [17, 3, 1, "", "aws_secret_access_key"], [17, 3, 1, "", "endpoint_url"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "s3_bucket"], [17, 3, 1, "", "s3_key"]], "lomas_server.utils.collections_models.DatasetOfUser": [[17, 3, 1, "", "dataset_name"], [17, 3, 1, "", "initial_delta"], [17, 3, 1, "", "initial_epsilon"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "total_spent_delta"], [17, 3, 1, "", "total_spent_epsilon"]], "lomas_server.utils.collections_models.DatasetsCollection": [[17, 3, 1, "", "datasets"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.collections_models.Metadata": [[17, 3, 1, "", "columns"], [17, 3, 1, "", "max_ids"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "row_privacy"]], "lomas_server.utils.collections_models.MetadataOfDataset": [[17, 3, 1, "", "database_type"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.collections_models.MetadataOfPathDB": [[17, 3, 1, "", "metadata_path"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.collections_models.MetadataOfS3DB": [[17, 3, 1, "", "aws_access_key_id"], [17, 3, 1, "", "aws_secret_access_key"], [17, 3, 1, "", "endpoint_url"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "s3_bucket"], [17, 3, 1, "", "s3_key"]], "lomas_server.utils.collections_models.User": [[17, 3, 1, "", "datasets_list"], [17, 3, 1, "", "may_query"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "user_name"]], "lomas_server.utils.collections_models.UserCollection": [[17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "users"]], "lomas_server.utils.config": [[17, 1, 1, "", "Config"], [17, 1, 1, "", "ConfigLoader"], [17, 1, 1, "", "DBConfig"], [17, 1, 1, "", "DatasetStoreConfig"], [17, 1, 1, "", "LRUDatasetStoreConfig"], [17, 1, 1, "", "MongoDBConfig"], [17, 1, 1, "", "Server"], [17, 1, 1, "", "TimeAttack"], [17, 1, 1, "", "YamlDBConfig"], [17, 4, 1, "", "get_config"]], "lomas_server.utils.config.Config": [[17, 3, 1, "", "admin_database"], [17, 3, 1, "", "dataset_store"], [17, 3, 1, "", "develop_mode"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "server"], [17, 3, 1, "", "submit_limit"]], "lomas_server.utils.config.ConfigLoader": [[17, 2, 1, "", "get_config"], [17, 2, 1, "", "load_config"], [17, 2, 1, "", "set_config"]], "lomas_server.utils.config.DBConfig": [[17, 3, 1, "", "db_type"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.config.DatasetStoreConfig": [[17, 3, 1, "", "ds_store_type"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.config.LRUDatasetStoreConfig": [[17, 3, 1, "", "max_memory_usage"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.config.MongoDBConfig": [[17, 3, 1, "", "address"], [17, 3, 1, "", "db_name"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "password"], [17, 3, 1, "", "port"], [17, 3, 1, "", "username"]], "lomas_server.utils.config.Server": [[17, 3, 1, "", "host_ip"], [17, 3, 1, "", "host_port"], [17, 3, 1, "", "log_level"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "reload"], [17, 3, 1, "", "time_attack"], [17, 3, 1, "", "workers"]], "lomas_server.utils.config.TimeAttack": [[17, 3, 1, "", "magnitude"], [17, 3, 1, "", "method"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.config.YamlDBConfig": [[17, 3, 1, "", "db_file"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.error_handler": [[17, 5, 1, "", "ExternalLibraryException"], [17, 5, 1, "", "InternalServerException"], [17, 5, 1, "", "InvalidQueryException"], [17, 5, 1, "", "UnauthorizedAccessException"], [17, 4, 1, "", "add_exception_handlers"]], "lomas_server.utils.input_models": [[17, 1, 1, "", "DummyOpenDPInp"], [17, 1, 1, "", "DummySNSQLInp"], [17, 1, 1, "", "GetDbData"], [17, 1, 1, "", "GetDummyDataset"], [17, 1, 1, "", "OpenDPInp"], [17, 1, 1, "", "SNSQLInp"], [17, 1, 1, "", "SNSQLInpCost"]], "lomas_server.utils.input_models.DummyOpenDPInp": [[17, 3, 1, "", "dataset_name"], [17, 3, 1, "", "dummy_nb_rows"], [17, 3, 1, "", "dummy_seed"], [17, 3, 1, "", "fixed_delta"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "opendp_json"]], "lomas_server.utils.input_models.DummySNSQLInp": [[17, 3, 1, "", "dataset_name"], [17, 3, 1, "", "delta"], [17, 3, 1, "", "dummy_nb_rows"], [17, 3, 1, "", "dummy_seed"], [17, 3, 1, "", "epsilon"], [17, 3, 1, "", "mechanisms"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "postprocess"], [17, 3, 1, "", "query_str"]], "lomas_server.utils.input_models.GetDbData": [[17, 3, 1, "", "dataset_name"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.input_models.GetDummyDataset": [[17, 3, 1, "", "dataset_name"], [17, 3, 1, "", "dummy_nb_rows"], [17, 3, 1, "", "dummy_seed"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"]], "lomas_server.utils.input_models.OpenDPInp": [[17, 3, 1, "", "dataset_name"], [17, 3, 1, "", "fixed_delta"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "opendp_json"]], "lomas_server.utils.input_models.SNSQLInp": [[17, 3, 1, "", "dataset_name"], [17, 3, 1, "", "delta"], [17, 3, 1, "", "epsilon"], [17, 3, 1, "", "mechanisms"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "postprocess"], [17, 3, 1, "", "query_str"]], "lomas_server.utils.input_models.SNSQLInpCost": [[17, 3, 1, "", "dataset_name"], [17, 3, 1, "", "delta"], [17, 3, 1, "", "epsilon"], [17, 3, 1, "", "mechanisms"], [17, 3, 1, "", "model_computed_fields"], [17, 3, 1, "", "model_config"], [17, 3, 1, "", "model_fields"], [17, 3, 1, "", "query_str"]], "lomas_server.utils.utils": [[17, 4, 1, "", "add_demo_data_to_admindb"], [17, 4, 1, "", "server_live"], [17, 4, 1, "", "stream_dataframe"]]}, "objnames": {"0": ["py", "module", "Python-Modul"], "1": ["py", "class", "Python-Klasse"], "2": ["py", "method", "Python-Methode"], "3": ["py", "attribute", "Python-Attribut"], "4": ["py", "function", "Python-Funktion"], "5": ["py", "exception", "Python-Exception"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:attribute", "4": "py:function", "5": "py:exception"}, "terms": {"0": [0, 8, 9, 10, 14, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 31], "00": [23, 25], "0000": 23, "000000000032756e": 28, "0001": [18, 19, 23, 25], "00014999500000001387": [18, 19, 27], "0004": [17, 28], "0005": [23, 25], "001": [20, 23, 25], "004850004999999986": [18, 19], "004950000000000006": 28, "004989999999999935": 28, "004994999999999967": 28, "005": [18, 19, 23, 25, 28], "01": [23, 27], "0100": 23, "02": 27, "031628": [18, 19, 28], "031895": 28, "038092": [18, 19, 28], "049234": 22, "05": [9, 23, 25, 28], "051034": 28, "051061": 27, "051717576669322": 19, "052": 19, "06": [9, 21, 23, 25, 28], "065792": 28, "0667355": 18, "07": [23, 24, 25], "070911": [18, 19, 28], "075665": 28, "08": 21, "085707": 28, "09": 25, "096718": [18, 19, 28], "0x7a92cbffc5f0": 18, "0x7a92cbffc9e0": 18, "0x7f2513f24290": 19, "0x7f2513f245f0": 19, "0x7f2513f24b90": 19, "0x7f2513f25910": 19, "1": [0, 4, 6, 8, 9, 10, 21, 22, 23, 24, 25, 26, 31], "10": [18, 19, 20, 21, 23, 25, 27, 28], "100": [8, 9, 13, 18, 19, 23, 25, 27, 28], "1000": [19, 22, 23, 25], "100000": [19, 22, 23, 25], "1001": 22, "100_000": 19, "1024": 12, "103": 23, "108904": 28, "11": [0, 18, 19, 20, 23, 25, 27, 28], "113_367": 19, "113_715": 19, "117959": [18, 19, 28], "12": [18, 19, 21, 23, 25, 27, 28], "122": 28, "123": 19, "123036": 28, "124": [18, 19, 23], "125950": [18, 19, 28], "127": [0, 26], "128719": 28, "13": [18, 19, 21, 23, 24, 25, 27, 28], "13189211774378": 19, "1345": 22, "139": 23, "14": [18, 19, 23, 25, 27, 28], "142": 27, "143560": 28, "143633": 27, "146055": 18, "146_443": 19, "148_265": 19, "149507": 28, "15": [18, 19, 23, 25, 27, 28], "150": [18, 19, 23, 25, 28], "152": [18, 19], "155": 22, "158": 28, "159": [18, 19, 28], "16": [18, 19, 23, 25, 27, 28], "160858": 27, "162871": 28, "1652308": 19, "166": 28, "169": 23, "17": [18, 19, 23, 25, 27, 28], "1701701776": 23, "1714988610": 19, "1714988634": 19, "1714988645": 19, "1714990018": 19, "1714990066": 19, "1714990074": 19, "1717296": 19, "1717666971": 18, "1717666974": 18, "1717666977": 18, "175608": 28, "178": 22, "178308": 21, "18": [18, 19, 21, 23, 24, 25, 27, 28], "182182": 22, "183_978": 19, "184409589415381": 19, "187407": 21, "189525": 21, "19": [18, 19, 23, 25, 27, 28], "190": [18, 19, 28], "193166": 28, "193759": 28, "196_970": 19, "19844": 19, "199": 18, "199850005": 27, "1_000": 19, "1_397_823": 19, "1e": [8, 9, 18, 19, 23, 25, 27, 28], "2": [4, 6, 21, 22, 23, 25], "20": [18, 19, 20, 23, 25, 27, 28], "200": [18, 19, 22, 27, 28], "2000": [18, 19, 23, 25, 28], "2022": 7, "2023": [21, 24], "2024": 25, "203165": 28, "20528": 22, "208473": [18, 19, 28], "21": [18, 19, 23, 25, 27, 28], "210": 28, "210_800": 19, "214": 28, "22": [18, 19, 23, 25, 27, 28], "2258": 28, "2279": 22, "23": [18, 19, 23, 25, 27, 28], "2316": [18, 19, 28], "236": [18, 19, 28], "238_634": 19, "239": 28, "24": [18, 19, 23, 24, 25, 27, 28], "240181818190626": 27, "242": [18, 19, 28], "2421": 27, "244": [18, 19, 28], "246787": [18, 19, 28], "246870": [18, 19, 28], "246992": 27, "24_022": 19, "25": [18, 19, 23, 25, 27, 28], "250": [18, 19, 23, 25, 28], "2503": 27, "253_164": 19, "254": 23, "255215": 28, "2562": 27, "2574": 22, "258123": 28, "26": [18, 19, 23, 24, 25, 27, 28], "262560": 28, "267_304": 19, "269176": 21, "27": [18, 23, 25, 27, 28], "2700": 22, "27017": [25, 31], "2733": 27, "275001": 28, "28": [18, 19, 21, 23, 25, 27, 28], "284_638": 19, "2873": [18, 19, 28], "29": [18, 19, 23, 25, 27, 28], "291426": 28, "291927": [18, 19, 28], "29734": 22, "2_032_543": 19, "3": [0, 4, 6, 20, 21, 22, 23, 25], "30": [9, 18, 19, 21, 23, 25, 27, 28], "300": 25, "31": [18, 19, 23, 25, 27, 28], "31113": 22, "3123": 22, "314292": 28, "32": [18, 19, 23, 25, 27, 28], "3265": 27, "326_425": 19, "33": [18, 19, 23, 25, 28], "337": 28, "34": [18, 19, 20, 21, 23, 24, 25, 27, 28], "342": [18, 19], "344": 19, "346": 9, "34mm": 28, "35": [18, 19, 23, 25, 28], "352_001": 19, "3552": 22, "36": [18, 19, 23, 25, 28], "3611896": 19, "3639": [18, 19, 28], "364104": 28, "366_879": 19, "37": [18, 19, 23, 25, 28], "3729": 22, "38": [18, 19, 21, 23, 25, 28], "39": [18, 19, 21, 22, 23, 24, 25, 27, 28], "393": 27, "396": 25, "399": 27, "3d": 23, "4": [3, 4, 20, 21, 22, 23, 25], "40": [18, 19, 23, 25, 28], "400": [18, 19, 27], "4078": 27, "408606": 28, "41": [19, 23, 25, 28], "413996": 28, "418": 25, "42": [8, 9, 13, 18, 19, 23, 25, 28], "4274": 22, "43": [9, 18, 19, 23, 25, 27, 28], "44": [18, 19, 23, 25, 28], "4452": 27, "4470": 22, "45": [19, 23, 25, 27, 28], "456669": 28, "46": [23, 25, 28], "460652": 28, "461": 25, "4611": 27, "464": 25, "4654": 22, "4656": 28, "466": 25, "469563": 28, "47": [18, 19, 23, 25, 28], "4703": 28, "474_690": 19, "4750721": 19, "48": [23, 25, 28], "4833": 27, "4860": 27, "49": [18, 19, 23, 24, 25, 27, 28], "4999e": [18, 19], "499e": 28, "5": [4, 8, 9, 17, 20, 21, 22, 23, 25, 27, 28, 31], "50": [19, 25, 28], "5036": [18, 19, 28], "50783673123655": 28, "51": [18, 19, 25, 28], "51532": 28, "5187": 28, "51mm": 19, "52": [19, 25, 28], "5224": 22, "52_209": 19, "52mm": 18, "53": [19, 23, 28], "530153": [18, 19, 28], "53955": 22, "54": [19, 25], "5401": [18, 19, 28], "5405": 27, "549": 23, "55": [18, 19, 28], "556213": 22, "5597": 22, "56": 21, "5630": [27, 28], "58": 23, "580124": 28, "5878": 22, "59": [21, 25], "6": [18, 19, 20, 22, 23, 25, 27, 28], "61": 9, "619788": 28, "628394": 24, "63": 28, "634_720": 19, "6397": 27, "642906": 28, "64_357": 19, "65": [9, 18, 19, 23, 25, 28], "6634": 27, "67": [19, 28], "674265": 18, "6743": 27, "6748": 22, "678": 25, "68": [18, 19], "686": 18, "68648927291686": 18, "687956": 22, "7": [18, 19, 20, 21, 22, 23, 25, 27, 28], "70": [21, 24], "7000": [18, 19, 23, 25, 28], "700943": 28, "703": 25, "7097": 22, "710": 25, "7122093023265229": 19, "7163742690067888": [18, 19], "726": 25, "7268": 22, "728733": 28, "74": 27, "741": 25, "743330": [18, 19, 28], "748048": 28, "749": 25, "75": [19, 28], "755": 25, "7578": 22, "7582": 22, "768420": 24, "76_231": 19, "773158": 28, "773991": 28, "777518": [18, 19, 28], "78": 27, "785415": 28, "785941": 27, "79": 27, "796037": [18, 19, 28], "796386": 28, "799": 22, "8": [9, 18, 19, 20, 22, 23, 25, 26, 27, 28], "80": [18, 19, 22, 27, 28], "800": [18, 19, 20, 21], "8039": 22, "805783": 28, "807157": 28, "8165": 22, "82": 23, "8266": 27, "827918": [18, 19, 28], "836323": 19, "83632334140567": 19, "83_326": 19, "84": 19, "849626": 28, "84mm": 19, "85": 28, "8501": 0, "851064": 28, "855419": 28, "8759": 22, "87_668": 19, "886555": 28, "8888": 0, "891": 27, "9": [18, 19, 20, 23, 25, 27, 28], "90": 24, "902943": 28, "903": 22, "91": 24, "911640": 22, "917": 25, "9196112": 19, "92": [24, 28], "9226": 27, "928019": 27, "928323": 28, "929142": [18, 19, 28], "93": 18, "93065072561188": 18, "930651": 18, "935352": 27, "936950": 27, "9381": 22, "93mm": 18, "94": [27, 28], "940005": [18, 19, 28], "940338": 27, "9480": 22, "948853": [18, 19, 28], "95": [18, 19, 23, 27, 28], "953047": 28, "953b9cef6799e942255a1d5edcb7cb7508230fb57e4d68d02e27aed4b1694eaf": 23, "96": [18, 19, 27, 28], "961493": [18, 19, 28], "9621": 27, "963435": [18, 19, 28], "963642": 28, "9648": 22, "97": 19, "9747347375568": 22, "975485": 28, "982129": 27, "983725": 28, "9842": 22, "9846": 27, "99": [9, 18, 19, 28], "99981": 22, "999999999999449e": 28, "Also": 28, "An": [10, 12, 13, 15], "In": [4, 7, 8, 9, 18, 19, 23, 24, 25, 26, 28, 29, 33, 34], "So": 28, "Will": 9, "_25": 19, "_50": 19, "__fields__": 17, "__init__": 25, "__name__": 25, "__pycache__": 25, "_add_dataset": 12, "_description_": [9, 13], "_item": 9, "_requ": 9, "_summary_": 16, "_typ": 9, "_writeresult": 10, "a": [3, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 24, 26, 29, 31, 32, 33, 34, 36], "abc": [10, 12, 13, 14, 15], "abl": [18, 19, 23, 25, 26, 27, 28], "about": [7, 9, 12, 17, 18, 19, 23, 24, 25, 28, 31], "abov": 28, "abs": 28, "absenc": 9, "abstract": [10, 12, 13, 15], "accept": 28, "access": [0, 3, 6, 7, 9, 10, 13, 15, 17, 18, 19, 20, 23, 24, 26, 28, 31, 34], "accessibl": 34, "accessing": [7, 32], "according": 36, "accordingly": [9, 12, 25, 26], "account": [15, 18, 19, 28], "acknowledged": [9, 10], "activated": 0, "actually": [18, 19, 28], "adapt": [0, 7, 26, 33], "adapted": 25, "add": [0, 9, 15, 16, 17, 18, 19, 24, 26, 27, 31], "add_dataset": [9, 20, 23, 25, 31, 35], "add_dataset_to_us": [9, 20, 23, 25, 31, 35], "add_datasets_via_yaml": [9, 25, 31, 35], "add_demo_data_to_admindb": [9, 17], "add_exception_handl": [9, 17], "add_us": [9, 20, 23, 25, 31, 35], "add_user_with_budget": [9, 20, 23, 25, 31, 35], "add_users_via_yaml": [9, 25, 31, 35], "added": [9, 18, 19, 20, 23, 24, 25, 28, 31], "adding": [9, 16, 17, 18, 19, 25, 28, 29], "addition": [24, 25, 26, 28, 29], "additional": [4, 7, 18, 19], "address": [0, 9, 17, 31], "adeli": [18, 19, 23, 25, 28], "admin": [0, 13, 18, 19, 23, 25, 26, 34], "admin123": [23, 25], "admin_databas": [9, 12, 13, 15, 17, 25, 35], "admin_notebook": 24, "admin_requirement": 23, "admindatabas": [9, 10, 12, 13, 15], "admindb": 17, "admindbtyp": [9, 17, 35], "administ": 24, "administrat": [7, 31], "administration": [0, 7, 9, 10, 35, 36], "administrativ": [3, 7, 29, 31], "administrator": 0, "adminmongodatabas": [9, 10], "adminyamldatabas": [0, 9, 10], "advanced": [9, 14, 18, 19], "advised": 7, "affect": [18, 19, 28], "affected": 28, "aft": [10, 16, 18, 19, 23, 25, 27, 28], "afterward": [18, 19, 27, 28], "again": [9, 18, 19, 23, 24, 27, 28], "age": [19, 22, 23, 25], "age_max": 27, "age_min": 27, "age_transformation_pipelin": 27, "agre": [18, 19, 28], "aim": [7, 18, 19, 28], "aiming": [23, 25], "aks": 33, "alic": [18, 19, 23, 25, 28], "all": [6, 7, 8, 9, 16, 18, 19, 23, 24, 26, 27, 28, 31], "allocated": [7, 25], "allow": [18, 19, 25, 27], "allowed": [23, 25], "allowing": [25, 36], "allows": [23, 31, 36], "alon": 26, "already": [7, 9, 10, 13, 18, 19, 23, 25, 28], "alreay": [23, 25], "alternativ": [26, 28], "alternatively": [23, 25], "always": 31, "among": [18, 19, 23, 25, 28], "amount": [9, 13], "analys": [7, 18, 19, 28], "analysis": [0, 18, 19, 28], "and": [3, 4, 6, 8, 9, 10, 12, 13, 14, 16, 17, 24, 28, 31, 32, 33, 34, 36], "annotated": 23, "annotation": [17, 21, 24], "anoth": [18, 19, 23, 24, 25, 27, 28], "antarctica": [18, 19, 28], "antartica": [18, 19, 20, 23, 25, 28], "anti": 17, "anti_timing_att": [9, 35], "any": [0, 6, 9, 13, 14, 18, 19, 23, 25, 27, 28], "any_query": 6, "anyio": 23, "anyway": 28, "api": [7, 9, 16, 23, 24, 25, 26], "apidoc": 0, "app": [17, 25, 31, 35], "app_url": [6, 18, 19, 22, 27, 28], "appart": 26, "application": [0, 4, 18, 19, 21, 24, 27, 28, 29], "apply": [18, 27], "appropriat": 15, "arbitrary": 22, "architectur": 7, "archiv": [4, 7, 9, 10, 31], "are": [3, 4, 7, 9, 12, 14, 18, 19, 23, 24, 25, 26, 27, 28], "arg": [9, 10, 23, 25], "argument": [10, 17, 23, 25], "around": 28, "as": [0, 6, 7, 9, 16, 18, 19, 22, 23, 24, 25, 26, 27, 28, 29], "ask": [18, 19, 28], "asked": [18, 19, 27, 28], "assert": 9, "assert_almost_equal": 28, "assigned": [18, 19, 23, 25, 28], "associated": [6, 7, 18, 19, 23, 25, 28], "ast": [9, 20, 25], "asttok": 23, "async": [9, 17], "asyncgenerator": [9, 17], "asyncio": 23, "at": [0, 9, 15, 18, 19, 20, 23, 24, 25, 26, 28], "atom_domain": 19, "atomic": 10, "attack": [9, 17], "attribut": 19, "attributeerror": 19, "authorisation": 3, "automat": 26, "automatically": [0, 26], "availabl": [0, 7, 18, 19, 23, 24, 25, 26, 28, 29, 33], "averag": 28, "avg": [18, 19, 27, 28], "avg_0": 28, "avg_1": 28, "avg_2": 28, "avg_ag": 27, "avg_bill_length": [18, 19, 28], "avg_bill_length_mm": [18, 19, 28], "avg_bill_length_respons": 28, "avoid": [17, 28], "aws": [9, 31], "aws_access_key_id": [9, 17, 23, 25, 31], "aws_secret_access_key": [9, 17, 23, 25, 31], "axis": 19, "aymond": 21, "back": 12, "backcall": 23, "bas": [8, 9, 10, 12, 13, 14, 15, 16, 17], "based": [0, 3, 8, 9, 10, 12, 13, 15, 18, 19, 23, 25, 27, 28, 31], "basemodel": [13, 14, 17], "bash": 0, "basic": [9, 12, 35], "basic_dataset_stor": [9, 35], "basicdatasetstor": [9, 12], "basicmodel": 13, "be": [0, 8, 9, 10, 15, 16, 17, 18, 19, 24, 26, 27, 28, 29, 31, 34], "becaus": [18, 19, 23, 27], "been": [23, 24, 25], "befor": [9, 10, 16, 18, 19, 23, 24, 25, 28, 33], "begin": 33, "being": [6, 16], "below": [6, 12, 18, 19, 33], "best": 32, "bett": [18, 19, 28], "betwe": 28, "big": [18, 19], "bigg": 28, "bill": 28, "bill_depth_mm": [9, 18, 19, 23, 25, 28], "bill_length_max": [18, 19], "bill_length_min": [18, 19], "bill_length_mm": [9, 18, 19, 23, 25, 28], "bill_length_transformation_pipelin": [18, 19], "bind": [25, 26], "bird": 28, "bisco": [18, 19, 23, 25, 28], "bit": [23, 25, 28], "bitnamichart": [21, 23, 24], "black": 0, "blob": 23, "bob": [23, 25], "body": [9, 18, 19, 28], "body_mass_g": [9, 18, 19, 23, 25, 28], "bool": [8, 9, 10, 14, 15, 17], "boolean": [9, 23, 25, 27], "both": 16, "boto3": 23, "botocor": 23, "bound": [9, 18, 22, 23, 25, 27, 28], "boundary": [8, 9], "branch": 0, "brevity": 7, "brows": [24, 26, 34], "bucket": [9, 31], "budget": [3, 7, 8, 9, 10, 13, 14, 16, 20, 29, 31], "buget": [18, 19, 28], "build": [14, 21, 23, 24], "built": [14, 23, 24, 26], "but": [0, 18, 19, 25, 28], "button": 36, "by": [7, 8, 9, 10, 12, 16, 17, 18, 19, 24, 25, 26, 27, 28, 33, 36], "c": [19, 20, 23, 25, 26, 27, 31], "c0114": 0, "c0301": 9, "cabin": [23, 25, 27], "cach": 12, "cached": 15, "caching": 12, "call": [7, 17, 18, 19, 23, 25, 27, 28], "call_next": [9, 17], "callabl": [9, 10, 17], "calling": [9, 10, 17], "can": [0, 4, 6, 7, 9, 12, 15, 16, 17, 18, 19, 24, 26, 27, 28, 29, 32, 33, 34, 36], "candidat": 19, "cannot": [10, 15, 17, 18, 19, 23, 27], "capabiliti": [18, 19], "capture_output": 20, "car": 7, "cardinality": [18, 19, 23, 25, 27, 28], "careful": [18, 19, 28], "carried": [23, 25], "cas": [8, 9, 17], "categori": [18, 19, 23, 25, 27, 28], "cd": [21, 23, 24, 26, 34], "cell": [18, 19], "censor_dim": [18, 19, 23, 25, 28], "central": 19, "certain": [23, 24, 25, 26, 29], "ch": [0, 34], "chang": [12, 17, 19, 23, 24, 33], "changed": [18, 19, 28], "chapt": [33, 34], "chart": [0, 7, 24, 32], "chdir": [20, 21, 23, 24], "check": [3, 9, 10, 13, 14, 17, 18, 19, 22, 23, 24, 25, 26, 27, 28], "check_dataset_and_metadata_exist": [9, 35], "check_result_acknowledged": [9, 10], "check_user_exist": [9, 35], "check_user_has_dataset": [9, 35], "checking": [0, 24], "chf": 19, "chinstrap": [18, 19, 23, 25, 28], "choic": 31, "choos": [18, 19, 28, 32, 36], "chos": 26, "ci_95_lower_bound": 28, "ci_95_upper_bound": 28, "class": [8, 9, 10, 12, 13, 14, 15, 16, 17, 21, 24], "classic": 7, "classmethod": 16, "classvar": 17, "clean": [9, 23, 25, 31], "cleaning": [23, 25], "clear": [12, 23, 25], "cli": 29, "click": [0, 36], "client": [1, 4, 5, 10, 22, 25, 26], "client_input": [18, 19], "cliententententent": 19, "clon": 34, "clos": [18, 19, 28], "cloud": 33, "cloudflar": 23, "clust": [7, 23, 24, 32, 33], "cod": [0, 6, 7, 8, 24, 25], "codebas": [23, 25], "coding": [18, 19], "col_nam": [9, 18, 19, 22, 27], "coldheart": [23, 25], "collected": [18, 19, 23, 28], "collecting": 23, "collection": [9, 10, 16, 17, 20, 23, 25], "collections_model": [9, 35], "column": [9, 13, 17, 18, 22, 23, 25, 27, 28], "column_nam": [18, 19], "com": [3, 23, 25, 34], "combinator": 9, "comm": 23, "command": [6, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 31, 33, 34], "commonly": 7, "communicat": 33, "compar": [18, 19, 28], "complet": 0, "completed_process": 20, "completely": [23, 25], "component": 7, "compos": [0, 25, 26, 34], "composed": 7, "comprised": [23, 24], "comput": [18, 19, 27, 28], "computed": 17, "computedfieldinfo": 17, "computing": [18, 19, 28], "concatenat": 19, "concret": 14, "confident": [18, 19, 28], "config": [0, 9, 10, 12, 23, 25, 35], "config_path": 17, "configdict": 17, "configkeys": [9, 35], "configload": [9, 17], "configmaps": 24, "configuration": [7, 9, 17, 23, 24, 25, 26, 33], "configured": [0, 24, 33], "conforming": 17, "connect": [7, 18, 19, 28], "connecting": 10, "connection": [16, 29], "connection_paramet": 10, "connection_string": 10, "consol": 26, "constant": [12, 15, 18, 19, 22, 25, 27, 35], "constructor": 9, "contact": 3, "contain": [0, 9, 14, 23, 25, 26], "containing": [8, 9, 13, 33], "content": [5, 35], "contrib": [19, 22], "converted": [8, 9], "copied": 23, "copy": [15, 23, 24], "correct": [7, 10, 12, 14], "correctly": [10, 17, 24], "corresponding": 17, "cost": [8, 9, 13, 14, 17], "cost_q25": 19, "cost_q50": 19, "cost_q75": 19, "cost_r": [18, 19, 27], "could": [18, 19, 23, 24, 25, 28], "count": [9, 27, 28], "cours": [18, 19, 23, 27, 28], "cov": 29, "creat": [0, 9, 13, 16, 17, 18, 19, 20, 23, 26, 27, 28, 31, 34], "create_users_collection": [20, 23], "created": [15, 18, 19, 27, 28], "creating": 23, "creation": 25, "credential": [7, 24], "credit": [18, 19, 28], "critical": 28, "critical_valu": 28, "csv": [23, 25], "csv_domain": 19, "ctrl": [25, 26], "current": [9, 10, 23, 25], "current_delta": 10, "current_epsilon": 10, "currently": 7, "custom": [3, 17, 33], "customiz": 36, "d": [20, 23, 25, 31], "d_path": [25, 31], "d_url": 23, "daisy": [23, 25], "dat": 10, "data": [7, 9, 15, 16, 17, 18, 24, 25, 26, 27, 29, 31, 34], "databas": [0, 7, 9, 10, 12, 13, 16, 17, 18, 19, 24, 26, 27, 28, 29, 31], "database_factory": [9, 10], "database_nam": 10, "database_typ": [9, 17, 23, 25, 31], "datafram": [3, 8, 9, 13, 14, 15, 17, 18, 19, 27, 28], "datalab": 36, "dataset": [3, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 24], "dataset_cach": [9, 12], "dataset_collection": [20, 23, 25], "dataset_df": 15, "dataset_info": 9, "dataset_must_exist": [9, 10], "dataset_nam": [6, 8, 9, 10, 12, 15, 17, 18, 19, 22, 23, 25, 27, 28, 31], "dataset_observ": 15, "dataset_path": [9, 15, 17, 23, 25, 31], "dataset_stor": [9, 13, 17, 25, 35], "dataset_store_factory": [9, 12], "dataset_store_typ": [9, 35], "dataset_url": 23, "datasetofpathdb": [9, 17], "datasetofs3db": [9, 17], "datasetofus": [9, 17], "datasets_list": [9, 17, 23, 25], "datasetscollection": [9, 17], "datasetstor": [9, 12, 13], "datasetstoreconf": [9, 12, 17], "datasetstoretyp": [9, 17, 35], "dateutil": 23, "days": [23, 25], "db": [9, 10, 16, 20, 23, 25, 31, 35], "db_a": 31, "db_fil": [9, 17], "db_n": 31, "db_nam": [9, 17, 31], "db_p": 31, "db_pwd": 31, "db_typ": [9, 17, 35], "db_type_mongodb": [9, 35], "db_u": 31, "dbconfig": [9, 10, 17], "debugpy": 23, "dec": 21, "decid": [18, 19, 23, 25, 28], "decimal": 28, "decod": 25, "deconstructing": 16, "decorated": 10, "decorator": [10, 23], "decreas": 3, "dedicated": 0, "def": [19, 20, 25, 28], "default": [8, 9, 13, 14, 17, 18, 19, 23, 25, 26, 28, 31, 33], "defaultdb": 31, "defin": [18, 19], "defined": [17, 24, 28], "del": 31, "del_dataset": [9, 25, 31, 35], "del_dataset_to_us": [9, 20, 23, 25, 31, 35], "del_us": [9, 20, 23, 25, 31, 35], "delays": 9, "delet": [9, 16, 20, 23, 25, 31], "deleted": [9, 20, 23, 25, 31], "deleting": [16, 21, 24], "deletion": 16, "delta": [8, 9, 10, 13, 14, 17, 18, 19, 20, 23, 25, 27, 28, 31], "delta_cost": [9, 13, 18, 19, 27, 28], "demand": 36, "demo": [4, 17, 22, 24, 26], "demonstrating": [4, 29], "demonstration": 25, "depend": 9, "dependenci": 23, "dependency": [23, 24], "depending": [9, 23, 26, 36], "deploy": [7, 21, 23, 24, 32, 33, 36], "deployed": [3, 7, 21, 24, 33], "deploying": [7, 24, 26, 32], "deployment": [7, 29, 36], "deprecated": [21, 24], "depth": [18, 19, 28], "described": 10, "describing": [3, 8], "deserialisation": 8, "deserialization": [7, 8], "desired": 24, "detail": [7, 18, 19, 23, 25, 28], "detailed": 31, "develop": [17, 25], "develop_mod": [9, 17, 35], "developed": 7, "development": [7, 10], "developp": 24, "developped": 36, "developping": [18, 19, 27, 28], "deviation": 28, "df": [8, 9, 15, 17, 18, 19, 27, 28], "df_dummy": [18, 19, 22, 27, 28], "df_flipp": 28, "dial": 23, "dict": [8, 9, 10, 12, 13, 14, 15, 17], "dictionari": 9, "dictionary": [8, 9, 10, 13, 14, 15, 17, 18, 19, 23, 25, 28], "dictionnary": [9, 10, 13], "did": [18, 19, 28], "diff": 28, "differenc": [27, 28], "different": [0, 4, 7, 8, 12, 18, 19, 23, 25, 27, 28], "differential": [3, 18, 19, 28], "differentially": [18, 19, 27, 28], "diffprivlib": 17, "dig": [21, 24], "dimension": [18, 19, 28], "direct": 7, "directly": 31, "directory": [0, 23, 24, 26, 33, 34], "disabl": [0, 9, 23, 24], "disclosur": [18, 19], "discov": [0, 28, 32], "disk": 17, "display": [18, 19, 20, 21], "disposal": [18, 19, 24, 28], "distribution": [18, 28], "divergenc": 9, "divers": 7, "dnspython": 23, "do": [0, 3, 6, 18, 19, 24, 28, 31], "dock": [0, 23, 24, 31, 32, 34], "dockerclient": 25, "dockerfil": 34, "dockerhub": [23, 24, 26], "docs": [0, 9, 14, 23, 24, 26], "docstring": 0, "documentation": [8, 9, 32], "docummentation": [23, 24, 26], "does": [3, 9, 10, 12, 13, 14, 15, 18, 19, 28], "does_dataset_exist": [9, 10], "does_user_exist": [9, 10], "doing": [23, 25, 28], "domain": 19, "don": [10, 18, 19, 20, 23, 24, 25, 26, 28, 33], "down": [0, 20, 23, 24, 25, 26], "download": [7, 23, 24], "downloaded": 26, "downloading": [21, 23, 24], "dp": [8, 9, 12, 13, 14, 18, 19, 22, 27], "dp_librairy": [18, 19], "dp_librari": [9, 13], "dp_logic": [9, 35], "dp_queri": [9, 12, 25, 35], "dplibrari": [5, 8, 9, 12, 13, 14, 35], "dpqueri": [9, 12, 13, 14], "dprock": 26, "dpserial": 28, "dr": [18, 19, 20, 22, 23, 25, 28], "dream": [18, 19, 23, 25, 28], "drop": [9, 16, 31], "drop_collection": [9, 20, 23, 25, 31, 35], "ds_store_typ": [9, 17], "dscc": [0, 34], "dtyp": 19, "due": [3, 28], "dummi": 28, "dummy": [8, 9, 13, 16, 17, 22, 24], "dummy_dataset": [9, 35], "dummy_nb_rows": [8, 9, 13, 17], "dummy_opendp_query_handl": [9, 35], "dummy_r": [18, 19, 27, 28], "dummy_r25": 19, "dummy_seed": [8, 9, 13, 17], "dummy_smartnoise_sql_handl": [9, 35], "dummy_var_r": [18, 19, 27], "dummyopendpinp": [9, 17], "dummysnsqlinp": [9, 17], "duplicat": 0, "during": [8, 10], "e": [7, 8, 9, 18, 19, 31], "e0401": 0, "e0611": 0, "each": [7, 18, 19, 23, 25, 26, 27, 28], "easily": 36, "easy": 0, "eco_branch": [19, 22, 23, 25], "edit": 33, "education": [19, 22, 23, 25], "effect": 9, "efficiently": [7, 31], "eith": [17, 28], "eks": 33, "els": [18, 19, 20], "embarked": [23, 25, 27], "empty": [23, 25], "en": 9, "enabl": [7, 9, 18, 19, 27, 28], "enable_featur": [19, 22], "enabled": [16, 23, 24], "encoded": [13, 14], "encoding": 14, "encount": 0, "encountered": 8, "end": [8, 9, 16], "endpoint": [9, 16, 17, 18, 19, 26, 27, 28, 31], "endpoint_url": [9, 17, 23, 25, 31], "enforc": [9, 10, 12], "enforce_tru": 9, "engin": [23, 25], "enough": [3, 9, 14, 18, 19, 26, 28], "ensur": [7, 18, 19, 23, 25, 28, 34], "enum": [8, 17], "enumerat": [18, 19], "environment": [0, 7, 16, 18, 19, 23, 24, 26, 27, 28, 31], "epsilon": [8, 9, 10, 13, 14, 17, 18, 19, 20, 23, 25, 27, 28, 31], "epsilon_cost": [9, 13, 18, 19, 27, 28], "equal": [12, 18, 19, 27, 28], "err_msg": 28, "error": [0, 8, 9, 18, 19, 23, 25, 27, 28], "error_handl": [9, 35], "error_messag": [5, 8, 17], "especially": 33, "establish": 31, "estimat": [8, 9, 13, 14], "estimate_cost": [9, 13, 28], "estimate_opendp_cost": [5, 8, 9, 18, 19, 27, 35], "estimate_smartnoise_cost": [5, 8, 9, 18, 19, 27, 28, 35], "estimated": [8, 9, 13, 18, 19, 27, 28], "estimation": [18, 19, 28], "eta": 23, "etc": [7, 9, 17, 29], "eval": 23, "even": [18, 19, 28], "every": [18, 19, 23, 25, 28], "everything": [18, 19, 25, 26], "exact": 28, "exactly": [18, 19], "exampl": [3, 7, 17, 23, 25, 28, 29], "example_conf": [23, 25], "example_dummy_opendp": 9, "example_dummy_smartnoise_sql": 9, "example_get_admin_db_data": 9, "example_get_dummy_dataset": 9, "example_input": [9, 35], "example_opendp": 9, "example_smartnoise_sql": 9, "example_smartnoise_sql_cost": 9, "except": 0, "exception": [8, 9, 10, 13, 14, 17, 18, 19], "exceptiongroup": 23, "exec": 0, "exec_run": 25, "execresult": 25, "execut": [7, 8, 9, 18, 19, 20, 23, 24, 25, 27], "executed": [0, 7, 9, 16], "executing": [7, 8, 23, 25], "execution": 7, "exisiting": [23, 25], "exist": [3, 9, 10, 12, 13, 14, 15, 23, 25], "existing": [9, 23, 25, 31], "exit": [20, 23, 25], "expect": [18, 19, 27], "expected": [18, 19, 23, 25, 27, 28], "experiment": 24, "explain": [18, 19, 25, 28, 29], "explod": 19, "explor": 32, "exploration": 28, "exploring": 26, "exposed": [23, 24], "expr": 19, "expr_domain": 19, "expression": 19, "extensiv": 7, "external": [3, 7, 9, 13, 14, 17], "externallibraryexception": [9, 14, 17], "extract": [18, 19], "f": [18, 19, 20, 21, 23, 24, 27, 28, 31, 33], "f3b2a691537260044746bc4a8898e9ae68e8c29864639737b6da920f99aebe97": [21, 24], "f64": 9, "facilitat": [7, 29], "facing": 7, "fail": [3, 12, 17, 18, 19, 27], "failed": [3, 23], "failur": 3, "fals": [8, 9, 10, 14, 17, 18, 19, 20, 23, 24, 25, 27, 28, 31], "familiariz": 29, "far": [7, 23, 25, 27], "fashion": 23, "fastapi": [7, 9, 17, 23, 24], "featur": [4, 23, 24], "federal": 19, "femal": [18, 19, 23, 25, 27, 28], "fetched": [23, 25], "few": [0, 18, 19, 23, 25, 26, 27, 28], "field": [9, 10, 16, 17, 18, 19, 20, 23, 24, 25, 28, 31], "fieldinfo": 17, "figsiz": 19, "figur": 19, "fil": [0, 7, 9, 15, 16, 24, 26, 31, 33], "filenam": [18, 19, 20, 21], "filepath": 17, "fin": [28, 36], "finally": [18, 19, 28], "find": [4, 7, 26, 36], "finding": 28, "finish": [9, 25], "first": [0, 10, 13, 14, 16, 18, 19, 23, 24, 25, 26, 27, 28, 34], "fit": [12, 32, 33], "fix": 3, "fixed_delta": [8, 9, 17, 18, 19], "fixed_smoothed_max_divergenc": [9, 35], "fixm": 0, "fixtur": 16, "flag": [9, 10, 18, 19, 28], "flak": 0, "flake8": 0, "flipp": [18, 19], "flipper_length_mm": [9, 18, 19, 23, 25, 28], "flipper_length_respons": 28, "float": [8, 9, 10, 13, 14, 17, 18, 19, 22, 23, 25, 27, 28], "fly": [18, 19], "fold": 0, "follow": [6, 26, 33, 34], "following": [6, 7, 9, 18, 19, 23, 25, 26, 33, 34, 36], "follows": 7, "folow": 36, "fonction": [18, 19, 28], "fontsiz": 19, "for": [3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 23, 24, 25, 26, 31, 32, 33, 34, 36], "forget": [18, 19], "format": [0, 9, 15, 17, 18, 19, 23, 25, 28], "formating": 0, "formatted": [8, 10], "formatting": 20, "found": [23, 25, 29], "fr": [19, 20, 21, 22, 23, 24, 25], "franc": 36, "free": 23, "freshly": 24, "from": [0, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 31], "frostina": [23, 25], "fso": [6, 22, 23, 25], "fso_dpserial": 28, "fso_income_metadata": 19, "fso_income_synthetic": [19, 20, 22, 23, 25], "fso_income_synthetic_metadata": [23, 25], "fullnameoverrid": 24, "func": [9, 10, 23, 25], "function": [6, 7, 8, 9, 10, 12, 16, 18, 19, 20, 23, 25, 27, 28, 31], "function_map": 25, "functionaliti": [17, 31], "functionnal": [18, 19], "functionnaliti": 4, "functionnality": [18, 19, 27, 28], "furth": 0, "futur": 23, "g": [7, 8, 9, 27], "gaussian": 9, "gav": [18, 19, 28], "generat": [0, 8, 9], "generated": [0, 26], "generating": [0, 8, 9], "generation": 16, "gentoo": [18, 19, 23, 25, 28], "get": [0, 9, 10, 12, 13, 14, 15, 17, 23, 24, 25, 26, 31, 33, 34], "get_and_set_may_user_query": [9, 10], "get_conf": [9, 17], "get_dataset": [25, 31], "get_dataset_field": [9, 10], "get_dataset_metadata": [5, 8, 9, 10, 18, 19, 22, 27, 28, 35], "get_dummy_dataset": [5, 8, 9, 18, 19, 22, 27, 28, 35], "get_dummy_dataset_for_query": [9, 13], "get_epsilon_or_delta": [9, 10], "get_initial_budget": [5, 8, 9, 10, 18, 19, 27, 28, 35], "get_list_of_dataset": [9, 35], "get_list_of_datasets_from_us": [9, 35], "get_list_of_us": [9, 35], "get_memory_usag": [9, 15, 35], "get_metadata": [9, 15], "get_mongodb": [9, 10], "get_mongodb_url": [9, 10], "get_output_measur": [13, 14], "get_pandas_df": [9, 15], "get_previous_queri": [5, 8, 18, 19], "get_queri": [9, 12], "get_remaining_budget": [5, 8, 9, 10, 18, 19, 27, 28, 35], "get_stat": [9, 35], "get_total_spent_budget": [5, 8, 9, 10, 18, 19, 27, 28, 35], "get_us": [25, 31], "get_user_dataset": [25, 31], "get_user_previous_queri": [9, 10, 35], "getdbdata": [9, 17], "getdummydataset": [9, 13, 17], "git": 34, "github": [0, 4, 7, 29, 34], "githubusercontent": [23, 25], "giv": [9, 10, 12, 13, 17, 18, 19, 20, 23, 25, 26, 27, 28, 31], "gke": 33, "go": [0, 21, 23, 24, 25, 28, 34], "goes": [0, 24, 28], "good": [18, 19, 28], "gramm": [18, 19], "grounbdbreaking": [18, 19, 28], "group": 28, "groupby": 19, "grouping_column": 19, "gt": [17, 18, 19, 23, 25], "guess": [18, 19, 28], "guid": [6, 33], "guidanc": 32, "h": [20, 23, 25, 27], "h_0": 28, "h_a": 28, "hackathon": 7, "had": [18, 19, 25, 28], "handl": [7, 8, 9, 13], "handle_query": [9, 13], "happ": [3, 23, 24], "happened": [18, 19], "hardcoded": 17, "has": [7, 9, 10, 18, 19, 23, 24, 25, 28], "has_user_access_to_dataset": [9, 10], "hav": [0, 3, 6, 9, 10, 13, 18, 19, 20, 23, 24, 25, 27, 28, 33, 34], "he": [23, 25], "head": [9, 18, 19, 22, 27, 28], "heard": [23, 25], "helm": [0, 7, 20, 24, 32], "help": [3, 4, 20, 23, 24, 25], "henc": [18, 19, 28], "her": [0, 4, 18, 19, 25, 27, 28, 33], "hesitat": [23, 25], "high": [18, 19, 27, 28], "him": [23, 25], "his": [23, 25], "histogram": [18, 19], "history": 9, "hold": [12, 13, 17, 24, 25, 26], "hom": 23, "hook": 16, "host": [23, 24], "host_ip": [9, 17], "host_port": [9, 17], "hour": 10, "how": [4, 7, 18, 19, 23, 24, 25, 28, 29, 31, 32, 34], "howev": [18, 19, 28], "html": [0, 9, 14], "http": [0, 7, 8, 15, 18, 19, 22, 26, 27, 28, 34], "httpreadseek": 23, "https": [0, 9, 14, 19, 20, 21, 22, 23, 24, 25, 34], "hue": 19, "hypothes": 28, "i": [18, 19], "icebergina": 28, "icerbegina": [18, 19, 28], "icergina": [18, 19, 28], "id": [9, 23, 31], "idea": [18, 19, 28], "idna": 23, "if": [0, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 28, 31, 33], "ignor": 0, "ignore_index": 19, "iloc": [18, 19, 27, 28], "imag": [18, 19, 20, 26], "image_demo_admin_sid": 20, "image_demo_client": [18, 19], "image_demo_deployment_client": 21, "image_demo_deployment_contain": 21, "image_demo_deployment_servic": 21, "impact": [18, 19, 27, 28], "implement": 7, "implementation": [12, 14], "implemented": 7, "implementing": 12, "import": [0, 6, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28], "important": 24, "in_memory_dataset": [9, 35], "includ": [7, 10], "including": 8, "incom": [23, 25], "income_25": 19, "income_50": 19, "income_averag": 22, "income_average_pipelin": 22, "income_lower_bound": 19, "income_max": 22, "income_metadata": 22, "income_min": 22, "income_synthetic_data": [23, 25], "income_upper_bound": 19, "incoming": 7, "indeed": [23, 25, 26], "index": [18, 19], "index_nam": [18, 19], "indicating": 9, "info": [9, 25], "information": [7, 9, 12, 14, 17, 18, 19, 23, 25, 27, 28, 31], "infrastructur": [7, 32], "ingress": [21, 23, 24], "ingressclassnam": [21, 24], "inital": [18, 19, 27, 28], "initial": [6, 8, 9, 10, 18, 19, 27, 28], "initial_delta": [9, 17, 18, 19, 23, 25, 27, 28, 31], "initial_epsilon": [9, 17, 18, 19, 20, 23, 25, 27, 28, 31], "initialis": [4, 6, 17], "initialisation": 10, "initialization": 9, "initialized": [9, 12, 13, 15, 20, 23, 25, 31], "initiat": 36, "inlin": 23, "inmemorydataset": [9, 13, 15], "input": [17, 19], "input_data_typ": 19, "input_model": [9, 35], "inse": 36, "insid": 25, "inspecting": [23, 24], "install": [0, 4, 6, 23, 24, 25, 33], "installation": [7, 23, 24, 33], "installed": [0, 18, 19, 23, 24, 27, 28, 33, 34], "installing": [23, 24, 32], "instanc": [9, 10, 12, 13, 14, 15, 17], "instantiat": [10, 12, 18, 19, 27], "instead": [18, 19, 21, 24, 28, 31], "instruction": [6, 31, 32, 33, 34], "int": [8, 9, 12, 13, 14, 15, 17, 19, 22, 23, 25, 27], "int32": 19, "integration": [0, 16], "intended": [22, 28], "intention": [23, 25], "interact": [0, 7, 18, 19, 23, 25, 27, 28, 31], "interacting": 7, "interaction": [7, 18, 19, 27, 28], "interested": [18, 19, 25, 28], "interfac": 12, "internal": [3, 17], "internalserverexception": [9, 10, 12, 13, 14, 15, 17], "interpreted": 17, "into": [0, 7, 12, 15, 23, 24, 26, 34], "introduction": 32, "invalid": [3, 17], "invalidqueryexception": [9, 10, 13, 14, 17, 18, 19, 27], "investigation": [23, 25], "io": [0, 21, 23, 24], "ipykernel": 23, "ipython": [18, 19, 20, 21, 23], "iris": [20, 23, 25], "iris_metadata": [23, 25], "irrelevant": 28, "is": [0, 3, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 21, 23, 24, 25, 26, 27, 28, 31, 33, 34, 36], "is_measurement": [13, 14], "island": [9, 18, 19, 23, 25, 28], "issu": [0, 3, 17, 24], "it": [0, 7, 8, 9, 16, 18, 19, 22, 24, 26, 27, 28, 29, 31, 34], "its": [6, 7, 12, 17, 23, 25], "jack": [23, 25, 27], "jedi": 23, "jitt": [9, 35], "jmespath": 23, "json": [9, 10, 13, 14], "jsonrespons": [9, 17], "jupyt": [0, 25], "jupyter_client": 23, "jupyter_cor": 23, "jupyterlab": 26, "just": [18, 19, 22, 23, 24, 25, 27, 28], "kb": 23, "keep": [9, 25, 26], "keeping": [23, 24], "kept": [18, 19], "key": [9, 10, 18, 19, 22, 23, 27, 31], "keys": 9, "kind": 33, "know": [18, 19, 26, 27, 28], "knows": [18, 19, 28], "kubectl": [23, 24, 33], "kubernet": [7, 32], "kwarg": 9, "l": 27, "l1": 19, "lab": [7, 19, 20, 21, 22, 23, 24, 25], "lambda": [19, 25], "lanc": [0, 36], "laplacian": [18, 19, 27], "larg": [12, 28], "last": [10, 19, 21, 23, 24, 25], "lat": [21, 23, 24, 25], "latt": 16, "lazily": 12, "lazy": 12, "lazyfram": 19, "lazyframe_domain": 19, "le": 17, "learn": [18, 19, 24, 28, 32], "least": 12, "lemaniqu": 19, "len": [18, 19, 25], "less": 28, "let": [19, 24, 25, 28], "level": 28, "lf_domain": 19, "lhs": 9, "lib": [14, 23], "librairi": 8, "librari": [7, 9, 12, 14, 17, 23], "library": [3, 4, 6, 7, 9, 12, 13, 14, 17, 25], "lifespan": [9, 35], "lik": [17, 18, 19, 28, 33], "limit": [18, 19], "limited": [18, 19, 28], "lin": [19, 23, 24, 25, 31, 33], "link": [7, 36], "linux": 0, "list": [0, 8, 9, 10, 15, 17, 19, 25, 31], "listed": 23, "literal_eval": [20, 25], "littl": 28, "liv": [17, 26], "load": [17, 23], "load_conf": [9, 17], "loading": [9, 12], "local": [7, 9, 15, 16, 17, 18, 19, 24, 27, 28, 32, 33], "local_db": 23, "localhost": [0, 18, 27, 34], "locally": [18, 19, 24, 26, 28, 32], "locat": 36, "located": [15, 23, 24, 26, 34], "location": [9, 15, 23, 24], "log": [23, 24, 26], "log_level": [9, 17], "loggr": [9, 35], "logic": 7, "login": [23, 24], "lomas": [0, 4, 6, 20, 21, 22, 23, 24, 27, 31, 32, 33, 34], "lomas_client": [1, 2, 6, 7, 18, 19, 21, 22, 24, 27, 28], "lomas_client_dev": [25, 26], "lomas_serv": [0, 1, 8, 20, 21, 23, 24, 30], "lomas_server_dev": [0, 19, 22, 25, 26, 28], "lomas_streamlit_dev": 25, "lomas_test_mongo_integration": 16, "long": 6, "look": [18, 19, 23, 24, 25, 28], "looking": [23, 25], "lookup": 23, "lord": [23, 25], "loss": 9, "lot": 28, "low": [18, 19, 22, 23, 25, 27, 28], "lower_bound": [18, 19, 27, 28], "lru": [9, 12, 17, 35], "lru_cach": 9, "lru_dataset_stor": [9, 35], "lru_dataset_store_max_siz": [9, 35], "lrudatasetstor": [9, 12], "lrudatasetstoreconf": [9, 17], "ls": 25, "lt": [18, 19, 23, 25], "m": [0, 19], "m_db": [23, 25, 31], "m_s3_ak": [23, 31], "m_s3_sak": [23, 31], "m_s3_url": [23, 31], "m_s3b": [23, 31], "m_s3k": [23, 31], "machin": [32, 33, 34], "mad": [18, 19, 23, 24, 28], "madam": [23, 25], "magic": [23, 24], "magnitud": [9, 17], "main": 7, "mak": [0, 12, 18, 19, 23, 24, 25, 26, 27, 28, 33], "make_basic_composition": 19, "make_chain_tt": 9, "make_collect": 19, "make_dummy_dataset": [9, 13], "make_gaussian": [8, 9], "make_quantile_pipelin": 19, "make_select_column": [9, 18, 19, 22, 27], "make_split_datafram": [9, 18, 19, 22, 27], "make_zcdp_to_approxdp": [8, 9], "making": 29, "mal": [18, 19, 23, 25, 27, 28], "malicious": [23, 25], "manag": [7, 12, 29], "managed": [25, 26], "management": [10, 12, 18, 19], "managing": [7, 29, 31], "mapping": 17, "margin": 19, "mass": [18, 19, 28], "mast": [0, 23, 25], "match": [8, 17], "matplotlib": [19, 23], "max_divergenc": [9, 35], "max_id": [9, 17, 18, 19, 22, 23, 25, 27, 28], "max_memory_usag": [9, 12, 17], "maximum": [12, 28], "may": [3, 9, 10, 16, 20, 23, 25, 31, 36], "may_query": [9, 10, 17, 23, 25], "may_user_query": [9, 10], "mb": 23, "mcfreez": [23, 25], "md": 7, "mean": 28, "meant": 17, "meas": [18, 19, 22, 27], "measur": 14, "measurement": [3, 8, 9, 14, 17, 18, 19, 22, 27], "mechanism": [3, 8, 9, 14, 17, 18, 19], "medata": 25, "memory": [9, 12, 13, 15], "merg": [17, 19], "messag": [3, 8, 20, 23, 25, 26], "meta": [23, 25], "metadata": [7, 8, 9, 10, 13, 15, 17, 23, 25, 31], "metadata_aws_access_key_id": [9, 31], "metadata_aws_secret_access_key": [9, 31], "metadata_database_typ": [9, 31], "metadata_endpoint_url": [9, 31], "metadata_path": [9, 17, 23, 25, 31], "metadata_s3_bucket": [9, 31], "metadata_s3_key": [9, 31], "metadataofdataset": [9, 17], "metadataofpathdb": [9, 17], "metadataofs3db": [9, 17], "metatada": 31, "method": [0, 9, 12, 16, 17, 25, 32], "methodnam": 16, "metric": 19, "mib": 15, "micro": 7, "middlewar": [9, 35], "might": [0, 10, 18, 19, 23, 25, 27, 28], "mind": [23, 25, 26], "minikub": 33, "minimum": 17, "minio": [23, 25], "misbehaving": 23, "missing": 0, "mistak": [18, 19], "mittleland": 19, "mm": [18, 19, 28], "mod": [17, 18, 19, 24, 25], "model": [7, 17], "model_computed_field": [9, 17], "model_conf": [9, 17], "model_field": [9, 17], "modification": 26, "modified": [25, 33], "modify": [23, 25], "modifying": 32, "modul": [0, 5, 19, 23, 25, 35], "modulindex": 7, "mon": 24, "mondodb": 0, "mongo": [9, 25, 26], "mongodata": [0, 25, 26, 34], "mongodb": [0, 7, 9, 10, 16, 17, 24, 26, 29, 34, 35], "mongodb_addr": [9, 35], "mongodb_admin": [0, 16, 20, 23, 25, 35], "mongodb_admin_cli": [25, 31, 35], "mongodb_databas": [9, 35], "mongodb_port": [9, 35], "mongodbconf": [9, 17], "month": 19, "mor": [0, 14, 18, 19, 23, 25, 26, 28], "most": [19, 23, 25], "mount": [25, 26], "mounted": [25, 26], "mov": [23, 24, 34], "mp": [20, 23, 25, 31], "mr": [23, 25], "mrs": [23, 25], "much": [18, 19, 28], "multipl": [18, 19, 23, 24, 25, 28, 29], "must": [8, 9, 10, 18, 19, 23, 24, 25, 26, 27, 28, 31], "mwaskom": [23, 25], "mypy": 0, "n": 20, "nam": [0, 6, 7, 8, 9, 10, 12, 13, 15, 17, 18, 19, 21, 23, 24, 25, 27, 28, 31], "name_of_dataset_you_want_to_query": 6, "named": [10, 18, 19, 23, 25, 28], "nameoverrid": 24, "namespac": [21, 24], "nan": [14, 28], "navigat": [33, 34], "nb_0": 28, "nb_1": 28, "nb_2": 28, "nb_it": 14, "nb_passeng": 27, "nb_penguin": [18, 19, 28], "nb_row": 9, "nb_rows": [8, 9, 13, 18, 19, 22, 27, 28], "necessary": 7, "need": [18, 19, 23, 24, 25, 26, 27, 28, 32, 33, 34, 36], "nest": 23, "nev": [3, 7, 18, 19, 28], "new": [9, 10, 17, 23, 25], "next": 24, "no": [0, 9, 12, 17, 18, 19, 23, 25, 26, 28], "nobody": [18, 19, 28], "nois": [18, 19, 27, 28], "noisy": 28, "non": [8, 9, 10, 12, 15, 16, 17, 18, 19, 21, 23, 24], "nonetyp": 17, "noqa": 9, "north": 19, "not": [0, 3, 7, 8, 9, 10, 12, 13, 14, 15, 17, 18, 19, 21, 24, 26, 27, 28, 31], "notebook": [0, 4, 18, 19, 21, 23, 24, 25, 26, 28, 29], "nothing": [23, 25], "notic": 19, "notified": 12, "notify": 12, "now": [6, 18, 19, 24, 26, 27, 28, 31, 34], "no\u00efs": 17, "np": [18, 19, 27, 28], "nsos": 7, "null": 28, "num_rows": 22, "num_rows_pipelin": 22, "numb": [8, 9, 14, 15, 28], "numpy": [18, 19, 27, 28], "o": [0, 27, 31], "object": [8, 9, 12, 13, 14, 17, 18, 19, 25, 27, 28], "oblivious": 7, "observ": 15, "obtain": [18, 19, 28], "occur": [3, 13, 17], "occurred": 23, "oci": [21, 23, 24], "od": [23, 25, 31], "of": [0, 3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 24, 26, 31], "offered": [18, 19, 23, 25, 28], "offic": 19, "official": 33, "ofs_dpserial": 28, "old": 27, "om": [23, 25, 31], "on": [3, 7, 8, 9, 10, 12, 13, 14, 15, 17, 24, 25, 26, 28, 31, 32, 34], "onc": [9, 18, 19, 23, 24, 25, 26, 27, 28, 33, 34, 36], "one": [6, 9, 12, 13, 14, 18, 19, 23, 24, 25, 26, 27, 28, 33], "ones": [23, 25], "ongoing": [9, 13], "only": [7, 15, 16, 18, 19, 23, 24, 25, 26, 27, 28], "onyxia": [22, 23, 29, 32], "open": [6, 23, 34], "opendp": [3, 4, 5, 7, 8, 9, 13, 17, 28, 35], "opendp_json": [9, 17, 18, 19], "opendp_pip": 14, "opendp_pipelin": [8, 9, 18, 19, 22, 27], "opendp_query": [5, 8, 18, 19, 22, 27], "opendp_query_handl": [9, 35], "opendpinp": [9, 14, 17], "opendpmeasurement": [9, 14, 35], "opendpqueri": [13, 14], "operation": [10, 20, 23, 25], "opportunity": 28, "option": [23, 28, 31, 32], "optional": [8, 9, 13, 14, 17, 18, 19, 28, 31, 36], "optionnal": [18, 19, 27], "optionnally": 0, "optionnaly": [18, 19], "or": [0, 3, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 23, 24, 25, 26, 28, 31, 33], "ord": [12, 17, 18, 19, 23, 25, 28], "ordereddict": 12, "org": [9, 14], "oriental": 19, "originally": 7, "os": [20, 21, 23, 24], "oth": [7, 9, 12, 13, 14, 17, 18, 19, 23, 24, 25, 28], "otherwis": [10, 14, 17, 23, 25], "our": [4, 7, 19, 23, 24, 25, 26, 29], "out": [19, 23, 25, 26], "outdated": [21, 24], "output": [14, 19, 20, 25, 28], "overall": [10, 15], "overriding": [9, 14], "overview": 29, "overwrit": [9, 23, 25, 31], "overwrite_dataset": [9, 23, 25, 31], "overwrite_metadata": [9, 23, 25, 31], "own": [7, 23, 24, 25, 26, 29, 31], "p": 27, "packag": [2, 5, 23, 30, 35], "packaging": 23, "pag": [0, 18, 19, 23, 25, 26, 28], "palett": 19, "pandas": [8, 15, 17, 19], "param": [9, 17], "paramet": [0, 3, 6, 8, 9, 10, 12, 13, 14, 15, 17, 18, 19, 24, 25, 26, 27, 28, 31, 36], "parch": [23, 25, 27], "parso": 23, "part": [7, 24], "partial_chain": 9, "participat": [18, 19, 28], "particular": [10, 26], "particularly": [18, 19, 27], "passed": [23, 25], "passeng": 27, "passengerid": [23, 25, 27], "password": [9, 17, 23, 24, 26, 31], "path": [9, 10, 15, 17, 19, 23, 25, 31, 35], "path_dataset": [9, 35], "path_db": [9, 17, 25, 31], "pathdataset": [9, 15], "paulineml": 24, "pclass": [23, 25, 27], "pd": [8, 9, 13, 14, 15, 17, 19], "penguin": [4, 9, 20, 23, 25], "penguin_metadata": [18, 19, 20, 23, 25], "perform": [13, 14, 23, 25, 28, 31], "performed": [0, 9], "performing": 0, "permission": 7, "persist": 26, "persistenc": 34, "persistent": [25, 26], "personal": [23, 24], "pet": 7, "petal_length": [23, 25], "petal_width": [23, 25], "pexpect": 23, "pickleshar": 23, "pip": [6, 18, 19, 23, 25, 27, 28], "pipelin": [3, 8, 9, 14, 18, 27], "pl": 19, "plac": 25, "platform": [7, 18, 19, 36], "platformdir": 23, "play": 19, "pleas": [7, 21, 24], "plt": 19, "png": [18, 19, 20, 21], "poc": 23, "pod": [23, 24], "point": 7, "port": [9, 17, 25, 31], "possibiliti": [18, 19], "possibility": [18, 19], "possibl": [0, 9, 18, 19, 27, 28, 31], "post": 28, "postprocess": [8, 9, 17, 18, 19], "postprocessing": [8, 9, 18, 19, 28], "potential": [7, 9, 28], "powerful": [18, 19, 28], "practical": [4, 23], "pref": 28, "prelud": [19, 22], "prepar": [7, 10, 31], "preparation": [18, 24, 28], "prepare_save_query": [9, 10], "prerequisit": 32, "presenc": 9, "present": [18, 19, 28], "presented": 6, "prevent": 17, "previous": [8, 9, 10, 18, 19, 25, 27, 31], "previous_queri": [9, 18, 19], "previously": [18, 19, 23, 24, 28], "primary": 7, "print": [18, 19, 20, 22, 23, 25, 27, 28, 31], "printed": 26, "privacy": [3, 8, 9, 14, 18, 19, 28], "privat": [7, 8, 9, 12, 15, 28], "private_dataset": [9, 13, 14, 25, 35], "private_dataset_factory": [9, 15], "private_dataset_observ": [9, 35], "privatedatabasetyp": [9, 17, 35], "privatedataset": [9, 12, 13, 14, 15], "privatedatasetobserv": [9, 12, 15], "probably": 14, "probl": [18, 19, 28], "proceed": 33, "process": [0, 17, 33, 36], "processed": [18, 19, 27], "processing": [7, 18, 19, 27, 28], "production": 23, "profession": [19, 22, 23, 25], "project": [0, 18, 19, 28, 36], "promised": [18, 19, 28], "prompt": 23, "properly": [17, 18, 19, 28], "protect": 9, "provid": [0, 7, 29, 31, 32, 33, 34], "provided": [8, 9, 10, 18, 19, 23, 25, 27, 28, 31, 33], "providing": 6, "ps": [25, 26], "psutil": 23, "ptyprocess": 23, "public": [0, 18, 19, 23, 24, 28], "publish": 0, "published": [23, 24], "pull": 0, "pulled": [21, 24], "pur": 23, "purpos": 25, "push": [21, 23, 24], "pushed": [23, 24], "put": [12, 19, 23], "py": [13, 16, 20, 23, 25, 31], "py3": 23, "pyaml": 23, "pydantic": [17, 23], "pydantic_cor": 23, "pygment": 23, "pylint": [0, 9], "pymongo": [10, 23], "pypi": 7, "pyplot": 19, "python": [0, 6, 9, 18, 19, 20, 23, 25, 31], "python3": 23, "pyyaml": 23, "pyzmq": 23, "q": [23, 25, 27], "q1": 19, "q2": 19, "q25": 19, "q3": 19, "q50": 19, "q75": 19, "qualnam": [8, 9], "quantil": 19, "quantile_data": 19, "queen": [18, 19, 28], "quellcod": [8, 9, 10, 12, 13, 14, 15, 16, 17], "queri": [3, 4, 7, 8, 9, 10, 12, 13, 14, 17, 28, 31], "querier_factory": [13, 14], "queriermanag": 12, "queries_archiv": [10, 20, 23, 31], "query": [3, 7, 8, 9, 10, 13, 14, 16, 17, 20, 23, 25, 31], "query_json": [9, 10, 13, 14], "query_respons": [9, 13, 18, 19, 22, 27, 28], "query_str": [9, 17, 18, 19], "query_typ": [12, 13], "queryhandl": [9, 13], "querying": [7, 10, 18, 19, 28], "r": [20, 23], "r0903": 0, "r25": 19, "r50": 19, "r75": 19, "rais": [9, 10, 23, 25], "raised": 9, "random": [8, 9, 17, 18, 19, 28], "randomly": [18, 19], "rang": [18, 19], "rath": 0, "raw": [9, 17, 23, 25], "re": [6, 23, 25, 28], "read": 15, "readabl": 20, "readm": 7, "ready": [18, 19, 23, 24, 25, 28], "real": 13, "really": [18, 19, 28], "reasearch": 7, "reason": [3, 9], "rebuild": 23, "received": 10, "recent": [19, 23, 25], "recently": 12, "recommended": [0, 28], "reconstruct": 14, "reconstruct_measurement_pipelin": [13, 14], "reconstructed": [14, 17], "recreat": 26, "reduc": 28, "ref": 7, "referenc": [7, 12, 13], "regard": 7, "regarding": 0, "regardless": 26, "region": [19, 22, 23, 25], "region_count": 19, "registered": [18, 19, 27, 28], "registry": [21, 23, 24], "reject": 28, "related": [0, 7, 9], "releas": 20, "relevant": [3, 18, 19], "reload": [9, 17], "reloaded": 17, "rely": 0, "remaining": [7, 8, 9, 10, 18, 19, 27, 28], "remaining_delta": [9, 18, 19, 27, 28], "remaining_epsilon": [9, 18, 19, 27, 28], "remot": [15, 28], "remote_http_db": [20, 23], "remotely": 19, "remov": [9, 10, 12, 23, 26], "replac": [17, 19, 20, 23, 25], "repo": [0, 21, 23, 24], "repons": 17, "repository": [4, 7, 23, 24, 26, 29, 34], "represent": [18, 19, 28], "representation": [13, 14], "representing": [8, 9], "reproducibility": [18, 19, 28], "requ": [0, 3, 7, 8, 9, 13, 14, 17, 18, 19, 23, 27, 28], "requested": 10, "requested_by": [9, 13, 18, 19], "requir": [16, 26, 31], "required": [6, 7, 17, 18, 19, 23, 25, 26, 31], "requirement": [0, 23, 32, 33, 36], "res": [6, 8, 10, 28], "res_local": 28, "res_serv": 28, "res_server_dummy": 28, "research": [18, 19, 23, 25, 27, 28], "respectively": 19, "respons": [8, 9, 10, 13, 14, 17, 18, 19, 25, 27, 28], "responsibl": [3, 7, 18, 19, 27, 28], "rest": [7, 18, 19, 23, 25, 28], "result": [8, 9, 10, 13, 14, 18, 27, 28], "result_local_dummy": 28, "resulting": [0, 14, 17, 23, 25], "retriev": [8, 9, 10, 25], "return": [8, 9, 10, 12, 13, 14, 15, 17, 18, 19, 20, 25, 27, 28], "returned": [15, 18, 19, 27, 28], "reveal": [23, 25], "revision": [21, 24], "rhs": 9, "right": [3, 24], "risk": [18, 19, 28], "root": [18, 19, 24, 26, 27, 28], "round": [18, 19, 27, 28], "row": [18, 19, 28], "row_privacy": [9, 17, 18, 19, 23, 25, 27, 28], "rows": [8, 9, 28], "rst": 0, "rstrip": 20, "ruckgab": [8, 9, 10, 12, 13, 14, 15, 17], "ruckgabetyp": [8, 9, 10, 12, 13, 14, 15, 17], "run": [0, 6, 20, 23, 24, 25, 26, 28, 33, 34], "run_command": 25, "run_integration_t": 0, "running": [0, 9, 16, 21, 23, 24, 25, 26, 33, 34], "runt": 16, "runtim": [0, 17, 36], "runtime_arg": [9, 35], "s": [0, 7, 17, 18, 19, 22, 24, 25, 27, 28, 36], "s3": [4, 7, 9, 15, 17, 23, 31, 35], "s3_ak": [23, 31], "s3_bucket": [9, 17, 23, 25, 31], "s3_dataset": [9, 35], "s3_db": [9, 17, 23, 25, 31], "s3_key": [9, 17, 23, 25, 31], "s3_paramet": 15, "s3_sak": [23, 31], "s3_url": [23, 31], "s3b": [23, 31], "s3dataset": [9, 15], "s3k": [23, 31], "s3transf": 23, "saf": 23, "safeguard": [18, 19, 27, 28], "sam": [18, 19, 23, 24, 25, 28, 34], "sampl": 28, "satisfied": [23, 36], "sav": [10, 23, 33], "save_current_databas": [9, 10], "save_query": [9, 10], "saving": [21, 23, 24], "scal": [9, 18, 19, 22, 27], "scenarios": 4, "schema": [23, 25], "schema_overrid": 19, "scientist": 7, "scop": [25, 26], "script": [0, 20, 23, 25], "sdd": [20, 23, 24], "seaborn": [19, 23, 25], "seamless": [7, 18, 19, 27, 28], "second": [10, 13, 14, 24, 25], "secondly": 26, "secret": [9, 17, 23, 24, 31], "secrets_path": 17, "section": [4, 7, 29, 31], "secur": [18, 19, 25, 27], "see": [4, 7, 8, 9, 14, 23, 25, 26, 28], "seed": [8, 9, 13, 18, 19, 22, 27, 28], "seem": 28, "seen": [18, 19, 27, 28], "sees": [18, 19, 28], "select": [0, 9, 18, 19, 27, 28, 36], "selected": [18, 19, 28], "send": 8, "sending": [18, 19, 27, 28], "sensitiv": [7, 15, 18, 19, 28], "sent": [7, 10], "sep": 24, "sepal_length": [23, 25], "sepal_width": [23, 25], "separat": 10, "separator": [9, 18, 19, 22, 27], "serialisation": 8, "serialization": 7, "series_domain": 19, "serv": [0, 1, 3, 8, 9, 10, 13, 17, 18, 19, 22, 24, 27, 28, 31, 32, 33, 34, 35, 36], "server_contain": 25, "server_liv": [9, 17], "server_url": [23, 24], "servic": [7, 17, 25, 32, 36], "session": 25, "set": [0, 6, 9, 10, 14, 16, 17, 18, 19, 20, 23, 24, 25, 26, 28, 31, 33, 34], "set1": 19, "set_budget_field": [9, 20, 23, 25, 31, 35], "set_conf": [9, 17], "set_may_query": [9, 20, 23, 25, 31, 35], "set_may_user_query": [9, 10], "set_mechanism": [13, 14], "setosa": [23, 25], "setting": [9, 16, 18, 19, 23, 24, 28, 35, 36], "setup": [6, 9, 16], "setupclass": [9, 16], "sev": [0, 33], "sex": [9, 18, 19, 22, 23, 25, 27, 28], "sex_count": 19, "sex_region_count": 19, "sh": 0, "sha256": [21, 23, 24], "shap": [18, 19, 22, 27, 28], "shared": 7, "she": [18, 19, 28], "shell": 20, "short": 7, "should": [3, 17, 18, 19, 23, 24, 25, 26, 27, 28, 34], "show": [9, 19, 20, 23, 24, 25, 26, 31, 33], "show_archiv": [25, 31], "show_archives_of_us": [9, 35], "show_collection": [9, 20, 23, 25, 31, 35], "show_dataset": [9, 25, 31, 35], "show_metadata": [25, 31], "show_metadata_of_dataset": [9, 35], "show_us": [9, 20, 23, 25, 31, 35], "showcas": [18, 19, 23, 24, 25, 26, 28], "shown": [9, 31], "shows": [24, 26], "shutdown": 9, "sibsp": [23, 25, 27], "sid": [7, 9], "significanc": 28, "similar": [18, 19, 28], "similarly": 24, "simpl": 0, "simplify": 33, "simply": [20, 23, 24, 26], "sinc": [10, 19, 23], "singl": [18, 19, 28], "singleton": 17, "sit": 23, "six": 23, "siz": [9, 12, 18, 19, 22, 27], "slightly": [18, 19, 27, 28], "small": [14, 18, 19, 27, 28], "smarnois": 17, "smartnois": [3, 8, 9, 14, 17, 23, 25, 28], "smartnoise_query": [5, 8, 18, 19, 27, 28], "smartnoise_sql": [5, 8, 9, 13, 18, 19, 35], "smartnoise_sql_handl": [9, 35], "smartnoisesql": [7, 9, 14, 18, 19, 23, 25, 27, 28], "smartnoisesqlqueri": [13, 14], "smoothed_max_divergenc": [9, 35], "smoothedmaxdivergenc": [8, 9], "sniffio": 23, "sns": 19, "snsqlinp": [9, 14, 17], "snsqlinpcost": [9, 14, 17], "solution": [18, 19, 28], "som": [4, 9, 18, 19, 24, 28, 29], "sort_valu": 19, "sourc": 0, "spac": [25, 26], "spec": [21, 24], "speci": [9, 23, 25, 28], "species_count_pipelin": [18, 19], "species_counts_r": [18, 19], "specific": [3, 7, 9, 10, 13, 18, 19, 25, 28, 33, 36], "specified": [9, 10, 12, 24, 31], "specify": [25, 28], "specifying": 24, "spend": [18, 19, 28], "spending": [10, 28], "spent": [8, 9, 10, 13, 18, 19, 27, 28], "spent_delta": [9, 10, 13, 18, 19], "spent_epsilon": [9, 10, 13, 18, 19], "spent_valu": 10, "sphinx": 0, "split": [7, 25], "sql": [3, 8, 9, 14, 17], "sqrt": [18, 19, 27, 28], "src": 23, "sspcloud": [19, 20, 21, 22, 23, 24, 25], "stabl": 9, "stack": 23, "stall": [9, 35], "standard": [0, 16, 18, 19, 27, 28], "standard_error": [18, 19, 27, 28], "starlett": 23, "start": [8, 9, 18, 19, 24, 28, 31, 34, 36], "started": [16, 23, 24], "starting": [7, 26], "startup": [3, 9], "startups": 26, "stat": [7, 9, 10, 23, 24, 25, 26], "stated": [23, 24], "statement": 0, "static": 0, "statistical": [7, 19], "statistics": 4, "status": [8, 10, 18, 19, 21, 24, 27], "stay": [12, 25], "std": 28, "std_0": 28, "std_1": 28, "std_2": 28, "std_bill_length": 28, "std_bill_length_mm": 28, "stdout": 20, "step": [4, 6, 23, 24, 25, 26, 36], "steps": [0, 8, 25, 32, 33, 36], "stichwortverzeichnis": 7, "still": [18, 19, 23, 28], "stop": 31, "stopping": 26, "stor": [9, 12, 17, 24], "storag": [15, 25, 26], "stored": [7, 18, 19, 20, 23, 25, 27, 28, 31], "storing": 7, "str": [8, 9, 10, 12, 13, 14, 15, 17, 18, 19, 22, 27], "stream": 17, "stream_datafram": [9, 17], "streaming": 17, "streamingrespons": [9, 17], "streamlit": [0, 25, 29, 31], "strenum": [8, 9], "string": [8, 9, 13, 14, 18, 19, 23, 25, 27, 28], "strip": 25, "strongly": 7, "study": 28, "subcommand": [20, 23, 25], "submit_limit": [9, 17, 35], "submodul": [5, 35], "subpackag": 35, "subprocess": 20, "subscrib": 12, "subscribe_for_memory_usage_updat": [9, 15], "subsequent": 26, "substarct": [18, 19, 27, 28], "successfully": [23, 33], "such": [7, 18, 19, 29], "sufficient": [3, 24], "suffix": 19, "suit": [21, 24, 32], "suplied": 9, "supplied": 9, "supported": [10, 15], "sur": [0, 23, 24, 25, 27, 28, 33], "survived": [23, 25, 27], "swagg": 26, "swiss": 19, "switch": 23, "symmetric_distanc": 19, "syntax": 28, "synthetic": [18, 28], "syst": [28, 34], "t": [18, 19, 21, 23, 24, 25, 28, 33], "t_01": 28, "t_02": 28, "t_12": 28, "t_test": 28, "tabl": [8, 9, 10, 19, 23, 25], "tailed": 28, "tak": [7, 15], "target": [21, 23, 24], "task": [7, 29, 31], "tcp": 23, "team": 3, "tear": [20, 23, 24, 25], "teardown": [9, 16], "teardownclass": [9, 16], "temperatur": 19, "terminal": [6, 25, 26, 34], "test": [9, 18, 19, 21, 24, 25, 28, 35], "test_add_dataset_to_us": [9, 16], "test_add_datasets_via_yaml": [9, 16], "test_add_local_dataset": [9, 16], "test_add_us": [9, 16], "test_add_user_wb": [9, 16], "test_add_users_via_yaml": [9, 16], "test_api": [9, 35], "test_boolean_column": [9, 16], "test_budget_over_limit": [9, 16], "test_cardinality_column": [9, 16], "test_conf": 0, "test_datetime_column": [9, 16], "test_del_dataset": [9, 16], "test_del_dataset_to_us": [9, 16], "test_del_us": [9, 16], "test_drop_collection": [9, 16], "test_dummy_generation": [9, 35], "test_dummy_opendp_query": [9, 16], "test_dummy_smartnoise_query": [9, 16], "test_float_column": [9, 16], "test_get_dataset_metadata": [9, 16], "test_get_dummy_dataset": [9, 16], "test_get_initial_budget": [9, 16], "test_get_previous_queri": [9, 16], "test_get_remaining_budget": [9, 16], "test_get_total_spent_budget": [9, 16], "test_int_column": [9, 16], "test_mongodb_admin": [9, 35], "test_nullable_column": [9, 16], "test_opendp_cost": [9, 16], "test_opendp_query": [9, 16], "test_seed": [9, 16], "test_set_budget_field": [9, 16], "test_set_may_query": [9, 16], "test_smartnoise_cost": [9, 16], "test_smartnoise_query": [9, 16], "test_stat": [9, 16], "test_subsequent_budget_limit_logic": [9, 16], "testcas": 16, "tested": [18, 19, 28], "testing": [7, 16, 18, 19, 26], "testmakedummydataset": [9, 16], "testmongodbadmin": [9, 16], "testrootapiendpoint": [9, 16], "text": [8, 20], "than": [18, 19, 27, 28], "thank": [18, 19, 28], "that": [0, 7, 8, 9, 10, 12, 17, 18, 19, 23, 24, 25, 26, 27, 28, 29, 32], "the": [0, 3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 26, 29, 31, 32, 34, 36], "their": [3, 12, 17, 18, 19, 23, 24, 25, 26, 28], "them": [17, 18, 19, 23, 25, 26, 28], "then": [0, 8, 9, 12, 18, 19, 23, 25, 26, 28], "then_cast_default": [9, 18, 19, 22, 27], "then_clamp": [9, 18, 19, 22, 27], "then_col": 19, "then_count": 22, "then_count_by_categori": [18, 19], "then_groupby_stabl": 19, "then_laplac": [9, 18, 19, 22, 27], "then_mean": 22, "then_private_agg": 19, "then_private_quantile_expr": 19, "then_resiz": [9, 18, 19, 22, 27], "then_scan_csv": 19, "then_varianc": [9, 18, 19, 27], "theoratical": 28, "theoretical": 28, "ther": [3, 9, 12, 14, 18, 19, 23, 25, 26, 27, 28], "therefor": [18, 19, 25, 27, 28], "thes": [4, 6, 7, 18, 19, 21, 23, 24, 25, 28, 29, 31, 33, 34], "they": [3, 7, 12, 18, 19, 23, 25, 28], "this": [3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 36], "thos": [0, 8, 36], "though": 26, "three": 19, "through": [23, 25, 26, 28, 33], "throws": 17, "thus": [7, 19, 23, 24], "ticino": 19, "ticket": [23, 25, 27], "tim": [17, 18, 19, 26, 28], "time_attack": [9, 17, 35], "timeattack": [9, 17], "timeattackmethod": [9, 17, 35], "timestamp": [18, 19], "timing": [9, 17], "titanic": [4, 20, 23, 25], "titanic_metadata": [23, 25, 27], "titl": 19, "tls": 24, "to": [0, 3, 4, 6, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 22, 23, 24, 27, 28, 29, 31, 32, 33, 36], "to_dict": [20, 25], "to_pandas": 19, "toa": [9, 18, 19, 22, 27], "todo": [13, 14, 37], "todos": 0, "togeth": 19, "tok": [23, 24], "tolist": [18, 19, 27], "too": [0, 12, 14, 28], "tool": [24, 28, 29, 33], "toolkit": 23, "torgers": [18, 19, 23, 25, 28], "tornado": 23, "total": [8, 9, 10, 12, 18, 19, 23, 25, 27, 28], "total_count": 19, "total_spent_delta": [9, 10, 17, 18, 19, 23, 25, 27, 28], "total_spent_epsilon": [9, 10, 17, 18, 19, 23, 25, 27, 28], "traceback": [19, 23, 25], "traitlet": 23, "tran": [18, 19, 22, 27], "transform": 19, "transformation": [9, 18, 19, 22, 27], "translat": 17, "trial": 14, "tries": [3, 18, 19, 28], "true": [8, 9, 10, 14, 16, 17, 18, 19, 20, 22, 23, 24, 25, 27, 28, 31], "try": [18, 19, 28], "tue": 21, "tuning": 36, "tupl": [13, 14], "two": [0, 7, 18, 19, 28], "txt": 23, "typ": [0, 8, 9, 10, 12, 13, 14, 15, 17, 18, 22, 23, 24, 25, 27, 28, 31], "typically": 7, "typing_extension": 23, "u": [27, 31], "uint32": 19, "un": 7, "unauthorized": 17, "unauthorizedaccessexception": [9, 10, 13, 17], "understand": 4, "understanding": [18, 19, 28], "understood": [18, 19, 28], "unethical": [23, 25], "unexpected": 17, "unforse": [9, 13, 14], "uniform": 19, "uninstall": [20, 21, 23, 24], "uninstalled": 20, "union": [8, 9, 17], "unitt": 0, "unknown": [8, 9, 10, 13, 14, 17, 18, 19], "unless": [23, 25], "until": [12, 23, 25, 28], "up": [0, 16, 23, 24, 25, 26, 33, 34], "updat": [10, 12, 23, 24, 25, 26], "update_budget": [9, 10], "update_delta": [9, 10], "update_epsilon": [9, 10], "update_epsilon_or_delta": [9, 10], "update_memory_usag": [9, 12], "updated": [9, 10, 14, 18, 19, 23, 25, 28], "updating": [7, 10], "upp": [18, 19, 22, 23, 25, 27, 28], "upper_bound": [18, 19, 27, 28], "url": [6, 8, 9, 10, 18, 19, 22, 23, 24, 26, 27, 28, 31], "urllib3": 23, "us": [7, 19, 23, 24, 25], "usag": [6, 9, 12, 15, 20, 23, 25], "use": [0, 4, 6, 8, 9, 12, 16, 18, 19, 23, 24, 25, 26, 28, 29, 31, 33], "used": [8, 9, 12, 17, 18, 19, 26, 27, 28], "useful": [10, 18, 19, 27, 28], "user": [0, 3, 7, 8, 9, 10, 12, 13, 16, 17, 18, 21, 24, 27, 28, 29], "user_collection": [20, 23, 25], "user_dataset": 9, "user_must_exist": [9, 10], "user_must_have_access_to_dataset": [9, 10], "user_nam": [6, 8, 9, 10, 13, 17, 18, 19, 22, 23, 25, 27, 28], "user_pwd": 31, "usercollection": [9, 17], "usernam": [9, 10, 17, 31], "uses": [7, 17, 18, 19, 28], "using": [0, 4, 7, 18, 19, 23, 24, 32, 33], "usr": 17, "usually": 7, "utf": 25, "util": [9, 13, 25, 35], "utilizing": 7, "uvicorn": 17, "uvicorn_serv": [25, 35], "v": 31, "v1": 17, "v2": 23, "valid": [12, 13], "valu": [3, 8, 9, 10, 13, 14, 18, 19, 23, 24, 25, 26, 27, 28, 31, 32], "valueerror": [8, 9, 23, 25], "var_ag": 27, "var_age_transformation_pipelin": 27, "var_bill_length": [18, 19], "var_bill_length_measurement_pipelin": [18, 19], "var_r": [18, 19, 27], "variabl": [16, 18, 19, 28], "varianc": [18, 19, 27], "various": [4, 18, 19, 28, 29, 31, 32], "venv": [0, 23], "verifi": [10, 28], "verify": [0, 10, 18, 19, 23, 28], "versicolor": [23, 25], "version": [9, 23, 26], "verursacht": [8, 9, 10, 12, 13, 14, 15, 17], "very": [18, 19, 28], "via": [7, 16, 18, 19, 27, 28], "virginica": [23, 25], "virtual": 23, "visibl": [18, 19, 28], "visiting": [23, 24], "visualis": [23, 31], "volum": [0, 31, 34], "w0511": 0, "w0918": 24, "w1212": 21, "w82mf1fkjhldoyt4wusjhicde5c": 23, "wait": 25, "want": [0, 18, 19, 23, 25, 27, 28], "wanted": [18, 19], "warning": [8, 21, 24], "wast": [18, 19], "wcwidth": 23, "we": [0, 7, 12, 19, 23, 24, 25, 26, 27, 28, 29, 33], "web": [0, 26, 34], "webpag": 0, "well": [0, 7, 23, 24, 26, 28], "went": 26, "wer": [18, 19, 28], "west": 19, "what": [3, 18, 19, 27, 28], "when": [3, 12, 17, 18, 19, 24], "wher": [18, 19, 23, 25, 28, 31, 34], "wheth": [8, 9, 23, 24], "which": [3, 7, 9, 18, 19, 20, 23, 24, 25, 26, 27, 28], "whil": [3, 16, 23, 24], "whl": 23, "why": [3, 28], "width": [18, 19, 20, 21, 28], "with": [0, 4, 6, 7, 8, 9, 10, 12, 16, 17, 23, 24, 25, 26, 28, 29, 31, 33, 34, 36], "with_count": 19, "within": [3, 17, 28, 31, 36], "without": [3, 18, 19, 23, 24, 25, 28], "woman": 19, "won": [18, 19, 23, 25], "work": [0, 9, 17, 18, 19, 23, 24, 28], "workflow": [0, 28], "working": [18, 19, 22, 23, 24, 28], "worry": [18, 19], "would": [18, 19, 28], "wrapp": [9, 10], "wrapped": 10, "wrapper_decorator": 25, "writ": [18, 19], "writeconcernerror": [9, 10], "writeresult": 10, "writt": 25, "x": [19, 27], "xlabel": 19, "xtick": 19, "y": 19, "yaml": [0, 9, 10, 16, 17, 23, 24, 25, 26, 31, 32, 35], "yaml_databas": [9, 35], "yaml_db_path": 10, "yaml_fil": [9, 31], "yamldatabas": 16, "yamldbconf": [9, 17], "year": 27, "yet": [23, 25, 33], "yf": [25, 31], "yield": 9, "ylabel": 19, "yml": 26, "you": [0, 4, 6, 7, 23, 24, 25, 26, 28, 32, 33, 34, 36], "your": [6, 23, 24, 25, 26, 32, 33, 34, 36], "your_deployement_url": 6, "your_nam": 6, "your_registry": [21, 23, 24], "yourself": 29, "ytick": 19, "zero_concentrated_divergenc": [9, 35], "zeroconcentrateddivergenc": [8, 9], "zscor": [18, 19, 27, 28], "zurich": 19}, "titles": ["Tips for developers", "API Documentation", "Client API", "Errors", "Examples", "lomas_client", "Quickstart", "Welcome to Lomas documentation", "lomas_client package", "lomas_server package", "lomas_server.admin_database package", "lomas_server.administration package", "lomas_server.dataset_store package", "lomas_server.dp_queries package", "lomas_server.dp_queries.dp_libraries package", "lomas_server.private_dataset package", "lomas_server.tests package", "lomas_server.utils package", "Lomas: Client demo", "Lomas: Client demo with polar", "Secure Data Disclosure on Kubernetes: Server Administration", "Demo - Kubernetes Service Deployment", "Minimal OpenDP example on the income dataset", "Secure Data Disclosure on Kubernetes: Deployment and Server Administration", "Kubernetes Service Deployment", "Lomas-server: CLI administration", "Local Service Deployment - How to", "S3 example", "Secure Data Disclosure: Client side", "Administration", "Server API", "CLI", "Deployment", "Kubernetes", "Local", "lomas_server", "Onyxia", "Streamlit"], "titleterms": {"1": [18, 19, 20, 27, 28], "2": [18, 19, 20, 27, 28], "3": [18, 19, 27, 28], "4": [18, 19, 27, 28], "5": [18, 19], "a": [0, 18, 19, 20, 23, 25, 27, 28], "about": 20, "access": [21, 25], "accessibl": 20, "accessing": [20, 23, 33], "actually": 23, "add": [20, 23, 25], "adding": [20, 23], "additional": 0, "admin_databas": 10, "administering": [20, 23], "administrat": 25, "administration": [11, 20, 23, 25, 29, 31], "age": 27, "all": [20, 21, 25], "among": 20, "and": [0, 7, 18, 19, 20, 21, 23, 25, 26, 27], "anti_timing_att": 17, "api": [1, 2, 30], "app": 9, "apply": 19, "archiv": [18, 19, 23, 25], "are": 20, "as": 20, "availabl": 20, "averag": [18, 19, 27], "based": 20, "basic_dataset_stor": 12, "be": [20, 23, 25], "been": 20, "bill": [18, 19], "bound": [19, 20], "boxplot": 19, "budget": [18, 19, 23, 25, 27, 28], "building": [21, 23, 24], "by": [20, 21, 23], "can": [20, 23, 25], "certain": 20, "chang": 25, "changing": 23, "chart": [21, 23, 33], "check": [0, 21], "cleaning": 20, "cli": [25, 31], "client": [2, 6, 7, 8, 18, 19, 21, 24, 27, 28], "clust": 0, "collection": 31, "collections_model": 17, "column": [19, 20], "confidenc": [18, 19, 27, 28], "config": [17, 26], "connection": 31, "constant": [9, 16], "contain": [21, 24], "content": [8, 9, 10, 11, 12, 13, 14, 15, 16, 17], "cost": [18, 19, 27, 28], "could": 20, "count": [18, 19], "creat": 25, "credential": 21, "current": [18, 19, 27, 28], "data": [19, 20, 23, 28], "databas": [20, 23, 25], "dataset": [18, 19, 20, 22, 23, 25, 27, 28, 31], "dataset_stor": 12, "defined": 21, "demo": [18, 19, 20, 21], "dependency": 21, "deploy": 34, "deployed": 20, "deploying": [23, 33, 36], "deployment": [21, 23, 24, 26, 32, 37], "detail": 20, "develop": 0, "dictionary": 20, "direcly": 25, "directly": 23, "disclaim": 19, "disclosur": [20, 23, 28], "distribution": 19, "do": [20, 23, 25], "dock": [21, 25, 26], "documentation": [0, 1, 7], "download": 21, "dp_librari": 14, "dp_logic": 13, "dp_queri": [13, 14], "drop": [20, 23, 25], "dummy": [18, 19, 27, 28], "dummy_dataset": 13, "each": 20, "environment": 21, "error": 3, "error_handl": 17, "estimat": [18, 19, 27, 28], "everything": 23, "exampl": [4, 19, 22, 27, 31], "example_input": 17, "existing": 20, "expected": 20, "exploring": 19, "externallibraryexception": 3, "fashion": 20, "fil": [20, 21, 23, 25], "finally": [23, 25], "first": 6, "flipp": 28, "fom": 25, "for": [0, 18, 19, 20, 21, 27, 28], "format": 20, "from": 23, "fso": 19, "functionnaliti": [18, 19, 27, 28], "get": [18, 19, 21, 27, 28], "getting": [18, 19, 27, 28], "has": 20, "helm": [21, 23, 33], "history": 7, "how": [20, 26], "hypothesis": 28, "imag": [21, 23, 24], "in_memory_dataset": 15, "incom": [19, 22], "indic": 7, "information": 20, "ingress": 33, "initialis": [18, 19, 27, 28], "input_model": 17, "install": [18, 19, 21, 27, 28], "installation": 6, "installing": 33, "internalserverexception": 3, "interval": [18, 19, 27, 28], "introduction": 36, "invalidqueryexception": 3, "is": 20, "it": [20, 23, 25], "its": 20, "kubectl": 21, "kubernet": [0, 20, 21, 23, 24, 33], "length": [18, 19, 28], "let": [20, 23], "library": [18, 19, 27, 28], "linting": 0, "loaded": [23, 25], "local": [0, 26, 34], "locally": 34, "loggr": 17, "login": 21, "lomas": [7, 18, 19, 25, 36], "lomas_client": [5, 8], "lomas_serv": [9, 10, 11, 12, 13, 14, 15, 16, 17, 35], "lru_dataset_stor": 12, "machin": 0, "mak": 20, "medata": 20, "metadata": [18, 19, 20, 27, 28], "minimal": 22, "modifying": 33, "modul": [8, 9, 10, 11, 12, 13, 14, 15, 16, 17], "mongodb": [20, 21, 23, 25, 31], "mongodb_admin": 9, "mongodb_admin_cli": 9, "mongodb_databas": 10, "mor": 20, "nam": 20, "new": 19, "not": [20, 23, 25], "notebook": 20, "now": [20, 23, 25], "numb": [18, 19, 27], "of": [18, 19, 20, 23, 25, 27, 28], "on": [0, 18, 19, 20, 22, 23, 27, 33, 36], "one": 20, "onyxia": [0, 36, 37], "opendp": [14, 18, 19, 22, 27], "option": [20, 25], "oth": [0, 20], "over": [18, 19, 27, 28], "overview": 31, "own": 20, "packag": [8, 9, 10, 11, 12, 13, 14, 15, 16, 17], "pag": 20, "partition": 19, "password": 21, "path": 20, "path_dataset": 15, "penguin": [18, 19, 28], "per": [18, 19], "pipelin": 19, "polar": 19, "population": [18, 19, 27, 28], "prepar": [19, 25], "preparation": 19, "preparing": [20, 23], "prerequisit": [33, 34], "privat": [18, 19, 27], "private_dataset": 15, "private_dataset_observ": 12, "queri": [18, 19, 23, 25], "query": [18, 19, 27, 28], "querying": 21, "quickstart": 6, "real": [18, 19, 27, 28], "remot": [18, 19, 27], "remov": 25, "removing": 23, "required": 20, "result": 19, "right": [20, 23], "rows": [18, 19, 27], "s": [20, 23], "s3": 27, "s3_dataset": 15, "sam": 20, "section": 33, "secur": [20, 23, 28], "see": [18, 19, 20], "serv": [7, 20, 21, 23, 25, 26, 30], "server_url": 21, "servic": [0, 20, 21, 23, 24, 26, 33], "session": [21, 24, 26], "setup": [21, 26], "should": 20, "showcas": 20, "sid": 28, "sinc": 20, "singl": 23, "smartnois": [18, 19, 20, 27], "smartnoise_sql": 14, "smartnoisesql": 20, "som": 20, "speci": [18, 19], "sql": [18, 19, 27], "start": [0, 20, 25, 26], "starting": [21, 24], "stat": 21, "statistics": [18, 19, 27, 28], "step": [18, 19, 20, 27, 28], "steps": [6, 34], "stop": [25, 26], "stopping": [20, 21, 23, 24], "streamlit": 37, "submodul": [8, 9, 10, 12, 13, 14, 15, 16, 17], "subpackag": [9, 13], "switching": 19, "synthetic": 19, "tabl": 7, "temporary": 19, "test": [0, 16, 26], "test_api": 16, "test_dummy_generation": 16, "test_mongodb_admin": 16, "testing": 28, "the": [18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 33], "their": 20, "ther": 20, "this": 20, "through": 21, "tips": 0, "titanic": 27, "to": [7, 20, 21, 25, 26, 34], "typ": [19, 20], "unauthorizedaccessexception": 3, "understand": [18, 19, 27, 28], "updat": 21, "url": [20, 21], "use": 21, "user": [19, 20, 23, 25, 26, 31], "util": [10, 12, 14, 15, 17], "uvicorn_serv": 9, "valu": [20, 21, 33], "version": 19, "visualis": [19, 25], "volum": [25, 26], "we": 20, "welcom": 7, "wher": 20, "whol": [18, 19, 27, 28], "with": [18, 19, 20, 21, 27], "yaml": [20, 21, 33], "yaml_databas": 10, "your": 21}}) \ No newline at end of file diff --git a/html/de/server_administration.html b/html/de/server_administration.html deleted file mode 100644 index 4231fee6..00000000 --- a/html/de/server_administration.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - Administration — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Administration

-

This section explains how the data owner can manage the administrative database. -It covers some tasks such as adding data, making data available to certain users, -managing the budget, etc.

-

To familiarize yourself with this tool, we provide multiple notebooks demonstrating various -CLI commands that users can use. These notebooks can be found in our -GitHub repository.

-

In addition to the notebooks, we also provide a Streamlit application that facilitates these -administrative tasks.

- -
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/server_api.html b/html/de/server_api.html deleted file mode 100644 index 5dc5be4c..00000000 --- a/html/de/server_api.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - Server API — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Server API

- -
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/server_cli.html b/html/de/server_cli.html deleted file mode 100644 index e9e94af1..00000000 --- a/html/de/server_cli.html +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - - CLI — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

CLI

-

This section provides detailed instructions on how to use the CLI to perform various -administrative tasks efficiently.

-
-

Overview

-

The CLI allows data owners to interact with the administrative database directly from the command -line. It provides functionalities for managing users, datasets, metatada and queries_archive collections within the MongoDB environment.

-

NOTE: it is possible to use a streamlit app to interact with the database instead.

-
-
-

MongoDB Connection

-

The CLI requires connection parameters to establish a connection with MongoDB. These parameters must always for provided (for all functions):

-
    -
  • -db_u, --username: MongoDB username (default: „user“)

  • -
  • -db_pwd, --password: MongoDB password (default: „user_pwd“)

  • -
  • -db_a, --address: MongoDB server address (default: „mongodb“)

  • -
  • -db_p, --port: MongoDB server port (default: 27017)

  • -
  • -db_n, --db_name: MongoDB database name (default: „defaultdb“)

  • -
-
-
-

MongoDB Administration

-
-

Users

-
    -
  • add_user: Add a user to the users collection.

    -
      -
    • -u, --user: Username of the user to be added (required).

    • -
    -
  • -
  • add_user_with_budget: Add a user with a budget to the users collection.

    -
      -
    • -u, --user: Username of the user to be added (required).

    • -
    • -d, --dataset: Name of the dataset for the user (required).

    • -
    • -e, --epsilon: Epsilon value for the dataset (required).

    • -
    • -del, --delta: Delta value for the dataset (required).

    • -
    -
  • -
  • del_user: Delete a user from the users collection.

    -
      -
    • -u, --user: Username of the user to be deleted (required).

    • -
    -
  • -
  • add_dataset_to_user: Add a dataset with initialized budget values for a user.

    -
      -
    • -u, --user: Username of the user (required).

    • -
    • -d, --dataset: Name of the dataset to be added (required).

    • -
    • -e, --epsilon: Epsilon value for the dataset (required).

    • -
    • -del, --delta: Delta value for the dataset (required).

    • -
    -
  • -
  • del_dataset_to_user: Delete a dataset for a user from the users collection.

    -
      -
    • -u, --user: Username of the user (required).

    • -
    • -d, --dataset: Name of the dataset to be deleted (required).

    • -
    -
  • -
  • set_budget_field: Set a budget field to a given value for a specified user and dataset.

    -
      -
    • -u, --user: Username of the user (required).

    • -
    • -d, --dataset: Name of the dataset (required).

    • -
    • -f, --field: Field to be set („initial_epsilon“ or „initial_delta“) (required).

    • -
    • -v, --value: Value to set for the field (required).

    • -
    -
  • -
  • set_may_query: Set the „may query“ field to a given value for a specified user.

    -
      -
    • -u, --user: Username of the user (required).

    • -
    • -v, --value: Value to set for „may query“ (choices: „False“ or „True“) (required).

    • -
    -
  • -
  • show_user: Show all information about a user in the users collection.

    -
      -
    • -u, --user: Username of the user to be shown (required).

    • -
    -
  • -
  • add_users_via_yaml: Create users collection from a YAML file.

    -
      -
    • -yf, --yaml_file: Path to the YAML file (required).

    • -
    • -c, --clean: Clean the existing users collection (optional, default: False).

    • -
    • -o, --overwrite: Overwrite the existing users collection (optional, default: False).

    • -
    -
  • -
  • show_archives: Show all previous queries from a user.

    -
      -
    • -u, --user: Username of the user to show archives (required).

    • -
    -
  • -
  • get_users: Get the list of all users in the ‚users‘ collection.

  • -
  • get_user_datasets: Get the list of all datasets from a user.

    -
      -
    • -u, --user: Username of the user to show datasets (required).

    • -
    -
  • -
-
-
-

Datasets

-
    -
  • add_dataset: Add a dataset to the datasets collection.

    -
      -
    • -d, --dataset_name: Name of the dataset (required).

    • -
    • -db, --database_type: Type of the database where the dataset is stored (required).

    • -
    • -d_path, --dataset_path: Path to the dataset (required if database_type is ‚PATH_DB‘).

    • -
    • -s3b, --s3_bucket: S3 bucket name for the dataset file (required if database_type is ‚S3_DB‘).

    • -
    • -s3k, --s3_key: S3 key for the dataset file (required if database_type is ‚S3_DB‘).

    • -
    • -s3_url, --endpoint_url: S3 endpoint URL for the dataset file (required if database_type is ‚S3_DB‘).

    • -
    • -s3_ak, --aws_access_key_id: AWS access key ID for S3 for the dataset file (required if database_type is ‚S3_DB‘).

    • -
    • -s3_sak, --aws_secret_access_key: AWS secret access key for S3 for the dataset file (required if database_type is ‚S3_DB‘).

    • -
    • -m_db, --metadata_database_type: Type of the database where metadata is stored (required).

    • -
    • -mp, --metadata_path: Path to the metadata (required if metadata_database_type is ‚PATH_DB‘).

    • -
    • -m_s3b, --metadata_s3_bucket: S3 bucket name for metadata (required if metadata_database_type is ‚S3_DB‘).

    • -
    • -m_s3k, --metadata_s3_key: S3 key for metadata (required if metadata_database_type is ‚S3_DB‘).

    • -
    • -m_s3_url, --metadata_endpoint_url: S3 endpoint URL for metadata (required if metadata_database_type is ‚S3_DB‘).

    • -
    • -m_s3_ak, --metadata_aws_access_key_id: AWS access key ID for metadata (required if metadata_database_type is ‚S3_DB‘).

    • -
    • -m_s3_sak, --metadata_aws_secret_access_key: AWS secret access key for metadata (required if metadata_database_type is ‚S3_DB‘).

    • -
    -
  • -
  • add_datasets_via_yaml: Create datasets to database type collection based on a yaml file.

    -
      -
    • -yf, --yaml_file: Path to the YAML file (required).

    • -
    • -c, --clean: Clean the existing datasets collection (optional, default: False).

    • -
    • -od, --overwrite_datasets: Overwrite the existing datasets collection (optional, default: False).

    • -
    • -om, --overwrite_metadata: Overwrite the existing metadata collection (optional, default: False).

    • -
    -
  • -
  • del_dataset: Delete dataset and metadata from datasets and metadata collection.

    -
      -
    • -d, --dataset: Name of the dataset to be deleted (required).

    • -
    -
  • -
  • show_dataset: Show a dataset from the dataset collection.

    -
      -
    • -d, --dataset: Name of the dataset to show (required).

    • -
    -
  • -
  • show_metadata: Show metadata from the metadata collection.

    -
      -
    • -d, --dataset: Name of the dataset of the metadata to show (required).

    • -
    -
  • -
  • get_datasets: Get the list of all datasets in the ‚datasets‘ collection.

  • -
-
-
-

Collections

-
    -
  • drop_collection: Delete a collection from the database.

    -
      -
    • -c, --collection: Name of the collection to be deleted. Choices: „users“, „datasets“, „metadata“, „queries_archives“ (required).

    • -
    -
  • -
  • show_collection: Print a collection.

    -
      -
    • -c, --collection: Name of the collection to be shown. Choices: „users“, „datasets“, „metadata“, „queries_archives“ (required).

    • -
    -
  • -
-
-
-
-

Examples

-
# Add a user
-python mongodb_admin_cli.py add_user -u username
-
-# Add a user with budget
-python mongodb_admin_cli.py add_user_with_budget -u username -d dataset_name -e 0.5 -del 0.1
-
-# Delete a user
-python mongodb_admin_cli.py del_user -u username
-
-# Add a dataset to a user
-python mongodb_admin_cli.py add_dataset_to_user -u username -d dataset_name -e 0.5 -del 0.1
-
-# Delete a dataset from a user
-python mongodb_admin_cli.py del_dataset_to_user -u username -d dataset_name
-
-# Set budget field for a user and dataset
-python mongodb_admin_cli.py set_budget_field -u username -d dataset_name -f initial_epsilon -v 0.5
-
-# Set may query field for a user
-python mongodb_admin_cli.py set_may_query -u username -v True
-
-# Show user metadata
-python mongodb_admin_cli.py show_user -u username
-
-# Create users collection from a YAML file
-python mongodb_admin_cli.py add_users_via_yaml -yf users.yaml -c
-
-# Show all previous queries from user "username"
-python mongodb_admin_cli.py show_archives -u username
-
-# Get the list of all users
-python mongodb_admin_cli.py get_users
-
-# Get the list of all datasets from user "username"
-python mongodb_admin_cli.py get_user_datasets -u username
-
-# Add a dataset
-python mongodb_admin_cli.py add_dataset -d dataset_name -db database_type -d_path dataset_path -m_db metadata_database_type
-
-# Create datasets from a YAML file
-python mongodb_admin_cli.py add_datasets_via_yaml -yf datasets.yaml -c -od -om
-
-# Delete a dataset
-python mongodb_admin_cli.py del_dataset -d dataset_name
-
-# Show dataset "dataset_name"
-python mongodb_admin_cli.py show_dataset -d dataset_name
-
-# Show metadata for dataset "dataset_name"
-python mongodb_admin_cli.py show_metadata -d dataset_name
-
-# Drop a collection
-python mongodb_admin_cli.py drop_collection -c users
-
-# Show a collection
-python mongodb_admin_cli.py show_collection -c datasets
-
-
- -
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/server_deployment.html b/html/de/server_deployment.html deleted file mode 100644 index 3857cf5d..00000000 --- a/html/de/server_deployment.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - Deployment — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Deployment

-

This documentation provides guidance on deploying the Lomas server using various methods. -You can choose the deployment option that best suits your needs:

-
    -
  • -
    Local Deployment:

    Learn how to deploy the server locally on your machine using Docker.

    -
    -
    -
  • -
  • -
    Kubernetes Deployment:

    Explore instructions for deploying the server on a Kubernetes cluster using Helm.

    -
    -
    -
  • -
  • -
    Onyxia Deployment:

    Discover how to deploy the server on Onyxia.

    -
    -
    -
  • -
-

Choose the deployment method that fits your infrastructure and deployment requirements.

- -
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/server_kubernetes.html b/html/de/server_kubernetes.html deleted file mode 100644 index aa4a1767..00000000 --- a/html/de/server_kubernetes.html +++ /dev/null @@ -1,245 +0,0 @@ - - - - - - - Kubernetes — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Kubernetes

-

In this chapter, we will guide you through deploying the service on Kubernetes. -We provide a Helm chart to simplify this process.

-
-

Prerequisites

-

Before you begin, make sure you have the following:

-
    -
  1. -
    Kubernetes Cluster: A running Kubernetes cluster.

    If you don’t have one, you can set up a local cluster using Minikube -or Kind, or use a cloud provider like GKE, EKS, or AKS.

    -
    -
    -
  2. -
  3. -
    Helm: Helm installed on your local machine.

    Follow the official Helm installation guide -if you haven’t installed Helm yet.

    -
    -
    -
  4. -
  5. -
    kubectl: Kubernetes command-line tool kubectl

    installed and configured to communicate with your cluster. -You can install kubectl by following the -official Kubernetes installation guide.

    -
    -
    -
  6. -
-
-
-

Deploying the Service on Kubernetes

-

To deploy the service on Kubernetes, follow the instructions below.

-
-
-

Accessing the Helm Chart

-

The Helm chart for deploying the service on Kubernetes is available here:

-
-
-

Modifying values.yaml

-

Before installing the Helm chart, you need to adapt the values.yaml file to -fit your specific requirements, especially the ingress configuration.

-
-

Modifying the ingress Section

-

To change the ingress configuration, follow these steps:

-
    -
  1. Get the default values

  2. -
-
helm show values lomas/lomas-server > values.yaml
-
-
-
    -
  1. Edit values.yaml file

  2. -
  3. Save the Changes

  4. -
-
-
-
-

Installing the Helm Chart

-

Once you have modified the values.yaml file, you can proceed -to install the Helm chart with your custom configurations:

-
    -
  1. Install the Helm Chart

    -

    Navigate to the directory containing the modified values.yaml -file and run the following command:

    -
    helm install lomas-sever lomas/lomas-server -f values.yaml
    -
    -
    -
  2. -
-

By following these steps, you will have successfully configured and deployed the service -on Kubernetes using the provided Helm chart.

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/server_local.html b/html/de/server_local.html deleted file mode 100644 index 1376793d..00000000 --- a/html/de/server_local.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - - Local — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Local

-

This chapter provides instructions on how to deploy the server locally. -Follow these steps to get your server up and running on your local machine.

-
-

Prerequisites

-
    -
  • Ensure you have Docker and Docker Compose installed on your system.

  • -
-
-
-

Steps to Deploy Locally

-
    -
  1. Clone the Repository

    -

    First, you need to clone the repository. Open your terminal and run:

    -
    git clone https://github.com/dscc-admin-ch/lomas.git
    -
    -
    -
  2. -
  3. Navigate to the Server Directory

    -

    Move into the server directory where the Dockerfile is located:

    -
    cd server
    -
    -
    -
  4. -
  5. Create a Docker Volume for MongoDB

    -

    You need to create a Docker volume for MongoDB data persistence. -Run the following command:

    -
    docker volume create mongodata
    -
    -
    -
  6. -
  7. Start the Server

    -

    With Docker and Docker Compose set up, you can now start the server. -In the same directory, run:

    -
    docker compose up
    -
    -
    -
  8. -
  9. Access the Server

    -

    Once the server is up and running, it should be accessible on localhost. Open your web browser and go to:

    -
    http://localhost
    -
    -
    -
  10. -
-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/server_modules.html b/html/de/server_modules.html deleted file mode 100644 index 3e6ba76b..00000000 --- a/html/de/server_modules.html +++ /dev/null @@ -1,346 +0,0 @@ - - - - - - - lomas_server — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

lomas_server

-
- -
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/server_onyxia.html b/html/de/server_onyxia.html deleted file mode 100644 index 7fecf0ea..00000000 --- a/html/de/server_onyxia.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - Onyxia — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Onyxia

-
-

Introduction

-

Onyxia is a datalab developped by INSEE (France) allowing to -easily deploy services on demand.

-
-
-

Deploying Lomas on Onyxia

-

To start the lomas-server on Onyxia, folow those steps:

-
    -
  1. -
    Select the LOMAS Service:

    Within the Onyxia platform, locate the Lomas-server service. -You can find the service following this -link.

    -
    -
    -
  2. -
  3. -
    Customize Parameters (Optional):

    Depending on your specific requirements, you may choose to customize the -administration and runtime parameters. This step allows for fine-tuning the -deployment according to your project’s needs.

    -
    -
    -
  4. -
  5. -
    Initiate Deployment:

    Once satisfied with the parameter settings, click on the „Lancer“ button to -initiate the deployment process.

    -
    -
    -
  6. -
-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file diff --git a/html/de/server_streamlit.html b/html/de/server_streamlit.html deleted file mode 100644 index 87256e90..00000000 --- a/html/de/server_streamlit.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - Streamlit — Lomas 0.0.1 Dokumentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Streamlit

-
-

Deployment

-

TODO

-
-
-

Onyxia

-

TODO

-
-
- - -
-
- -
-
-
-
-
- - Version: latest - - -
- -
-
Languages
- -
en
- -
- - -
-
Versionen
- -
master
- -
develop
- -
- -
- -
-
- - - \ No newline at end of file