Charger et exécuter des fonctions personnalisées dans une salle blanche

Vue d’ensemble

Vous pouvez charger des UDFs et des UDTFs Python personnalisées dans votre salle blanche et les exécuter à partir de vos modèles pour effectuer des actions de données complexes. Ces actions comprennent le machine learning ou la manipulation personnalisée des données dans une requête, dans le cadre d’une seule étape ou d’un flux à plusieurs étapes. Python est le seul langage de codage pris en charge pour les UDFs personnalisées.

Votre code importé peut importer et utiliser des paquets à partir d’un bundle de packages Python approuvés et l’APISnowpark.

Templates in a clean room can call code uploaded by the account that added the template. Uploaded code can’t be viewed or downloaded. Snowflake scans uploaded code for security issues before installing the code.

There are different mechanisms for uploading code into a clean room, depending on your role:

Providers

  • Inline code upload: If you want to upload code using the default compute resources for a clean room, and need to use only the standard bundle of Python packages (including the Snowpark API), you should upload inline code.

  • Snowpark Container Services running within a clean room: If you need more control over the environment, such as specifying additional compute or custom libraries, you can run a container within a clean room.

Consumers

  • Inline upload with template: Consumers can upload and run a template bundled with code. The code is bound to the template, and must be approved by the clean room provider.

Cette rubrique montre comment charger et exécuter des UDFs et UDTFs Python personnalisés en tant que fournisseur ou consommateur.

Astuce

Pour obtenir des informations générales sur la manière de développer vos propres UDFs Python dans une salle blanche, consultez les rubriques suivantes :

Entry points for uploaded code

Chaque bundle de code importé peut définir plusieurs fonctions qui s’appellent entre elles, mais un bundle n’expose qu’une seule fonction de gestionnaire. Cette fonction de gestionnaire peut être appelée par des modèles créés ou exécutés par quiconque utilise la salle blanche. Si le code crée des tables internes, ces tables sont accessibles comme décrit dans la section Conception de flux à plusieurs étapes.

Par exemple, si vous avez chargé une fonction nommée simple_add qui intègre deux paramètres numériques, vous pouvez l’appeler à partir d’un modèle comme illustré ici. La fonction est toujours référencée à l’aide de la cleanroom à champ d’application limité. Par exemple, un modèle pourrait appeler simple_add comme suit :

SELECT cleanroom.simple_add({{ price | sqlsafe | int }}, {{ tax | sqlsafe | int }}) ...
Copy

Astuce

Si le fournisseur veut exécuter le code ci-dessus, il doit donner un alias à toutes les colonnes SELECT qui utilisent une fonction agrégée ou personnalisée, car une table de résultats est générée en arrière-plan :

SELECT
  cleanroom.simple_add(
    {{ price | sqlsafe | int }}, {{ tax | sqlsafe | int }}
    ) AS TOTAL_ITEM_COST
...
Copy

You can upload multiple functions in a single package, and functions within a single package can call each other, but functions can’t call functions within other packages. (They can call the handler functions, though.) For example, if you have a clean room where you upload two packages, each with a handler function and two helper functions:

Salle blanche avec deux paquets Python chargés

Paquet 1

Paquet 2

  • Fonction de gestionnaire A

  • Fonction d’assistance A1

  • Fonction d’assistance A2

  • Fonction de gestionnaire B

  • Fonction d’assistance B1

  • Fonction d’assistance B2

  • Code uploaded by either party (provider or consumer) can be run templates submitted by either party.

  • A template can call function A or function B, but not A1, A2, B1, or B2.

  • La fonction A peut appeler la fonction B, et inversement.

  • La fonction A ne peut pas appeler B1 ou B2 et la fonction B ne peut pas appeler A1 ou A2.

  • A1 peut appeler A2 et inversement. A1 et A2 peuvent appeler B. A1 et A2 ne peuvent pas appeler B1 ou B2.

  • B1 peut appeler B2 et inversement. B1 et B2 peuvent appeler A. B1 et B2 ne peuvent pas appeler A1 ou A2.

Mise à jour ou suppression des fonctions personnalisées

