Création d’UDFs Python¶
Cette rubrique montre comment créer et installer une UDF Python (fonction définie par l’utilisateur).
Dans ce chapitre :
Écrire le code Python¶
Écriture le module et la fonction Python¶
Écrivez un module qui suit les spécifications ci-dessous :
Définissez le module. Un module est un fichier contenant des définitions et des instructions Python.
Définissez une fonction à l’intérieur du module.
Si la méthode accepte des arguments, chaque argument doit être l’un des types de données spécifiés dans la colonne
Python Data Type
de la table SQL-Python Type Mappings.Les arguments de fonction sont liés par leur position, et non par leur nom. Le premier argument transmis à l’UDF est le premier argument reçu par la méthode Python.
Spécifiez une valeur de retour appropriée. Comme une UDF Python doit être une fonction scalaire, elle doit renvoyer une valeur à chaque fois qu’elle est appelée. Le type de la valeur de retour doit être l’un des types de données spécifiés dans la colonne
Python Data Type
de la table SQL-Python Type Mappings. Le type de la valeur de retour doit être compatible avec le type de données SQL spécifié dans la clauseRETURNS
de l’instruction CREATE FUNCTION.Votre module peut contenir plus d’une fonction. La fonction qui est appelée par Snowflake peut appeler d’autres fonctions dans le même module ou dans d’autres modules.
Votre fonction (et toute fonction appelée par votre fonction) doit respecter les contraintes Snowflake imposées pour les UDFs Python.
Note
Il existe une API par lots UDF Python, qui permet de définir des fonctions Python recevant des lots de lignes d’entrée sous forme de DataFrames Pandas et renvoyant des lots de résultats sous forme de tableaux Pandas ou de Series. Pour plus d’informations, voir API par lots UDF Python.
Lecture et écriture de fichiers avec un gestionnaire d’UDF¶
Vous pouvez lire et écrire des fichiers avec le code du gestionnaire d’UDF. Pour le faire en toute sécurité au sein du moteur restreint dans lequel Snowflake exécute des UDFs, suivez les directives décrites ici.
Lecture de fichiers avec un gestionnaire d’UDF¶
Un gestionnaire d’UDF peut lire des fichiers qui ont été téléchargés sur une zone de préparation Snowflake. La zone de préparation hébergeant le fichier doit être lisible par le propriétaire de l’UDF.
Lorsque vous spécifiez l’emplacement de zone de préparation d’un fichier dans la clause IMPORTS de la FUNCTION CREATE, Snowflake copie le fichier en zone de préparation dans un répertoire d’importation disponible spécifiquement pour les UDF. Votre code de gestionnaire peut lire le fichier à partir de là.
Notez que Snowflake copie tous les fichiers importés, potentiellement de plusieurs zones de préparation, dans un seul répertoire d’importation. Pour cette raison, les noms des fichiers spécifiés dans la clause IMPORTS doivent être distincts les uns par rapport aux autres.
Pour un exemple de code, voir Chargement d’un fichier d’une zone de préparation dans une UDF Python dans cette rubrique.
Pour lire un fichier avec le code du gestionnaire d’UDF :
Copiez le fichier dans une zone de préparation de Snowflake.
Vous pouvez utiliser la commande PUT pour télécharger des fichiers depuis un répertoire local sur un ordinateur client ; pour plus d’informations, voir PUT. Pour des informations plus générales sur le chargement de fichiers dans une zone de préparation, voir Vue d’ensemble du chargement des données.
Lorsque vous créez l’UDF à l’aide de la FUNCTION CREATE, spécifiez l’emplacement du fichier dans la clause IMPORTS.
Le code de l’exemple suivant spécifie un fichier
file.txt
dans une zone de préparation appeléemy_stage
.create or replace function my_udf() ... imports=('@my_stage/file.txt') ...
Dans votre code de gestionnaire, lisez le fichier du répertoire d’importation.
Snowflake copie le fichier en zone de préparation dans le répertoire d’importation de l’UDF. Vous pouvez récupérer l’emplacement du répertoire en utilisant l’option système
snowflake_import_directory
.En code Python, vous pouvez récupérer l’emplacement du répertoire en utilisant la méthode Python
sys._xoptions
, comme dans l’exemple suivant :IMPORT_DIRECTORY_NAME = "snowflake_import_directory" import_dir = sys._xoptions[IMPORT_DIRECTORY_NAME] def compute(): with open(import_dir + 'file.txt', 'r') as file: return file.read()
Écriture de fichiers avec un gestionnaire d’UDF¶
Un gestionnaire d’UDF peut écrire des fichiers dans un répertoire /tmp créé pour la requête appelant l’UDF.
N’oubliez pas qu’un répertoire /tmp est réservé à une seule requête d’appel, alors que plusieurs processus de travail Python peuvent être exécutés en même temps. Pour éviter les collisions, vous devez vous assurer que l’accès au répertoire /tmp est synchronisé avec les autres processus de travail Python ou que les noms des fichiers écrits dans /tmp sont uniques.
Pour un exemple de code, voir Décompression d’un fichier en zone de préparation dans cette rubrique.
Le code de l’exemple suivant écrit l’entrée text
dans le répertoire /tmp, en ajoutant l’ID du processus de la fonction pour garantir l’unicité de l’emplacement du fichier.
def func(text):
# Ensure the file name's uniqueness by appending the function's process ID.
file_path = '/tmp/content' + str(os.getpid())
with open(file_path, "w") as file:
file.write(text)
Création de la fonction dans Snowflake¶
Vous devez exécuter une instruction CREATE FUNCTION pour spécifier :
Le nom de la fonction SQL à utiliser.
Le nom de la fonction Python à appeler lorsque l’UDF Python est appelée.
Le nom de l’UDF ne doit pas nécessairement correspondre au nom de la fonction du gestionnaire écrit en Python. L’instruction CREATE FUNCTION associe le nom de l’UDF à la fonction Python.
Pour choisir un nom pour l’UDF :
Suivez les règles de Identificateurs d’objet.
Choisissez un nom qui soit unique, ou suivez les règles pour Surcharge.
Important
Contrairement à la surcharge pour les UDFs SQL, qui distingue les fonctions en fonction du nombre et des types de données des arguments, les UDFs Python distinguent les fonctions selon uniquement le nombre d’arguments.
Les arguments de fonction sont liés par leur position, et non par leur nom. Le premier argument transmis à l’UDF est le premier argument reçu par la méthode Python.
Pour plus d’informations sur les types de données des arguments, voir Mappages des types de données SQL-Python.
UDFs avec du code en ligne vs. UDFs avec du code téléchargé à partir d’une zone de préparation¶
Le code d’une UDF Python peut être spécifié de l’une des manières suivantes :
Téléchargé à partir d’une zone de préparation : l’instruction CREATE FUNCTION spécifie l’emplacement d’un code source Python existant dans une zone de préparation.
En ligne : l’instruction CREATE FUNCTION spécifie le code source Python.
Création d’une UDF Python en ligne¶
Pour une UDF en ligne, vous fournissez le code source Python dans le cadre de l’instruction CREATE FUNCTION.
Par exemple, l’instruction suivante crée une UDF Python en ligne qui ajoute +1 à un nombre entier donné :
create or replace function addone(i int)
returns int
language python
runtime_version = '3.8'
handler = 'addone_py'
as
$$
def addone_py(i):
return i+1
$$;
Le code source Python est spécifié dans la clause AS
. Le code source peut être délimité soit de guillemets simples, soit d’une paire de signes de dollar ($$
). L’utilisation du double signe dollar est généralement plus facile si le code source contient des guillemets simples intégrés.
Appelez l’UDF :
select addone(10);
Voici la sortie :
+------------+
| ADDONE(10) |
|------------|
| 11 |
+------------+
Le code source Python peut contenir plus d’un module, et plus d’une fonction dans un module, donc la clause HANDLER
spécifie le module et la fonction à appeler.
Une UDF Python en ligne peut appeler du code dans les modules qui sont inclus dans la clause IMPORTS
.
Pour plus de détails sur la syntaxe de l’instruction CREATE FUNCTION, voir CREATE FUNCTION.
Pour plus d’exemples, voir Exemples d’UDF Python en ligne.
Création d’une UDF Python avec du code téléchargé à partir d’une zone de préparation¶
Les instructions suivantes créent une simple UDF Python en utilisant du code téléchargé à partir d’une zone de préparation. La zone de préparation hébergeant le fichier doit être lisible par le propriétaire de l’UDF. En outre, les fichiers ZIP doivent être autonomes et ne pas dépendre de scripts d’installation supplémentaires pour être exécutés.
Créez un fichier Python nommé sleepy.py
qui contient votre code source :
def snore(n): # return a series of n snores
result = []
for a in range(n):
result.append("Zzz")
return result
Lancez SnowSQL (client CLI) et utilisez la commande PUT pour copier le fichier du système de fichiers local vers une zone de préparation utilisateur par défaut, nommée @~
. (La commande PUT
ne peut pas être exécutée par la GUI Snowflake.)
put
file:///Users/Me/sleepy.py
@~/
auto_compress = false
overwrite = true
;
Si vous supprimez ou renommez le fichier, vous ne pouvez plus appeler l’UDF. Si vous devez mettre à jour votre fichier, faites-le pendant qu’aucun appel à l’UDF ne soit possible. Si l’ancien fichier .jar se trouve toujours dans la zone de préparation, la commande PUT
doit inclure la clause OVERWRITE=TRUE
.
Créez l’UDF. Le gestionnaire spécifie le module et la fonction.
create or replace function dream(i int)
returns variant
language python
runtime_version = '3.8'
handler = 'sleepy.snore'
imports = ('@~/sleepy.py')
Appelez l’UDF :
select dream(3);
+----------+
| DREAM(3) |
|----------|
| [ |
| "Zzz", |
| "Zzz", |
| "Zzz" |
| ] |
+----------+
Spécification de fichiers d’importation multiples¶
Voici un exemple de la façon de spécifier plusieurs fichiers d’importation.
create or replace function multiple_import_files(s string)
returns string
language python
runtime_version=3.8
imports=('@python_udf_dep/bar/python_imports_a.zip', '@python_udf_dep/foo/python_imports_b.zip')
handler='compute'
as
$$
def compute(s):
return s
$$;
Note
Les noms des fichiers d’importation spécifiés doivent être différents. Par exemple, ceci ne fonctionnera pas : imports=('@python_udf_dep/bar/python_imports.zip', '@python_udf_dep/foo/python_imports.zip')
.
Accorder des privilèges sur la fonction¶
Pour qu’un rôle autre que le propriétaire de la fonction puisse appeler la fonction, le propriétaire doit accorder les privilèges appropriés au rôle.
Les instructions GRANT pour une UDF Python sont essentiellement identiques aux instructions GRANT pour d’autres UDFs, comme les UDFs JavaScript.
Par exemple :
GRANT USAGE ON FUNCTION my_python_udf(number, number) TO my_role;
Exemples¶
Utilisation d’un paquet importé dans une UDF Python en ligne¶
Une sélection de paquets tiers d’Anaconda est disponible. Pour plus d’informations, voir Utilisation de paquets tiers.
Note
Avant que vous puissiez utiliser les paquets fournis par Anaconda, l’administrateur de votre organisation Snowflake doit accepter les conditions de tiers de Snowflake. Pour plus d’informations, voir Prise en main.
Le code suivant montre comment importer des paquets et renvoyer leurs versions.
Créez l’UDF :
create or replace function py_udf()
returns variant
language python
runtime_version = 3.8
packages = ('numpy','pandas','xgboost==1.5.0')
handler = 'udf'
as $$
import numpy as np
import pandas as pd
import xgboost as xgb
def udf():
return [np.__version__, pd.__version__, xgb.__version__]
$$;
Appelez l’UDF :
select py_udf();
Voici la sortie :
+-------------+
| PY_UDF() |
|-------------|
| [ |
| "1.19.2", |
| "1.4.0", |
| "1.5.0" |
| ] |
+-------------+
Chargement d’un fichier d’une zone de préparation dans une UDF Python¶
Cet exemple montre comment importer des fichiers non codés dans une UDF Python à partir d’une zone de préparation. Le fichier ne sera lu qu’une seule fois pendant la création de l’UDF, et ne sera pas relu pendant l’exécution de l’UDF si la lecture du fichier a lieu en dehors du gestionnaire cible. Pour plus d’informations sur la lecture d’un fichier, voir Lecture de fichiers avec un gestionnaire d’UDF.
Note
Vous ne pouvez importer des fichiers qu’à partir du répertoire de premier niveau d’une zone de préparation, pas des sous-dossiers.
Créez l’UDF :
create or replace function my_udf()
returns string
language python
runtime_version=3.8
imports=('@my_stage/file.txt')
handler='compute'
as
$$
import sys
import os
with open(os.path.join(sys._xoptions["snowflake_import_directory"], 'file.txt'), "r") as f:
s = f.read()
def compute():
return s
$$;
Décompression d’un fichier en zone de préparation¶
Vous pouvez stocker un fichier .zip dans une zone de préparation, puis le décompresser dans une UDF en utilisant le module Python zipfile.
Par exemple, vous pouvez télécharger un fichier .zip dans une zone de préparation, puis référencer le fichier .zip à son emplacement de zone de préparation dans la clause IMPORTS lorsque vous créez l’UDF. Au moment de l’exécution, Snowflake copiera le fichier en zone de préparation dans un répertoire d’importation à partir duquel votre code pourra y accéder.
Pour plus d’informations sur la lecture et l’écriture de fichiers, voir Lecture et écriture de fichiers avec un gestionnaire d’UDF.
Dans l’exemple suivant, le code de l’UDF utilise un modèle NLP pour découvrir les entités dans le texte. Le code renvoie un tableau de ces entités. Pour configurer le modèle NLP afin de traiter le texte, le code utilise d’abord le module zipfile pour extraire le fichier du modèle (en_core_web_sm-2.3.1) d’un fichier .zip. Le code utilise ensuite le module spaCy pour charger le modèle à partir du fichier.
Notez que le code écrit le contenu des fichiers extraits dans le répertoire /tmp créé pour la requête qui appelle cette fonction. Le code utilise des verrous de fichier pour garantir que l’extraction est synchronisée entre les processus de travail Python ; de cette façon, le contenu n’est décompressé qu’une seule fois. Pour plus d’informations sur l’écriture de fichiers, voir Écriture de fichiers avec un gestionnaire d’UDF.
Pour en savoir plus sur le module zipfile, consultez la référence zipfile. Pour en savoir plus sur le module spaCy, consultez la documentation de l”API spaCy.
Créez l’UDF :
create or replace function py_spacy(str string)
returns array
language python
runtime_version = 3.8
handler = 'func'
packages = ('spacy')
imports = ('@spacy_stage/spacy_en_core_web_sm.zip')
as
$$
import fcntl
import os
import spacy
import sys
import threading
import zipfile
# File lock class for synchronizing write access to /tmp
class FileLock:
def __enter__(self):
self._lock = threading.Lock()
self._lock.acquire()
self._fd = open('/tmp/lockfile.LOCK', 'w+')
fcntl.lockf(self._fd, fcntl.LOCK_EX)
def __exit__(self, type, value, traceback):
self._fd.close()
self._lock.release()
# Get the location of the import directory. Snowflake sets the import
# directory location so code can retrieve the location via sys._xoptions.
IMPORT_DIRECTORY_NAME = "snowflake_import_directory"
import_dir = sys._xoptions[IMPORT_DIRECTORY_NAME]
# Get the path to the ZIP file and set the location to extract to.
zip_file_path = import_dir + "spacy_en_core_web_sm.zip"
extracted = '/tmp/en_core_web_sm'
# Extract the contents of the ZIP. This is done under the file lock
# to ensure that only one worker process unzips the contents.
with FileLock():
if not os.path.isdir(extracted + '/en_core_web_sm/en_core_web_sm-2.3.1'):
with zipfile.ZipFile(zip_file_path, 'r') as myzip:
myzip.extractall(extracted)
# Load the model from the extracted file.
nlp = spacy.load(extracted + "/en_core_web_sm/en_core_web_sm-2.3.1")
def func(text):
doc = nlp(text)
result = []
for ent in doc.ents:
result.append((ent.text, ent.start_char, ent.end_char, ent.label_))
return result
$$;
Traitement des NULL dans des UDFs Python¶
Le code suivant montre comment les valeurs NULL sont traitées. Pour plus d’informations, voir Valeurs NULL.
Créez l’UDF :
create or replace function py_udf_null(a variant)
returns string
language python
runtime_version = 3.8
handler = 'udf'
as $$
def udf(a):
if not a:
return 'JSON null'
elif getattr(a, "is_sql_null", False):
return 'SQL null'
else:
return 'not null'
$$;
Appelez l’UDF :
select py_udf_null(null);
select py_udf_null(parse_json('null'));
select py_udf_null(10);
Voici la sortie :
+-------------------+
| PY_UDF_NULL(NULL) |
|-------------------|
| SQL null |
+-------------------+
+---------------------------------+
| PY_UDF_NULL(PARSE_JSON('NULL')) |
|---------------------------------|
| JSON null |
+---------------------------------+
+-----------------+
| PY_UDF_NULL(10) |
|-----------------|
| not null |
+-----------------+