From 8cc72d907ca8d8671ee22c2ca8143a38decb948e Mon Sep 17 00:00:00 2001
From: Operateur <operateur@synchrotron-soleil.fr>
Date: Tue, 6 Feb 2024 10:50:41 +0100
Subject: [PATCH] feat: Add test and use it to debug

---
 ArchiveExtractor/Access.py    |  37 +++++-------
 ArchiveExtractor/Amenities.py |  20 ++++---
 ArchiveExtractor/Core.py      |  25 +++++---
 ArchiveExtractor/__init__.py  |  13 ++++-
 test/test_01.py               | 107 ++++++++++++++++++++++++++++++++++
 5 files changed, 161 insertions(+), 41 deletions(-)
 create mode 100644 test/test_01.py

diff --git a/ArchiveExtractor/Access.py b/ArchiveExtractor/Access.py
index 3cf194d..f634159 100644
--- a/ArchiveExtractor/Access.py
+++ b/ArchiveExtractor/Access.py
@@ -1,23 +1,24 @@
 import logging
 import datetime
+import traceback
+import pandas as pd
 import numpy as np
 import tango
-import pandas as pd
-import traceback
+
+import ArchiveExtractor as ae
 import ArchiveExtractor.Amenities as aea
 import ArchiveExtractor.Core as aec
 
 ##########################################################################
 ###                 Install logger for the module                      ###
 ##########################################################################
-logger = logging.getLogger(__name__)
-#logger.setLevel(getattr(logging, logger.upper()))
+logger = logging.getLogger("ArchiveExtractor")
 
 if not logger.hasHandlers():
     # No handlers, create one
     sh = logging.StreamHandler()
     sh.setLevel(logger.level)
-    sh.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
+    sh.setFormatter(logging.Formatter("{name}-{levelname:8}: {message}", style='{'))
     logger.addHandler(sh)
 
 ##########################################################################
