Frappe Framework
API base de données
Toutes les fonctions de frappe.db pour lire, écrire et administrer la base de données d’un site Frappe, ainsi que le modèle de transaction appliqué par le framework.
Présentation
L’API base de données (Database API) regroupe les fonctions de frappe.db qui lisent, écrivent et administrent les tables d’un site.
Attention
À partir de la version 16, get_list et get_all comportent des changements incompatibles.
Lecture des enregistrements
frappe.db.get_list
frappe.db.get_list(doctype, filters, or_filters, fields, order_by, group_by, start, page_length, run)
- Également disponible sous l’alias
frappe.get_list.
Renvoie une liste d’enregistrements de la table du doctype. C’est une surcouche ORM d’une requête SELECT. Les permissions de l’utilisateur de la session sont appliquées aux enregistrements. Si l’argument nommé fields n’est pas fourni, seuls les noms de documents sont renvoyés. Par défaut, la méthode renvoie une liste de dict, mais vous pouvez extraire un seul champ avec l’argument nommé pluck :
frappe.db.get_list('Employee')
# output
[{'name': 'HR-EMP-00008'},
{'name': 'HR-EMP-00006'},
{'name': 'HR-EMP-00010'},
{'name': 'HR-EMP-00005'}
]
# with pluck
frappe.db.get_list('Employee', pluck='name')
# output
['HR-EMP-00008',
'HR-EMP-00006',
'HR-EMP-00010',
'HR-EMP-00005'
]Combinaison de filtres et d’autres arguments :
frappe.db.get_list('Task',
filters={
'status': 'Open'
},
fields=['subject', 'date'],
order_by='date desc',
start=10,
page_length=20,
as_list=True
)
# output
(('Update Branding and Design', '2019-09-04'),
('Missing Documentation', '2019-09-02'),
('Fundraiser for Foundation', '2019-09-03'))
# Tasks with date after 2019-09-08
frappe.db.get_list('Task', filters={
'date': ['>', '2019-09-08']
})
# Tasks with date between 2020-04-01 and 2021-03-31 (both inclusive)
frappe.db.get_list('Task', filters=[[
'date', 'between', ['2020-04-01', '2021-03-31']
]])
# Tasks with subject that contains "test"
frappe.db.get_list('Task', filters={
'subject': ['like', '%test%']
})
# Count number of tasks grouped by status
frappe.db.get_list('Task',
fields=['count(name) as count', 'status'],
group_by='status'
)
# For version 16 +
frappe.db.get_list('Task',
fields=[{'COUNT': 'name', 'as': 'count'}, 'status'],
group_by='status'
)
# output
[{'count': 1, 'status': 'Working'},
{'count': 2, 'status': 'Overdue'},
{'count': 2, 'status': 'Open'},
{'count': 1, 'status': 'Filed'},
{'count': 20, 'status': 'Completed'},
{'count': 1, 'status': 'Cancelled'}]Avant la version 16, run=False renvoyait la requête SQL au lieu de l’exécuter. À partir de la version 16, vous obtenez à la place un objet du constructeur de requêtes (Query Builder). Appelez .get_sql() pour obtenir la requête SQL comme auparavant, ou modifiez la requête à votre guise avant de l’exécuter avec query.run().
frappe.db.get_all
frappe.db.get_all(doctype, filters, or_filters, fields, order_by, group_by, start, page_length, run)
- Également disponible sous l’alias
frappe.get_all.
Identique à frappe.db.get_list, mais récupère tous les enregistrements sans appliquer les permissions.
frappe.db.get_value
frappe.db.get_value(doctype, name, fieldname) ou frappe.db.get_value(doctype, filters, fieldname)
- Également disponible sous les alias
frappe.get_valueetfrappe.db.get_values.
Renvoie la valeur d’un champ d’un document, ou une liste de valeurs.
# single value
subject = frappe.db.get_value('Task', 'TASK00002', 'subject')
# multiple values
subject, description = frappe.db.get_value('Task', 'TASK00002', ['subject', 'description'])
# as dict
task_dict = frappe.db.get_value('Task', 'TASK00002', ['subject', 'description'], as_dict=1)
task_dict.subject
task_dict.description
# with filters, will return the first record that matches filters
subject, description = frappe.db.get_value('Task', {'status': 'Open'}, ['subject', 'description'])frappe.db.get_single_value
frappe.db.get_single_value(doctype, fieldname)
Renvoie la valeur d’un champ d’un DocType Single.
timezone = frappe.db.get_single_value('System Settings', 'timezone')frappe.db.exists
frappe.db.exists(doctype, name)
Renvoie vrai si l’enregistrement existe.
Avec le doctype et le nom du document :
frappe.db.exists("User", "[email protected]", cache=True)Avec un dictionnaire de filtres contenant la clé "doctype" :
frappe.db.exists({"doctype": "User", "full_name": "Jane Doe"})Avec le doctype et un dictionnaire de filtres :
frappe.db.exists("User", {"full_name": "Jane Doe"})frappe.db.count
frappe.db.count(doctype, filters)
Renvoie le nombre d’enregistrements pour un doctype et des filters donnés.
# total number of Task records
frappe.db.count('Task')
# total number of Open tasks
frappe.db.count('Task', {'status': 'Open'})Écriture des enregistrements
frappe.db.set_value
frappe.db.set_value(doctype, name, fieldname, value)
- Également disponible sous l’alias
frappe.db.update.
Définit la valeur d’un champ dans la base de données, sans appeler les déclencheurs de l’ORM, mais en mettant à jour l’horodatage modified (sauf indication contraire).
# update a field value
frappe.db.set_value('Task', 'TASK00002', 'subject', 'New Subject')
# update multiple values
frappe.db.set_value('Task', 'TASK00002', {
'subject': 'New Subject',
'description': 'New Description'
})
# update without updating the `modified` timestamp
frappe.db.set_value('Task', 'TASK00002', 'subject', 'New Subject', update_modified=False)Attention
Cette méthode n’appelle pas les déclencheurs de l’ORM comme validate et on_update. Utilisez-la pour mettre à jour des champs cachés, ou si vous savez exactement ce que vous faites.
frappe.db.delete
frappe.db.delete(doctype, filters)
Supprime les enregistrements du doctype qui correspondent aux filters. C’est une commande DML : elle peut être annulée par un rollback. Sans filtres, tous les enregistrements du doctype sont supprimés.
frappe.db.delete("Route History", {
"modified": ("<=", last_record_to_keep[0].modified),
"user": user
})
frappe.db.delete("Error Log")
frappe.db.delete("__Test Table")Vous pouvez passer le nom d’un DocType ou le nom d’une table interne. Par convention, les tables internes de Frappe sont préfixées par __, et l’API suit cette convention. Les deux dernières commandes ci-dessus exécutent un DELETE inconditionnel sur les tables tabError Log et __Test Table.
frappe.db.truncate
frappe.db.truncate(doctype)
Vide une table de la base de données. C’est une commande DDL TRUNCATE TABLE : un commit est déclenché avant son exécution, et l’opération ne peut pas être annulée. Elle convient par exemple au nettoyage périodique des tables de journaux.
frappe.db.truncate("Error Log")
frappe.db.truncate("__Test Table")Ces commandes exécutent un TRUNCATE sur les tables tabError Log et __Test Table.
frappe.db.bulk_update
frappe.db.bulk_update(doctype, doc_updates, *, chunk_size=100, modified=None, modified_by=None, update_modified=True, debug=False)
Met à jour plusieurs documents en masse, par lots, avec une seule requête SQL UPDATE utilisant des expressions CASE.
| Paramètre | Valeur par défaut | Rôle |
|---|---|---|
doctype | — | DocType concerné. |
doc_updates | — | Dictionnaire {nom du document: {champ: valeur}} (format ci-dessous). |
chunk_size | 100 | Nombre de documents par lot. |
modified | None | Horodatage modified à écrire. |
modified_by | None | Utilisateur à inscrire dans modified_by. |
update_modified | True | Met à jour les colonnes de modification. |
debug | False | Active le mode débogage. |
Exemple :
frappe.db.bulk_update(
"Task",
{
"TASK-0001": {"status": "Closed", "description": "Completed by QA"},
"TASK-0002": {"status": "Open", "description": "Pending assignment"},
},
chunk_size=200,
modified_by="[email protected]",
update_modified=True,
debug=True,
)Format de doc_updates :
{
"DOC-0001": {"field1": "value1", "field2": "value2"},
"DOC-0002": {"field1": "valueA", "field2": "valueB"},
}À retenir
Il s’agit d’une mise à jour directe en base : les événements et validations du Document ne sont pas déclenchés. Les champs non précisés restent inchangés.
Transactions
frappe.db.commit
frappe.db.commit()
Valide la transaction en cours. Exécute le COMMIT SQL.
À retenir
Dans la plupart des cas, vous n’avez pas besoin de valider manuellement. Reportez-vous au modèle de transaction de Frappe décrit plus bas.
frappe.db.savepoint
frappe.db.savepoint(save_point)
Crée un point de sauvegarde (savepoint) nommé, vers lequel vous pourrez revenir plus tard.
frappe.db.rollback
frappe.db.rollback()
Annule la transaction en cours. Exécute le ROLLBACK SQL.
À retenir
Frappe exécute automatiquement frappe.db.rollback() si une exception est levée pendant une requête web de type POST ou PUT. Utilisez cette méthode si vous devez annuler plus tôt dans la transaction.
frappe.db.rollback(save_point="save_point_name")
Revient à un point de sauvegarde précis au lieu d’annuler toute la transaction. Ce rollback n’annule pas les modifications faites sur le système de fichiers ni les autres observateurs de rollback.
Requêtes SQL
frappe.db.sql
frappe.db.sql(query, values, as_dict)
Exécute une requête SQL arbitraire. Utile pour des rapports côté serveur complexes avec des jointures, pour adapter la base de données à de nouvelles fonctionnalités, etc.
Exemple :
values = {'company': 'Frappe Technologies Inc'}
data = frappe.db.sql("""
SELECT
acc.account_number
gl.debit
gl.credit
FROM `tabGL Entry` gl
LEFT JOIN `tabAccount` acc
ON gl.account = acc.name
WHERE gl.company = %(company)s
""", values=values, as_dict=0)Attention
Évitez cette méthode : elle contourne les validations et les contrôles d’intégrité. Il vaut toujours mieux utiliser frappe.get_doc, frappe.db.get_list, etc. quand c’est possible.
frappe.db.multisql
frappe.db.multisql({'mariadb': mariadb_query, 'postgres': postgres_query})
Exécute l’instruction SQL adaptée au moteur de base de données utilisé, parmi ceux pris en charge.
Administration du schéma
frappe.db.rename_table
frappe.db.rename_table(old_name, new_name)
Exécute une requête de renommage de table. Indiquez directement le DocType ou le nom de la table interne.
Exemple :
frappe.db.rename_table("__internal_cache", "__temporary_cache")
frappe.db.rename_table("todo", "ToDo")Le second exemple ne doit être utilisé que si vous en comprenez les conséquences.
Attention
N’utilisez pas cette méthode pour renommer les tables de DocType ; utilisez frappe.rename_doc à la place.
frappe.db.describe
frappe.db.describe(doctype)
Renvoie un tuple décrivant la table du DocType donné.
frappe.db.change_column_type
frappe.db.change_column_type(doctype, column, new_type)
Change le type d’une colonne pour le DocType indiqué.
frappe.db.add_index
frappe.db.add_index(doctype, fields, index_name)
Crée des index sur les champs indiqués d’un doctype.
À retenir
Pour indexer un champ TEXT ou BLOB, vous devez préciser une longueur fixe.
Exemple :
frappe.db.add_index("Notes", ["id(10)", "content(500)"], index_name)frappe.db.add_unique
frappe.db.add_unique(doctype, fields, constraint_name=None)
Crée une contrainte d’unicité sur les champs indiqués d’un doctype.
Exemple :
frappe.db.add_unique("DoctypeName",["field1","field2"])Hooks de transaction
À retenir
Cette API a été introduite en v15.
Frappe fournit des hooks pour exécuter des fonctions de rappel avant ou après les commandes de transaction (commit, rollback). Ils servent notamment à :
- annuler des changements faits en dehors de la base de données quand la transaction est annulée ;
- propager des changements en dehors de la base de données uniquement quand la transaction est validée.
Les hooks disponibles :
| Hook | Moment d’exécution |
|---|---|
frappe.db.before_commit.add(func: Callable) | Avant le commit. |
frappe.db.after_commit.add(func: Callable) | Après le commit. |
frappe.db.before_rollback.add(func: Callable) | Avant le rollback. |
frappe.db.after_rollback.add(func: Callable) | Après le rollback. |
Exemple d’utilisation :
def create_file(self):
self.write_file()
# This ensures rollback if DB transaction is rolledback
frappe.db.after_rollback.add(self.rollback_file)
def rollback_file(self):
self.delete_file()Modèle de transaction
Les abstractions de base de données de Frappe appliquent par défaut un modèle de transaction raisonnable. Dans la plupart des cas, vous n’avez donc pas à gérer les transactions SQL vous-même. Voici ses grandes lignes.
Requêtes web
- Lors d’un
POSTou d’unPUT, les écritures en base sont validées à la fin de la requête si celle-ci réussit. - Les appels AJAX faits avec
frappe.callsont desPOSTpar défaut, sauf modification. - Les requêtes
GETne provoquent pas de commit implicite. - Toute exception non interceptée pendant le traitement de la requête annule la transaction.
Tâches de fond et tâches planifiées
- Une fonction exécutée comme tâche de fond (Background Job) ou tâche planifiée valide la transaction à la fin de son exécution si elle réussit.
- Toute exception non interceptée annule la transaction.
Patches
- La fin réussie de la fonction
executed’un patch valide automatiquement la transaction. - Toute exception non interceptée annule la transaction.
Tests unitaires
- La transaction est validée après l’exécution d’un module de test, c’est-à-dire d’un fichier de test Python comme
test_core.py. - La transaction est aussi validée à la fin de l’ensemble des tests.
- Toute exception non interceptée arrête le lanceur de tests, donc sans commit.
Attention
Si vous interceptez des exceptions quelque part, la couche base de données ne sait pas qu’un problème est survenu : c’est alors à vous d’assurer le rollback correct de la transaction.