Vous pouvez charger ou remplacer une fonction ou un modèle existant que vous avez chargé, mais vous ne pouvez pas supprimer une fonction ou un modèle existant. La seule façon de « supprimer » une fonction est de créer une fonction fictive avec exactement le même nom et la même signature qui réussit toujours.

Uploading a function with the same signature as one that you previously uploaded will overwrite the existing function, where a signature means the case-insensitive function name of an external handler, plus the data types of all its arguments, in the same order. Argument names are not part of the signature. You can’t overwrite a function uploaded by another account.

Comme la signature doit correspondre lorsque vous mettez à jour une fonction, vous ne pouvez pas modifier la signature d’une fonction existante : si vous chargez la fonction foo(name VARIANT age INTEGER), puis chargez la fonction foo(name VARIANT age FLOAT), la deuxième fonction sera ajoutée à la salle blanche en plus de la première, car les types d’arguments diffèrent.

Code soumis par le fournisseur

Les fonctions soumises par le fournisseur peuvent être chargées sous forme de code en ligne ou à partir d’une zone de préparation Snowflake. Les deux techniques sont couvertes ici.

Votre code chargé peut importer et utiliser nativement des packages à partir d’un ensemble approuvé de packages Python. Si vous avez besoin d’un package non proposé par défaut, vous devez utiliser Snowpark Container Services dans une salle blanche pour héberger votre code.

Vous ne pouvez pas voir le code du fournisseur chargé, ni même votre propre code, alors veillez à inclure une copie de ce que vous chargez exactement dans une salle blanche.

Vue d’ensemble

Voici une vue de haut niveau de la manière dont un fournisseur ajoute du code à une salle blanche :

  1. Le fournisseur crée et configure la salle blanche de manière habituelle.

  2. The provider uploads a bundle by calling provider.load_python_into_cleanroom. You can either upload your code inline directly within that procedure, or upload a code file to a stage, then provide the stage location to that procedure.

    Although each bundle can include multiple functions, only one handler function is exposed for each upload. To expose multiple functions to templates, upload each handler separately or do a bulk upload (described below).

  3. If the clean room is exposed externally, security checks are run before the code is installed in the clean room, and you must call provider.view_cleanroom_scan_status to confirm that security checks have passed before incrementing the default version.

  4. After each successful upload, a new patch version of the clean room is generated. You must then increase the default version by calling provider.set_default_release_directive with the new patch number.

  5. Create and upload a custom template that calls handlers in your code. The template must call the handler function using the cleanroom scope, that is: cleanroom.my_function(...).

  6. Le consommateur exécute votre modèle de la même manière que tout autre modèle.

    Astuce

    Si le consommateur rencontre une erreur de montage lorsqu’il installe une salle blanche avec du code personnalisé, cela peut indiquer une erreur de syntaxe dans le code.

Vous trouverez des exemples de code démontrant ce flux dans la section d’exemple de code écrit par le fournisseur.

Notes importantes sur la gestion des versions

Every time the provider uploads a function, it increases the clean room patch number (and there is a limit of 99 patch numbers). Therefore, do your best to test and debug your code thoroughly before adding it to the clean room to reduce version updates during development.

You can upload multiple packages at once in a single bulk upload to reduce the number of patches generated. However, bulk uploads can make it more challenging to debug if the upload has a security scan issue, because the file that caused the problem isn’t reported in the error response.

Si vous mettez à jour un numéro de correctif, les clients qui utilisent l’UI des salles blanches peuvent devoir actualiser la page pour voir le changement. Les clients qui utilisent l’API devrait voir les changements immédiatement, mais cela peut prendre un certain temps en fonction des ressources disponibles. En savoir plus sur la gestion des versions des salles blanches.

Charger des fonctions en ligne écrites par le fournisseur

Vous pouvez charger le code en ligne dans le paramètre code de provider.load_python_into_cleanroom. Voici un exemple de chargement d’une fonction simple en ligne :

