Bibliothèques Python personnalisées
Cette rubrique explique comment importer un module Python dans MapForce. Un module Python est un fichier Python (.py) contenant du code Python, tel que des fonctions, des classes et des variables.
Les fonctions, classes et valeurs Python définies dans le module peuvent apparaître dans la fenêtre Bibliothèques si elles répondent aux exigences décrites dans cette rubrique. Les fonctions Python peuvent alors être utilisées dans un mappage comme n’importe quelle autre fonction MapForce.
Note : Python 3.6 ou une version ultérieure doit être installé sur votre ordinateur. La plateforme MapForce (32 bits, 64 bits) doit être la même que celle de Python.
Important
•L’importation et l’exécution de fonctions Python nécessitent les éditions MapForce Professional et Enterprise.
•Les fonctions Python sont exécutées par le langage de transformation intégré. Elles ne sont pas disponibles dans le code Java, C#, C++ ou XSLT généré.
•MapForce utilise l’installation Python configurée dans Outils | Options | Python. Si aucun chemin d’accès n’est défini dans le dialogue Options, MapForce tente de détecter une installation Python à l’aide des variables d’environnement PYTHONHOME et PATH.
•Seuls les membres dont les paramètres et les valeurs de retour sont annotés avec un type pris en charge sont importés (voir les types pris en charge ci-dessous). Les membres suivants ne sont pas importés :
oLes membres dont le nom commence par un trait de soulignement
oLes membres hérités d’une classe définie en dehors du module
•Si le fichier .py est modifié, MapForce vous en informe et vous permet de le recharger. Lorsque vous rechargez le module, toutes les fonctions sont réimportées.
Automatisation avec MapForce Server
Lorsque vous exécutez un mappage contenant des fonctions Python personnalisées avec MapForce Server, vous devrez peut-être spécifier le chemin d'accès au répertoire racine de Python. Pour plus de détails, consultez la description de la commande run dans la documentation de MapForce Server (https://www.altova.com/fr/documentation#mapforceserver).
Automatisation avec FlowForce Server
Lorsque vous exécutez un mappage contenant des fonctions Python personnalisées avec FlowForce Server, vous devrez peut-être configurer l'environnement Python dans le fichier .tool et rendre le module Python disponible sur la machine sur laquelle FlowForce Server s'exécute. Pour plus de détails, consultez la rubrique Fichiers outils dans la documentation de FlowForce Server (https://www.altova.com/fr/documentation#flowforceserver).
Fichiers d’exemple
À des fins de test, vous pouvez utiliser un exemple disponible dans MapForceExamples\Python\FormatNumber.mfd, qui appelle la fonction définie dans Python\Format\Format.py.
Les exemples utilisés dans cette rubrique proviennent du fichier MapForceExamples\Python\Sales\Sales.py.
Importation d’un module Python
Pour ajouter un module Python en tant que bibliothèque MapForce, procédez comme suit :
1.Cliquez sur le bouton Ajouter/Supprimer des bibliothèques situé au bas de la fenêtre Bibliothèques. La fenêtre Gérer des bibliothèques s'ouvre.
2.Pour importer des fonctions en tant que bibliothèque locale (uniquement dans le cadre du document actuel), cliquez sur Ajouter sous le nom du mappage actuel. Pour importer des fonctions en tant que bibliothèque globale (au niveau du programme), cliquez sur Ajouter à côté de Importations de bibliothèques globales.
3.Recherchez le fichier .py que vous souhaitez importer, par exemple MapForceExamples\Python\Sales\Sales.py. La bibliothèque importée est désormais visible dans la fenêtre Bibliothèques. Vous pouvez également double-cliquer sur un fichier .py dans la fenêtre Projet pour l’ajouter en tant que bibliothèque.
Si la bibliothèque nouvellement importée n’apparaît pas dans la fenêtre Bibliothèques, assurez-vous que le langage de transformation est défini sur Built-In. Pour ajouter une fonction au mappage, faites-la glisser depuis la fenêtre Bibliothèques vers la zone de mappage.
Types Python pris en charge
Le tableau ci-dessous illustre la correspondance entre les types XML Schema et Python pris en charge :
Type de schéma | Type Python |
|---|---|
xs:string | str |
xs:integer | int |
xs:double | float |
xs:boolean | bool |
xs:decimal | decimal.Decimal |
xs:base64Binary | bytes |
MapForce prend en charge le transfert de valeurs du schéma XML vers Python et de Python vers le schéma XML, en utilisant les types correspondants indiqués dans le tableau. Les types Python peuvent être utilisés pour les paramètres, les valeurs de retour et les attributs de module ou de classe. De plus, MapForce prend en charge les classes définies dans le même module, à condition que ces classes puissent être utilisées par MapForce (voir la section Objets ci-dessous).
Les fonctions et méthodes doivent comporter des annotations de type pour leurs paramètres et leurs valeurs de retour (telles que a: int ou -> int). Si un paramètre ou une valeur de retour n’est pas annoté ou présente un type non pris en charge, la fonction ou la méthode correspondante n’est pas importée.
Types Python non pris en charge
Les autres types Python, tels que ceux énumérés ci-dessous, ne sont pas pris en charge :
•Les classes et types importés autres que decimal.Decimal
•list, dict, tuple, set, et autres types de conteneurs
•Les types génériques tels que list[int]
•Optional[T] et Union[...]
•Les unions PEP 604 telles que int | str
•Les itérateurs et les générateurs
•Les fonctions asynchrones et les générateurs
•Listes d’arguments à longueur variable (*args, **kwargs)
Objets
MapForce prend en charge les objets qui sont des instances de classes utilisables définies dans le module importé. MapForce peut obtenir une telle instance à partir d’un constructeur, d’une valeur au niveau du module ou d’une fonction d’usine, puis transmettre l’instance à des fonctions ou l’utiliser pour appeler des méthodes d’instance et lire des propriétés d’instance.
Le listing de code ci-dessous montre la classe CurrencyFormatter définie dans Sales.py.
class CurrencyFormatter: DEFAULT_PATTERN: str = ",.2f"
def __init__(self, prefix: str, pattern: str): self._prefix = prefix self._pattern = pattern
@property def prefix(self) -> str: return self._prefix
def format_amount(self, amount: Decimal) -> str: return self._prefix + format(amount, self._pattern)
@staticmethod def plain(amount: Decimal) -> str: return format(amount, CurrencyFormatter.DEFAULT_PATTERN) |
Cette classe ajoute cinq entrées à la fenêtre Bibliothèques :
•CurrencyFormatter() : le constructeur qui crée une nouvelle instance de CurrencyFormatter.
•CurrencyFormatter.DEFAULT_PATTERN : un attribut de classe accessible sans instance.
•CurrencyFormatter.format_amount : une méthode d’instance qui s’applique à une instance de CurrencyFormatter.
•CurrencyFormatter.prefix : une propriété accessible à partir d’une instance de CurrencyFormatter.
•CurrencyFormatter.plain : une méthode statique pouvant être appelée sans instance. Les méthodes de classe s’importent de la même manière.
Obtention et transmission d’instances
MapForce peut obtenir une instance d’une classe utilisable en appelant son constructeur ou via un autre membre utilisable qui renvoie une instance. Par exemple, une instance peut être fournie par une valeur au niveau du module ou par une fonction d’usine :
EURO: CurrencyFormatter = CurrencyFormatter("EUR ", CurrencyFormatter.DEFAULT_PATTERN)
def formatter_for(prefix: str) -> CurrencyFormatter: return CurrencyFormatter(prefix, CurrencyFormatter.DEFAULT_PATTERN)
def format_with(formatter: CurrencyFormatter, amount: Decimal) -> str: return formatter.format_amount(amount) |
Dans l’extrait de code ci-dessus, EURO est une valeur au niveau du module contenant une instance de CurrencyFormatter, et formatter_for() est une fonction de fabrique qui renvoie une instance de CurrencyFormatter. La fonction format_with() accepte une instance de CurrencyFormatter en tant que paramètre.
Objets appelables
Une valeur contenant une instance d’une classe qui définit la méthode __call__ peut être appelée comme une fonction (extrait de code ci-dessous). MapForce importe donc une telle valeur en tant que fonction et ajoute () à son nom dans la fenêtre Bibliothèques. Dans l’exemple ci-dessous, ROUND_CENTS est une valeur qui peut être appelée comme une fonction. MapForce l’importe sous le nom ROUND_CENTS() dans la fenêtre Bibliothèques.
class Rounder: def __init__(self, places: int): self._places = places
def __call__(self, amount: Decimal) -> Decimal: return round(amount, self._places)
ROUND_CENTS: Rounder = Rounder(2) |
Fonctions et valeurs
Une fonction au niveau du module est importée en tant que fonction. Une valeur au niveau du module est importée sous la forme d’une fonction sans paramètres qui renvoie cette valeur.
Le listing de code ci-dessous présente la définition de valeurs au niveau du module (VAT_RATE et REPORT_TITLE) et de fonctions au niveau du module (net_amount et period).
VAT_RATE: Decimal = Decimal("0.20") REPORT_TITLE: str = "Regional sales"
def net_amount(gross: Decimal) -> Decimal: """Removes VAT from a gross amount.""" return gross / (1 + VAT_RATE)
def period(start: str, end: str) -> str: return start + " - " + end |
Le module contient également une fonction décorée avec @functools.lru_cache. MapForce importe ces fonctions comme d’habitude et utilise les paramètres et le type de retour de la fonction d’origine pour déterminer son interface. Le décorateur ne modifie pas les paramètres ni le type de retour que MapForce indique pour la fonction.
@functools.lru_cache(maxsize=None) |
Réutilisation d’objets et mise en cache
Lorsqu’une fonction ou un constructeur est utilisé dans un mappage, MapForce l’appelle pour chaque ligne qui le traverse. Si un constructeur crée un objet dont l’initialisation prend du temps, la création d’une nouvelle instance pour chaque ligne peut s’avérer inefficace.
Pour réutiliser le même objet, créez-le une seule fois au niveau du module et connectez cette valeur là où l’instance est nécessaire :
EURO: CurrencyFormatter = CurrencyFormatter("EUR ", CurrencyFormatter.DEFAULT_PATTERN) |
En connectant EURO au premier paramètre de CurrencyFormatter.format_amount, l’objet est créé une seule fois, lors du chargement du module, puis réutilisé pour chaque ligne. Les valeurs au niveau du module sont lues à chaque utilisation. Par conséquent, si du code Python met à jour une valeur au niveau du module, MapForce lit la valeur mise à jour lors de la prochaine utilisation.
Vous pouvez également mettre en cache les résultats d’appels de fonction coûteux. Un dictionnaire au niveau du module ou @functools.lru_cache stocke les résultats précédemment renvoyés :
@functools.lru_cache(maxsize=None) def region_name(code: str) -> str: """Expands a region code. The lookup table is built on first use only.""" return _region_names().get(code, code) |
Le décorateur @functools.lru_cache fait en sorte que region_name() s’exécute une seule fois par code de région distinct. Si les lignes suivantes utilisent un code qui a déjà été traité, la fonction mise en cache renvoie le résultat enregistré au lieu de réexécuter la fonction.
Les fonctions et autres noms commençant par un trait de soulignement, tels que _region_names, sont considérés comme privés au module et ne sont pas importés par MapForce.
Gardez à l’esprit les points suivants :
•Les objets au niveau du module et les valeurs mises en cache restent disponibles pendant toute la durée du mappage. Ils sont réinitialisés lorsque la bibliothèque est importée à nouveau.
•MapForce évalue un mappage sur un seul thread ; par conséquent, un cache au niveau du module ne nécessite pas de verrouillage.
•Évitez d’utiliser @functools.lru_cache sur une méthode d’instance lorsqu’une nouvelle instance est créée pour chaque ligne. Dans ce cas, le cache est associé aux instances et peut continuer à grossir. Utilisez plutôt une fonction au niveau du module pour la mise en cache.
•MapForce peut lire les attributs au niveau du module et de la classe, mais ne peut pas leur attribuer de valeurs. Utilisez du code Python pour mettre à jour ces valeurs.