#!/usr/bin/python3
# -*- coding: utf-8 -*-
########################################################################
#
# This file is part of python module <pyspc>.
# Copyright (C) 2013-2021 R. Marty
# (renaud.marty@developpement-durable.gouv.fr)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see COPYING.txt).
# If not, see <http://www.gnu.org/licenses/>.
#
########################################################################
"""
Bases de données ACCESS
"""
from datetime import datetime as dt, timedelta as td
import os.path
import pyspc.core.exception as _exception
import pyspc.io.dbase.dbase as _dbase
REF_DTIME = dt(1899, 12, 31, 0, 0) #
"""Date initiale, date 'zéro' pour Access"""
# DRIVER = "{Microsoft Access Driver (*.mdb)}" # version 32 bits
DRIVER = "Microsoft Access Driver (*.mdb, *.accdb)" # version 64 bits
"""Driver de lecture de base Access."""
[docs]
class Mdb(_dbase.Dbase):
"""
Bases Access (.mdb).
Attributes
----------
filename : str
Nom du fichier
_dbase_connect : pyodbc
Object de connexion pyodbc
_dbase_cursor : pyodbc
Curseur de pyodbc
sql : str
Requête SQL
_tables : str
Tables de la base
"""
def __init__(self, filename=None):
"""
Initialiser l'instance Mdb.
Parameters
----------
filename : str
Nom du fichier Access (.mdb)
"""
super().__init__(filename=filename)
[docs]
def connect(self):
"""
Créer la connexion à la base de données et le curseur pyodbc.
.. warning:: Cette méthode nécessite l'import de la
bibliothèque tierce pyodbc
"""
import pyodbc
# Contrôles
if not os.path.exists(self.filename):
raise OSError(f"Base de données inconnue {self.filename}")
# Ouverture de la base
self._dbase_connect = pyodbc.connect(
driver=DRIVER,
DBQ=self.filename)
self._dbase_cursor = self._dbase_connect.cursor()
[docs]
def execute(self, warning=True):
"""
Exécution de la requête SQL.
Parameters
----------
warning : bool
Afficher les avertissements. Par défaut: True
Returns
-------
list or None
Eléments retenus par la requête SQL
.. warning:: Cette méthode nécessite l'import de la
bibliothèque tierce pyodbc
"""
import pyodbc
data = None
if self.sql is None:
raise ValueError('Requête SQL non définie')
if not isinstance(self.sql, str):
raise ValueError('Requête SQL incorrecte')
if not isinstance(self._dbase_cursor, pyodbc.Cursor):
raise OSError('Curseur MDB incorrect ou non défini')
try:
self._dbase_cursor.execute(self.sql)
# except ValueError:
except pyodbc.Error as pe:
_exception.Warning(
__name__,
"erreur d'exécution de la requête SQL\n"
f"'{self.sql}'.\nLa lib 'pyodbc' indique ceci : {pe.args[-1]}")
else:
try:
data = self._dbase_cursor.fetchall()
except pyodbc.ProgrammingError:
if 'INSERT' not in self.sql and 'UPDATE' not in self.sql:
_exception.Warning(
__name__,
"une erreur est survenue lors de la "
"récupération des éléments demandés par SQL. ")
data = None
except pyodbc.DataError:
data = None
if warning:
_exception.Warning(
__name__,
"une erreur est survenue lors de la "
"lecture de la base Access. "
"Cela peut provenir de la "
"lecture de la date")
return data
return data
[docs]
@staticmethod
def from_datetime(dtime=None, fmt=None, tolerance=0):
"""
Convertir un <string> ou un <datetime> au format accepté par Access.
- Convertir les dates du format 'str' au format 'datetime.datetime'
- Convertir en réel: mode de stockage des dates dans Access
- Il faut ajouter 1 jour lors du calcul du flottant
car 366j en 1900 (Access) et 365j en 1900 (datetime.datetime)
- Il faut enlever/ajouter 60 sec. (tolerance)
dans le calcul du flottant pour contrer le pb de
précision numérique SQL/ACCESS
Parameters
----------
dtime : datetime ou str
Date à convertir
fmt : str
Format de la date, si définie comme un str
tolerance : int
Tolérance en secondes. Par défaut: 0
Returns
-------
float
Valeur réelle correspondant à la date et à la tolérance
"""
if isinstance(dtime, str):
_exception.check_str(fmt)
dtime = dt.strptime(dtime, fmt)
_exception.check_dt(dtime)
_exception.check_numeric(tolerance)
dtime = dtime - REF_DTIME + td(days=1)
access_dtime = dtime.days + float(dtime.seconds + tolerance) / 86400
return access_dtime