CALL samooha_by_snowflake_local_db.provider.load_python_into_cleanroom(
$cleanroom_name,
'simple_add',                         -- Name used to call the UDF from a template.
['first INTEGER', 'second INTEGER'],  -- Arguments of the UDF, specified as '<variable_name> <SQL type>' pairs.
['numpy', 'pandas'],                  -- Packages imported by the UDF.
'INTEGER',                            -- SQL return type of UDF.
'add_two',                            -- Handler function in your code called when external name is called.
$$
import numpy as np   # Not used, but you can load supported packages.
import pandas as pd

def add_two(first, second):
    return first + second
$$
);
Copy

Le modèle d’appel appelle cleanroom.simple_add pour appeler cette fonction. Les exemples de fonctions écrites par le fournisseur montrent comment charger du code en ligne.

Chargement de fonctions écrites par un fournisseur à partir d’une zone de préparation

You can upload Python files to a clean room stage and reference the stage when you call provider.load_python_into_cleanroom. Loading code from a stage allows you to develop the code in your local system in an editor, avoid copy/paste errors when loading it inline, and also have better versioning control of your source code. Note that you can upload multiple files in a single procedure call, but only one handler function is exposed for each upload.

Le code est chargé depuis une zone de préparation dans la salle blanche lorsque vous appelez load_python_into_cleanroom. Les modifications ultérieures apportées au code sur la zone de préparation ne sont pas répercutées dans la salle blanche.

Pour charger votre UDF dans une zone de préparation :

  1. Créez votre fichier .py et rendez-le disponible dans un emplacement où vous pouvez le charger dans une zone de préparation Snowsight.

  2. To get the name of the stage for your clean room, call provider.get_stage_for_python_files. You must use the specified stage; you cannot use an arbitrary stage that you create.

  3. Chargez le fichier .py dans la zone de préparation de votre salle blanche. Il y a plusieurs façons de procéder, notamment en utilisant la CLI, Snowsight ou des pilotes spécifiques à la langue.

  4. Appelez provider.load_python_into_cleanroom avec l’emplacement de la zone de préparation, le gestionnaire, le nom externe, les arguments et le type de retour. Les modèles de votre salle blanche peuvent désormais appeler la fonction.

L’exemple de code suivant montre comment charger du code dans une salle blanche à partir d’une zone de préparation.

-- Save the following code as reverser.py:
--import numpy as np
--def main(some_string):
--  '''Return the reverse of a string plus a random number 1-10'''
--  return some_string[::-1] + str(np.random.randint(1,10))

-- Get the stage for your clean room.
CALL samooha_by_snowflake_local_db.provider.get_stage_for_python_files($cleanroom_name);

-- Save the file to the stage. Here is how to do it by using the Snowflake CLI
PUT file://~/reverser.py <STAGE_NAME> overwrite=True auto_compress=False;

-- Load the code from the stage into the clean room.
CALL samooha_by_snowflake_local_db.provider.load_python_into_cleanroom(
    $cleanroom_name,
    'reverse', -- Name used to call the function
    ['some_string  STRING'], -- Arguments and SQL types
    ['numpy'],               -- Any required packages
    ['/reverser.py'],        -- Relative path to file on stage
    'STRING',                -- Return type
    'reverser.main'          -- <FILE_NAME>.<FUNCTION_NAME>
);

-- Uploading code, even from a stage, increases the patch number.
CALL samooha_by_snowflake_local_db.provider.set_default_release_directive(
  $cleanroom_name, 'V1_0', <NEW_PATCH_NUMBER>);

-- Upload a template that calls the function.
CALL samooha_by_snowflake_local_db.provider.add_custom_sql_template(
    $cleanroom_name,
    $udf_template_name,
    $$
    SELECT
      p.status,
      cleanroom.reverse(p.status)
    FROM SAMOOHA_SAMPLE_DATABASE.DEMO.CUSTOMERS AS p
    LIMIT 100;
    $$
);

-- Switch to the consumer account and run the template to see the results.
Copy

Les exemples de code écrit par le fournisseur illustrent le chargement de code à partir d’une zone de préparation.

Dépannage des erreurs de syntaxe ou des échecs d’analyse dans le code chargé