@@ -41,11 +42,9 @@ def init(
     loglevel: string
         loglevel to pass to logging.Logger
     """
-    global _extractors
-    global _AttrTables
 
-    _extractors = (None, None)
-    _AttrTables = (None, None)
+    ae._Extractors = (None, None)
+    ae._AttrTables = (None, None)
 
     try:
         logger.setLevel(getattr(logging, loglevel.upper()))
@@ -54,17 +53,17 @@ def init(
 
     logger.debug("Instanciating extractors device proxy...")
 
-    _extractors = (tango.DeviceProxy(HdbExtractorPath), tango.DeviceProxy(TdbExtractorPath))
-    logger.debug("{} and {} instanciated.".format(*_extractors))
+    ae._Extractors = (tango.DeviceProxy(HdbExtractorPath), tango.DeviceProxy(TdbExtractorPath))
+    logger.debug("{} and {} instanciated.".format(*ae._Extractors))
 
     logger.debug("Configuring extractors device proxy...")
-    for e in _extractors:
+    for e in ae._Extractors:
         # set timeout to 3 sec
         e.set_timeout_millis(3000)
 
     logger.debug("Filling attributes lookup tables...")
-    _AttrTables = tuple(e.getattnameall() for e in _extractors)
-    logger.debug("HDB: {} TDB: {} attributes counted".format(len(_AttrTables[0]), len(_AttrTables[1])))
+    ae._AttrTables = tuple(e.getattnameall() for e in ae._Extractors)
+    logger.debug("HDB: {} TDB: {} attributes counted".format(len(ae._AttrTables[0]), len(ae._AttrTables[1])))
 
 ##########################################################################
 ###                    Module access functions                         ###
@@ -210,12 +209,11 @@ def findattr(pattern, db="H"):
     if not db in ("H", "T"):
         raise AttributeError("Attribute db should be 'H' or 'T'")
 
-    global _AttrTables
 
     keywords=pattern.lower().split('*')
 
     # Select DB
-    attr_table = _AttrTables[{'H':0, 'T':1}[db]]
+    attr_table = ae._AttrTables[{'H':0, 'T':1}[db]]
 
     matches = [attr for attr in attr_table if all(k in attr.lower() for k in keywords)]
 
@@ -248,7 +246,7 @@ def infoattr(attribute, db='H'):
     info = dict()
 
     for func in ("GetAttDefinitionData", "GetAttPropertiesData"):
-        R=getattr(_extractors[{'H':0, 'T':1}[db]], func)(attribute)
+        R=getattr(ae._Extractors[{'H':0, 'T':1}[db]], func)(attribute)
         if not R is None:
             for i in R:
                 _s=i.split("::")
@@ -257,8 +255,3 @@ def infoattr(attribute, db='H'):
             logger.warning("Function %s on extractor returned None"%func)
 
     return info
-
-## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
-## Initialize on import
-## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
-init()
diff --git a/ArchiveExtractor/Amenities.py b/ArchiveExtractor/Amenities.py
index 5087ff8..03e9798 100644
--- a/ArchiveExtractor/Amenities.py
+++ b/ArchiveExtractor/Amenities.py
@@ -1,4 +1,10 @@
+import logging
+import datetime
+import numpy as np
+import ArchiveExtractor as ae
 
+# Get the module logger
+logger = logging.getLogger("ArchiveExtractor")
 
 ##########################################################################
 ###               Commodity private variables                          ###
@@ -33,8 +39,7 @@ def _check_initialized():
     -------
     success : boolean
     """
-    global _extractors
-    if None in _extractors:
+    if None in ae._Extractors:
         logger.error("Module {0} is not initialied. You should run {0}.init().".format(__name__))
         return False
     return True
@@ -120,12 +125,11 @@ def _check_attribute(attribute, db):
     db: str
         Which database to look in, 'H' or 'T'.
     """
-    global _extractors
 
     logger.debug("Check that %s is archived."%attribute)
-    if not _extractors[{'H':0, 'T':1}[db]].IsArchived(attribute):
-        logger.error("Attribute '%s' is not archived in DB %s"%(attribute, _extractors[{'H':0, 'T':1}[db]]))
-        raise ValueError("Attribute '%s' is not archived in DB %s"%(attribute, _extractors[{'H':0, 'T':1}[db]]))
+    if not ae._Extractors[{'H':0, 'T':1}[db]].IsArchived(attribute):
+        logger.error("Attribute '%s' is not archived in DB %s"%(attribute, ae._Extractors[{'H':0, 'T':1}[db]]))
+        raise ValueError("Attribute '%s' is not archived in DB %s"%(attribute, ae._Extractors[{'H':0, 'T':1}[db]]))
 
 ##----------------------------------------------------------------------##
 def _chunkerize(attribute, dateStart, dateStop, db, Nmax=100000):
@@ -154,11 +158,11 @@ def _chunkerize(attribute, dateStart, dateStop, db, Nmax=100000):
         List of datetime giving the limit of each chunks.
         For N chunks, there is N+1 elements in cdates, as the start and end boundaries are included.
     """
-    info=infoattr(attribute, db=db)
+    info=ae.infoattr(attribute, db=db)
     logger.debug("Attribute information \n%s"%info)
 
     # Get the number of points
-    N=_extractors[{'H':0, 'T':1}[db]].GetAttDataBetweenDatesCount([
+    N=ae._Extractors[{'H':0, 'T':1}[db]].GetAttDataBetweenDatesCount([
             attribute,
             dateStart.strftime(_DBDFMT2),
             dateStop.strftime(_DBDFMT2)
diff --git a/ArchiveExtractor/Core.py b/ArchiveExtractor/Core.py
index e35d542..d360797 100644
--- a/ArchiveExtractor/Core.py
+++ b/ArchiveExtractor/Core.py
@@ -1,4 +1,13 @@
+import logging
+import datetime
+import numpy as np
+import pandas as pd
 
+import ArchiveExtractor as ae
+import ArchiveExtractor.Amenities as aea
+
+# Get the module logger
+logger = logging.getLogger("ArchiveExtractor")
 
 ##########################################################################
 ###                    Module core functions                           ###
@@ -14,7 +23,7 @@ def _extract_attribute(attribute, method, date1, date2, db):
     aea._check_attribute(attribute, db)
 
     # Get info about the attribute
-    info=infoattr(attribute, db=db)
+    info=ae.infoattr(attribute, db=db)
     logger.debug("Attribute information \n%s"%info)
 
     # Detect spectrum
@@ -48,7 +57,7 @@ def _extract_scalar(attribute, method, date1, date2, db, dtype):
 
     # =====================
     if method == "nearest":
-        cmdreturn = aea._cmd_with_retry(_extractors[{'H':0, 'T':1}[db]], "GetNearestValue", [
+        cmdreturn = aea._cmd_with_retry(ae._Extractors[{'H':0, 'T':1}[db]], "GetNearestValue", [
                                                 attribute,
                                                 date1.strftime(aea._DBDFMT),
                                                 ])
@@ -79,7 +88,7 @@ def _extract_scalar(attribute, method, date1, date2, db, dtype):
 
         # For each date chunk
         for i_d in range(len(cdates)-1):
-            cmdreturn = aea._cmd_with_retry(_extractors[{'H':0, 'T':1}[db]], "ExtractBetweenDates", [
+            cmdreturn = aea._cmd_with_retry(ae._Extractors[{'H':0, 'T':1}[db]], "ExtractBetweenDates", [
                                                     attribute,
                                                     cdates[i_d].strftime(aea._DBDFMT),
                                                     cdates[i_d+1].strftime(aea._DBDFMT)
@@ -120,7 +129,7 @@ def _extract_scalar(attribute, method, date1, date2, db, dtype):
 def _extract_vector(attribute, method, date1, date2, db):
 
     # Get info about the attribute
-    info=infoattr(attribute, db=db)
+    info=ae.infoattr(attribute, db=db)
 
     # =====================
     if method == "nearest":
@@ -132,7 +141,7 @@ def _extract_vector(attribute, method, date1, date2, db):
         dt=datetime.timedelta(seconds=10)
         while cnt<1:
             logger.debug("Seeking points in {} to {}".format(date1-dt,date1+dt))
-            cnt=_extractors[{'H':0, 'T':1}[db]].GetAttDataBetweenDatesCount([
+            cnt=ae._Extractors[{'H':0, 'T':1}[db]].GetAttDataBetweenDatesCount([
                     attribute,
                     (date1-dt).strftime(aea._DBDFMT2),
                     (date1+dt).strftime(aea._DBDFMT2)
@@ -142,7 +151,7 @@ def _extract_vector(attribute, method, date1, date2, db):
 
 
         # For vector, we have to use the GetAttxxx commands
-        cmdreturn = aea._cmd_with_retry(_extractors[{'H':0, 'T':1}[db]], "GetAttDataBetweenDates", [
+        cmdreturn = aea._cmd_with_retry(ae._Extractors[{'H':0, 'T':1}[db]], "GetAttDataBetweenDates", [
                                                 attribute,
                                                 (date1-dt).strftime(aea._DBDFMT),
                                                 (date1+dt).strftime(aea._DBDFMT),
@@ -158,7 +167,7 @@ def _extract_vector(attribute, method, date1, date2, db):
 
         # Read the history
         logger.debug("Retrieve history of %d values. Dynamic attribute named %s."%(N, name))
-        attrHist = _extractors[{'H':0, 'T':1}[db]].attribute_history(name, N)
+        attrHist = ae._Extractors[{'H':0, 'T':1}[db]].attribute_history(name, N)
 
         # Transform to datetime - value arrays
         _value = np.empty((N, int(info["max_dim_x"])), dtype=float)
@@ -295,7 +304,7 @@ def ExtrBetweenDates_MinMaxMean(
                 cdates[i_d+1].strftime(_DBDFMT2))
                 )
 
-            _val =getattr(_extractors[{'H':0, 'T':1}[db]], "GetAttData%sBetweenDates"%func)([
+            _val =getattr(ae._Extractors[{'H':0, 'T':1}[db]], "GetAttData%sBetweenDates"%func)([
                 attribute,
                 cdates[i_d].strftime(_DBDFMT2),
                 cdates[i_d+1].strftime(_DBDFMT2)
diff --git a/ArchiveExtractor/__init__.py b/ArchiveExtractor/__init__.py
index 75fcb96..196a71e 100644
--- a/ArchiveExtractor/__init__.py
+++ b/ArchiveExtractor/__init__.py
@@ -10,11 +10,18 @@ __all__ = ["Access", ]
 ###                  Module private variables                          ###
 ##########################################################################
 # Tuple of extractor for HDB and TDB
-global _extractors
-_extractors = (None, None)
+_Extractors = (None, None)
 
 # Tuple for attribute tables
-global _AttrTables
 _AttrTables = (None, None)
 
+##########################################################################
+###               Functions in Access are entry points                 ###
+##########################################################################
+from .Access import *
+
 
+## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
+## Initialize on import
+## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
+init()
diff --git a/test/test_01.py b/test/test_01.py
new file mode 100644
index 0000000..8997c60
--- /dev/null
+++ b/test/test_01.py
@@ -0,0 +1,107 @@
+import logging
+
+logger = logging.getLogger("tester")
+sh = logging.StreamHandler()
+sh.setFormatter(logging.Formatter("{name}-{levelname:8}: {message}", style='{'))
+logger.addHandler(sh)
+logger.setLevel(logging.DEBUG)
+
+# Put root folder in path
+import sys, os
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
+# test import
+logger.info("Testing import")
+import ArchiveExtractor
+
+# it should have been automatically initialized, check that extractors are set
+logger.info("Testing auto init")
+logger.debug(ArchiveExtractor._Extractors)
+if None in ArchiveExtractor._Extractors:
+    raise RuntimeError("ArchiveExtractor does not seems properly initialized")
+
+# Test init()
+logger.info("Testing init")
+ArchiveExtractor.init(loglevel='debug')
+
+###############################################################################
+# Test findattr
+logger.info("Testing findattr()")
+
+attrs = ArchiveExtractor.findattr("*dg*dcct*current*")
+logger.debug(attrs)
+if len(attrs) < 1:
+    raise RuntimeError("Failed to get attributes with findattr")
+
+attrs = ArchiveExtractor.findattr("*dg*dcct*current*", db='T')
+logger.debug(attrs)
+if len(attrs) < 1:
+    raise RuntimeError("Failed to get attributes with findattr")
+
+###############################################################################
+# Test infoattr
+logger.info("Testing infoattr()")
+
+info = ArchiveExtractor.infoattr(attrs[0], db='T')
+logger.debug(info)
+
+###############################################################################
+# Test extractions
+
+attr = ArchiveExtractor.findattr("ans/dg*dcct*current*")[0]
+
+logger.info("Testing extract() ; scalar, nearest, timedelta")
+val = ArchiveExtractor.extract(attr, "0h")
+logger.debug(val)
+if val is None:
+    raise RuntimeError("Could not perform extraction")
+
+logger.info("Testing extract() ; scalar, between, precise date and timedelta")
+val = ArchiveExtractor.extract(attr, "1h", "2023-12-13-00:30",  method='between')
+logger.debug(val)
+if val is None:
+    raise RuntimeError("Could not perform extraction")
+
+
+logger.info("Testing extract() ; scalar, nearest, specific date")
+# Test several formats
+for fmt in [
+        "2023-08",
+        "2024-01-10",
+        "2024-01-10-12:00",
+        ]:
+    logger.debug(fmt)
+    val = ArchiveExtractor.extract(attr, fmt)
+    logger.debug(val)
+    if val is None:
+        raise RuntimeError("Could not perform extraction")
+
+logger.info("Testing extract() ; dict, nearest, specific date")
+val = ArchiveExtractor.extract({"attr":attr, "attr2":attr}, "2023-06")
+logger.debug(val)
+
+logger.info("Testing extract() ; list, nearest, specific date")
+val = ArchiveExtractor.extract(ArchiveExtractor.findattr("dg*dcct*current"), "2023-06")
+logger.debug(val)
+
+
+logger.info("Testing extract() ; scalar, between, timedelta")
+val = ArchiveExtractor.extract(attr, "3h", method='between')
+logger.debug(val)
+if val is None:
+    raise RuntimeError("Could not perform extraction")
+
+logger.info("Testing extract() ; scalar, between, precise date")
+val = ArchiveExtractor.extract(attr, "2023-12-13-00:30", "2023-12-13-01:30", method='between')
+logger.debug(val)
+if val is None:
+    raise RuntimeError("Could not perform extraction")
+
+logger.info("Testing extract() ; spectrum, nearest, precise date")
+val = ArchiveExtractor.extract('ANS/DG/BPM-MANAGER/zRefOrbit', "2023-12-13-00:30")
+logger.debug(val)
+if val is None:
+    raise RuntimeError("Could not perform extraction")
+
+
+logger.info("Test success !")
-- 
GitLab