Si vous chargez une fonction qui échoue en raison d’une erreur de syntaxe, ou si une analyse de sécurité échoue, un correctif non publiable peut être généré. Par conséquent, vous devez tester soigneusement votre code avant de le charger pour vous assurer qu’il ne contient pas d’erreurs de syntaxe.

Vous pouvez voir la liste des paquets et leur état de révision en exécutant la commande SQL suivante, et en fournissant l’ID de la salle blanche à l’endroit indiqué :

SHOW VERSIONS IN APPLICATION PACKAGE samooha_cleanroom_cleanroom_id;

Analyses de sécurité

Une analyse de sécurité est exécutée après toute action qui génère une nouvelle version de correctif dans une salle blanche externe, par exemple lorsque le fournisseur télécharge Python dans la salle blanche. (Le code soumis par le consommateur, décrit sur cette page, ne déclenche pas d’analyse de sécurité). Les salles blanches internes n’effectuent pas d’analyses de sécurité, mais si vous transformez une salle blanche interne en salle blanche externe, une analyse de sécurité sera déclenchée pour ce correctif. Un correctif de salle blanche ne peut pas être publié en externe tant que le correctif n’a pas été analysé.

Snowflake Data Clean Rooms utilise le Framework d’analyse de sécurité des applications natives Snowflake. Suivez les meilleures pratiques de sécurité des applications natives pour éviter les erreurs d’analyse de sécurité.

Vous pouvez effectuer d’autres actions de création de correctifs avant que la dernière analyse de sécurité ne soit terminée. Cependant, vous devez attendre que provider.view_cleanroom_scan_status ait réussi avant de pouvoir mettre à jour la directive de version par défaut afin d’utiliser la dernière version de la salle blanche.

Uploading multiple Python functions in a single patch (bulk uploading)

If you want to upload multiple Python packages to your clean room, you can call prepare_python_for_cleanroom multiple times, then call load_prepared_python_into_cleanroom once to scan, upload, and generate a single patch for your clean room. The following example demonstrates uploading a UDF and a UDTF using bulk uploading:

---- Add custom inline UDF ----
CALL samooha_by_snowflake_local_db.provider.prepare_python_for_cleanroom(
    $cleanroom_name,
    'get_next_status',  -- Name of the UDF. Can be different from the handler.
    ['status VARCHAR'], -- Arguments of the UDF, specified as (variable name, SQL type).
    ['numpy'],          -- Packages needed by UDF.
    [],                 -- When providing the code inline, this is an empty array.
    'VARCHAR',          -- Return type of UDF.
    'get_next_status',  -- Handler.
    $$
import numpy as np
def get_next_status(status):
  """Return the next higher status, or a random status
  if no matching status found or at the top of the list."""

  statuses = ['MEMBER', 'SILVER', 'GOLD', 'PLATINUM', 'DIAMOND']
  try:
    return statuses[statuses.index(status.upper()) + 1]
  except:
    return 'NO MATCH'
    $$
);

---- Add custom inline UDTF. ----
CALL samooha_by_snowflake_local_db.provider.prepare_python_for_cleanroom(
    $cleanroom_name,
    'get_info',  -- Name of the UDTF. Can be different from the handler.
    ['hashed_email VARCHAR', 'days_active INT', 'status VARCHAR', 'income VARCHAR'],   -- Name/Type arguments of the UDTF.
    ['numpy'],         -- Packages used by UDTF.
    [],                -- When providing the code inline, this is an empty array.
    'TABLE(hashed_email VARCHAR, months_active INT, level VARCHAR)',  -- Return type of UDTF.
    'GetSomeVals',     -- Handler class name.
$$
class GetSomeVals:
  def __init__(self):
    self.month_days = 30

  def process(self, hashed_email, days_active, status, income):
    '''Change days into rough months, and also return whether we
    think the user's membership status is lower, higher, or equal to
    what is expected, based on their income.'''

    months_active = days_active // self.month_days
    brackets = ['0-50K', '50K-100K', '100K-250K', '250K+']
    statuses = ['MEMBER', 'SILVER', 'GOLD', 'PLATINUM']
    if(statuses.index(status) < brackets.index(income)):
      level = 'low'
    elif(statuses.index(status) > brackets.index(income)):
      level = 'high'
    else:
      level = 'equal'

    yield(hashed_email, months_active, level)
$$
);

-- Upload all stored procedures.
-- Note the new patch number returned by this procedure. Keep this number for later use.
CALL samooha_by_snowflake_local_db.provider.load_prepared_python_into_cleanroom($cleanroom_name);

-- Set the release directive specified by the last load_python_into_cleanroom call.
CALL samooha_by_snowflake_local_db.provider.set_default_release_directive($cleanroom_name, 'V1_0', <PATCH_NUMBER>);
Copy

Exemples de code écrits par le fournisseur

Les exemples suivants illustrent l’ajout de UDFs et de UDTFs écrites par le fournisseur dans une salle blanche.

Download the following examples and then upload them as worksheet files in your Snowflake account. You need separate accounts for the provider and consumer, each with the clean rooms API installed. Replace the information as noted in the sample files. See instructions to upload a SQL worksheet into your Snowflake account.

  • :download :Exemple de code fournisseur</samples/clean-rooms/provider-udf-p.sql>

  • :download :Exemple de code consommateur</samples/clean-rooms/provider-udf-c.sql>

  • :download :Chargement d’un fichier à partir d’une zone de préparation </samples/clean-rooms/udf_from_stage.ipynb>. Exécutez ce notebook après avoir exécuté l’exemple du fournisseur pour essayer de charger une UDF à partir d’une zone de préparation.

  • :download :Chargement de plusieurs fonctions Python dans un seul correctif. </samples/clean-rooms/upload-multiple-python-packages.sql> Il s’agit d’une salle blanche de test interne à compte unique ; vous pouvez utiliser le même compte pour le rôle de fournisseur et le rôle de consommateur.

Code soumis par le consommateur

Le code chargé par le consommateur est regroupé et importé avec un modèle personnalisé à l’aide du flux de chargement du modèle du consommateur. Le code chargé peut être appelé par n’importe quel modèle dans la salle blanche.

Pour charger du code en tant que consommateur, vous devez comprendre la syntaxe des modèles personnalisés.

Notez que tout code chargé par un consommateur peut être vu par le fournisseur lorsqu’il demande l’autorisation de l’importer. Le code du consommateur est également visible chaque fois qu’un fournisseur ou un consommateur examine le modèle.

Voici un aperçu des étapes à suivre pour charger un code consommateur personnalisé :

  1. Le fournisseur crée la salle blanche de la manière standard, puis invite le consommateur.

  2. Le consommateur installe et configure la salle blanche de la manière standard.

  3. Le consommateur prépare un modèle qui appelle l’UDF ou l’UDTF dans l’espace de noms de la cleanroom. Par exemple, pour appeler la fonction calculate_tax définie par le consommateur, un modèle simple peut ressembler à l’extrait suivant :

    SELECT {{ cleanroom.calculate_tax(p.cost) }} AS Tax FROM my_db.my_sch.sales AS p;
    
    Copy
  4. Le consommateur prépare son code Python. Nous vous recommandons d’utiliser des guillemets doubles (" ") plutôt que des guillemets simples (' ') dans votre code afin d’éviter tout échappement supplémentaire nécessaire ultérieurement. Votre code peut faire référence à ces bibliothèques Python prises en charge.

  5. Le consommateur transmet son code Python à consumer.generate_python_request_template. La procédure renvoie le code Python sous forme de procédure stockée, avec un espace réservé pour le modèle JinjaSQL personnalisé. Il existe plusieurs chaînes de plusieurs lignes dans le modèle qui utilisent $$ en tant que délimiteurs à plusieurs lignes.

  6. Le consommateur remplace l’espace réservé du modèle dans la sortie de generate_python_request_template par son modèle JinjaSQL.

  7. Dans le modèle combiné, échappez les guillemets simples comme suit : \'. En effet, les guillemets simples seront utilisés comme délimiteurs externes pour toute la chaîne de procédure multiligne quand vous le chargez dans la salle blanche. Voici un exemple de procédure stockée qui inclut le code Python du consommateur et le modèle personnalisé, avec l’échappement des caractères :

      BEGIN
    
      CREATE OR REPLACE FUNCTION CLEANROOM.custom_compare(min_status STRING, max_status STRING, this_status STRING)
      RETURNS boolean
      LANGUAGE PYTHON
      RUNTIME_VERSION = 3.10
      PACKAGES = (\'numpy\')
    
      HANDLER = \'custom_compare\'
      AS $$
      import numpy as np
    
      def custom_compare(min_status:str, max_status:str, this_status:str):
        statuses = [\'MEMBER\', \'SILVER\', \'GOLD\', \'PLATINUM\']
        return ((statuses.index(this_status) >= statuses.index(min_status)) &
                (statuses.index(this_status) <= statuses.index(max_status)))
      $$;
    
      -- Custom template
      LET SQL_TEXT varchar := $$
      SELECT
        c.status,
        c.hashed_email
      FROM IDENTIFIER( {{ my_table[0] }} ) as c
      WHERE cleanroom.custom_compare({{ min_status }}, {{ max_status }}, c.status);
      $$;
    
      LET RES resultset := (EXECUTE IMMEDIATE :SQL_TEXT);
      RETURN TABLE(RES);
    
      END;
    
    Copy
  8. Le consommateur appelle consumer.create_template_request avec le modèle combiné. Utilisez des guillemets simples (' ') au lieu de délimiteurs à double signe de dollar ($$...$$) autour du code que vous fournissez pour la procédure stockée dans l’argument template_definition. Par exemple :

    CALL samooha_by_snowflake_local_db.consumer.create_template_request(
      $cleanroom_name,
      $template_name,
      '
    BEGIN
    
    -- First, define the Python UDF.
    CREATE OR REPLACE FUNCTION CLEANROOM.custom_compare(min_status STRING, max_status STRING, this_status STRING)
    RETURNS boolean
    LANGUAGE PYTHON
    RUNTIME_VERSION = 3.10
    PACKAGES = (\'numpy\')
    
    HANDLER = \'custom_compare\'
    AS $$
    import numpy as np
    
    def custom_compare(min_status:str, max_status:str, this_status:str):
      statuses = [\'MEMBER\', \'SILVER\', \'GOLD\', \'PLATINUM\']
      return ((statuses.index(this_status) >= statuses.index(min_status)) &
              (statuses.index(this_status) <= statuses.index(max_status)))
        $$;
    
    -- Then define and execute the SQL query.
    LET SQL_TEXT varchar := $$
    SELECT
      c.status,
      c.hashed_email
    FROM IDENTIFIER( {{ my_table[0] }} ) as c
    WHERE cleanroom.custom_compare({{ min_status }}, {{ max_status }}, c.status);
    $$;
    
    -- Execute the query and then return the result.
    LET RES resultset := (EXECUTE IMMEDIATE :SQL_TEXT);
    RETURN TABLE(RES);
    
    END;
    ');
    
    Copy
  9. Le consommateur et le fournisseur poursuivent avec le flux de modèles défini par le consommateur :

    1. Le fournisseur voit la requête de modèle (provider.list_pending_template_requests) et l’approuve ensuite en appelant approve_template_request. Dans la requête, le fournisseur peut voir le modèle et le code en bundle.

    2. Le consommateur vérifie le statut de la requête (consumer.list_template_requests), et lorsque le statut est APPROVED, il exécute le modèle (consumer.run_analysis).

    Consumer code uploads don’t trigger a security scan or affect the clean room patch number.

Exemples de code écrit par les consommateurs

Les exemples suivants illustrent l’ajout d’UDFs écrites par le fournisseur dans une salle blanche.

Download the following examples and then upload them as worksheet files in your Snowflake account. You need separate accounts for the provider and consumer, each with the clean rooms API installed. Replace the information as noted in the sample files. See instructions to upload a SQL worksheet into your Snowflake account.