apb_cx_oracle_spatial.gestor_oracle

   1#   coding=utf-8
   2#  #
   3#   Author: Ernesto Arredondo Martinez (ernestone@gmail.com)
   4#   File: gestor_oracle.py
   5#   Created: 05/04/2020, 00:38
   6#   Last modified: 10/11/2019, 11:24
   7#   Copyright (c) 2020
   8
   9import csv
  10import datetime
  11from functools import wraps
  12import inspect
  13import itertools
  14import json
  15import os
  16import shutil
  17import sys
  18from collections import namedtuple, OrderedDict
  19from subprocess import Popen, PIPE
  20from tempfile import SpooledTemporaryFile
  21from zipfile import ZipFile, ZIP_DEFLATED
  22
  23import cx_Oracle
  24import lxml.etree as etree
  25
  26from apb_extra_utils import utils_logging
  27from apb_extra_utils.utils_logging import logger_path_logs
  28from apb_spatial_utils import topojson_utils
  29from . import sdo_geom as m_sdo_geom
  30from apb_extra_utils.sql_parser import x_sql_parser
  31
  32# Nombres tipo geometria GTYPE oracle por orden valor GTYPE
  33GTYPES_ORA = ["DEFAULT",
  34              "POINT",
  35              "LINE",
  36              "POLYGON",
  37              "COLLECTION",
  38              "MULTIPOINT",
  39              "MULTILINE",
  40              "MULTIPOLYGON"]
  41
  42# Se inicializan tipos de geometria Oracle
  43__class_tips_geom_ora = {}
  44for nom_tip in GTYPES_ORA:
  45    __class_tips_geom_ora[nom_tip] = namedtuple("gtype_" + nom_tip.upper(),
  46                                                ['TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID'])
  47
  48
  49def class_tip_geom(gtype_ora="DEFAULT"):
  50    """
  51    Retorna NAMEDTUPLE 'cursor_desc_tip_geom_GTYPE' con columnas
  52    'TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID'
  53
  54    Args:
  55        gtype_ora: tipo geometrias como claves en __class_tips_geom_ora
  56
  57    Returns:
  58        namedtuple('TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID')
  59
  60    """
  61    gtype_ora = gtype_ora.upper()
  62
  63    if gtype_ora not in __class_tips_geom_ora:
  64        gtype_ora = "DEFAULT"
  65
  66    return __class_tips_geom_ora.get(gtype_ora)
  67
  68
  69# Caches para atributos de tablas_o_vistas de Oracle
  70__cache_pks_tab = {}
  71__cache_row_desc_tab = {}
  72__cache_tips_geom_tab = {}
  73__cache_row_class_tab = {}
  74
  75
  76def del_cache_rel_con_db(con_db_name: str):
  77    """
  78    Borra las Caches para atributos de tablas_o_vistas de Oracle
  79    Args:
  80        con_db_name: nom de la connexió
  81
  82    Returns:
  83
  84    """
  85    all_cache = [__cache_pks_tab, __cache_row_desc_tab, __cache_tips_geom_tab, __cache_row_class_tab]
  86    for cache_dic in all_cache:
  87        # alamcenamos las claves porque no se puede eliminar del diccionario mientras se itera
  88        keys_remove = []
  89        for key in cache_dic:
  90            if key.startswith(con_db_name):
  91                keys_remove.append(key)
  92        for key_r in keys_remove:
  93            del cache_dic[key_r]
  94
  95
  96def get_oracle_connection(user_ora, psw_ora, dsn_ora=None, call_timeout=None, schema_ora=None):
  97    """
  98    Return cx_Oracle Connection
  99    Args:
 100        user_ora (str):
 101        psw_ora (str):
 102        dsn_ora (str=None):
 103        call_timeout (int=None): miliseconds
 104        schema_ora(str=None): indicate scheme when it is different from the user, default schema = user
 105
 106    Returns:
 107        cx_Oracle.Connection
 108    """
 109    connection = cx_Oracle.Connection(user_ora, psw_ora, dsn_ora, encoding="UTF-8", nencoding="UTF-8")
 110    if call_timeout:
 111        connection.call_timeout = call_timeout
 112
 113    if schema_ora:
 114        connection.current_schema = schema_ora
 115
 116    return connection
 117
 118
 119def get_nom_conexion(con_db):
 120    """
 121    Devuelve el nombre de la conexion a Oracle
 122
 123    Args:
 124        con_db:
 125
 126    Returns:
 127
 128    """
 129    return "@".join((con_db.username.upper(), con_db.dsn.upper()))
 130
 131
 132def new_cursor(con_db, input_handler=None, output_handler=None):
 133    """
 134    Retorna cx_Oracle.Cursor con los handlers pasados por parámetro
 135
 136    Args:
 137        con_db:
 138        input_handler:
 139        output_handler:
 140
 141    Returns:
 142        cx_Oracle.cursor
 143    """
 144    try:
 145        curs = con_db.cursor()
 146
 147        if input_handler:
 148            curs.inputtypehandler = input_handler
 149
 150        if output_handler:
 151            curs.outputtypehandler = output_handler
 152
 153        return curs
 154
 155    except cx_Oracle.Error as exc:
 156        print("!!ERROR!! - Error al instanciar Cursor para la conexion Oracle {}\n"
 157              "     Error: {}".format(get_nom_conexion(con_db), exc))
 158        raise
 159
 160
 161def get_row_descriptor(curs, nom_base_desc=None):
 162    """
 163    Retorna instancia namedtuple indexada por las columnas de la query ejecutada en el cursor con los
 164    tipos de columna por valores.
 165
 166    Si se pasa nom_base_desc se hará heredar el namedtuple de esa clase
 167
 168    Args:
 169        curs:
 170        nom_base_desc:
 171
 172    Returns:
 173
 174    """
 175    ora_desc = None
 176    dd_reg = curs.description
 177    dict_camps = OrderedDict.fromkeys([def_col[0].replace(" ", "_") for def_col in dd_reg])
 178    if nom_base_desc:
 179        # Por si viene nombre de tabla con esquema delante nos quedamos solo con la ultima parte
 180        nom_base_desc = nom_base_desc.split(".")[-1]
 181        nom_base_desc += "_cursor"
 182    else:
 183        nom_base_desc = "cursor_" + str(id(curs))
 184
 185    try:
 186        nt_class = namedtuple(nom_base_desc,
 187                              list(dict_camps))
 188        ora_desc = nt_class(*[def_cols[1] for def_cols in dd_reg])
 189    except ValueError:
 190        raise Exception("!ERROR! - No se puede crear descriptor de fila para el SQL especificado. "
 191                        "El nombre de la columna {} no es válido".format(str(sys.exc_info()[1]).split(":")[1]),
 192                        sys.exc_info())
 193
 194    return ora_desc
 195
 196
 197def get_row_class_cursor(cursor_desc, con_db):
 198    """
 199    Retorna clase de fila por defecto (class row_cursor) para un cursor
 200
 201    Args:
 202        cursor_desc: (opcional) clase de la que se quiera heredar
 203        con_db: conexión cx_Oracle
 204
 205    Returns:
 206        class (default = row_cursor)
 207    """
 208    cls_curs_dsc = type(cursor_desc)
 209
 210    class row_cursor(cls_curs_dsc):
 211        """
 212        Clase para fila devuelta por query SQL (para queries que NO sean sobre una tabla/vista)
 213        """
 214        ora_descriptor = cursor_desc
 215        con_ora = con_db
 216
 217        def vals(self):
 218            """
 219            Valores de la fila
 220
 221            Returns:
 222                OrderedDict
 223            """
 224            return self._asdict()
 225
 226        def as_xml(self,
 227                   nom_root_xml="root_reg_sql",
 228                   atts_root_xml=None,
 229                   excluded_cols=None,
 230                   as_etree_elem=False):
 231            """
 232            Devuelve fila en formato XML
 233
 234            Args:
 235                nom_root_xml: define el nombre del TAG root del XML. Por defecto 'root_reg_sql'
 236                atts_root_xml: lista que define las columnas que se asignarán
 237                               como atributos del TAG root
 238                excluded_cols: lista con nombres de columnas que no se quieran incluir
 239                as_etree_elem (default=False): Si True devuelve registro como objeto etree.Element.
 240                                              Si False (por defecto) como str en formato XML
 241
 242            Returns:
 243                str (formato XML) OR etree.Element
 244            """
 245            if not excluded_cols:
 246                excluded_cols = list()
 247
 248            atts_xml = None
 249            d_xml_elems = self.xml_elems()
 250            if atts_root_xml:
 251                atts_xml = OrderedDict({att.lower(): d_xml_elems[att.lower()]
 252                                        for att in atts_root_xml})
 253
 254            tab_elem = etree.Element(nom_root_xml.lower(), atts_xml)
 255            excluded_cols = [c.lower() for c in excluded_cols]
 256            for tag, xml_val in d_xml_elems.items():
 257                if tag in excluded_cols:
 258                    continue
 259
 260                try:
 261                    camp_elem = etree.SubElement(tab_elem, tag.lower())
 262                    camp_elem.text = xml_val
 263                except:
 264                    print("!ERROR! - No se ha podido añadir el campo '",
 265                          tag, "' al XML", sep="")
 266
 267            ret = tab_elem
 268
 269            if not as_etree_elem:
 270                ret = etree.tostring(tab_elem, encoding="unicode")
 271
 272            return ret
 273
 274        def xml_elems(self):
 275            """
 276            Itera los campos del registro y devuelve su nombre y su valor parseado para XML
 277
 278            Yields:
 279                n_camp, val_camp
 280            """
 281            d_xml_elems = dict()
 282            for camp, val in self.vals().items():
 283                ret_val = ""
 284                if isinstance(val, datetime.datetime):
 285                    ret_val = val.strftime("%Y-%m-%dT%H:%M:%S")
 286                elif isinstance(val, m_sdo_geom.sdo_geom):
 287                    ret_val = val.as_wkt()
 288                elif val:
 289                    ret_val = str(val)
 290
 291                d_xml_elems[camp.lower()] = ret_val
 292
 293            return d_xml_elems
 294
 295        def as_json(self):
 296            """
 297            Retorna la fila como JSON con las geometrias en formato GeoJson
 298
 299            Returns:
 300                str con la fila en formato json
 301            """
 302            return json.dumps(self.vals(),
 303                              ensure_ascii=False,
 304                              cls=geojson_encoder)
 305
 306        def as_geojson(self):
 307            """
 308            Retorna la fila como GEOJSON
 309
 310            Returns:
 311                str con la fila en formato geojson
 312            """
 313            return json.dumps(self.__geo_interface__,
 314                              ensure_ascii=False,
 315                              cls=geojson_encoder)
 316
 317        @property
 318        def __geo_interface__(self):
 319            """
 320            GeoJSON-like protocol for geo-spatial para toda una fila devuelta por query SQL
 321
 322            Si la fila tiene varias geometrias devolverá geojson como "GeometryCollection"
 323
 324            Returns:
 325                dict con las geometrias y campos alfanuméricos (properties de Feature) en formato geojson
 326            """
 327            geos = dict(**self.sdo_geoms)
 328            num_geos = len(geos)
 329            geom = None
 330            if num_geos == 1:
 331                geom = next(iter(geos.values()))
 332            elif num_geos > 1:
 333                geom = {"type": "GeometryCollection",
 334                        "geometries": list(geos.values())}
 335
 336            if geom:
 337                return dict({"type": "Feature",
 338                             "geometry": geom,
 339                             "properties": {nc: val for nc, val in self.vals().items() if nc not in geos}})
 340
 341        @property
 342        def sdo_geoms(self):
 343            """
 344            Devuelve diccionario indexado por los nombres de columnas que tienen valor geometrico de la clase sdo_geom
 345
 346            Returns:
 347                dict {nom_column:sdo_geom}
 348            """
 349            return {nc: g.__geo_interface__
 350                    for nc, g in self.vals().items() if isinstance(g, m_sdo_geom.sdo_geom)}
 351
 352        def geometries(self, as_format=None):
 353            """
 354            Itera por los campos geométricos informados
 355
 356            Args:
 357                as_format (default=None): Se puede informar con nombre funcion 'as_XXXX' a la que responda SDO_GEOM
 358
 359            Yields:
 360                nom_camp, geom
 361            """
 362            for ng, g in self.vals().items():
 363                if isinstance(g, m_sdo_geom.sdo_geom):
 364                    g_val = g
 365                    if as_format:
 366                        g_val = getattr(g, as_format)()
 367
 368                    yield ng, g_val
 369
 370        @property
 371        def cols_pts_angles(self):
 372            """
 373            Devuelve diccionario de campos angulo para geometrias puntuales con la clave el nombre del campo angulo y
 374            el valor el nombre del campo puntual al que hace referencia
 375
 376            Returns:
 377                dict
 378            """
 379            sufix_ang = "_ANG"
 380            cols = self.cols
 381            cols_ang = {}
 382            for c in (c for c, gd in self.geoms_vals().items() if gd.GTYPE.endswith('POINT')):
 383                col_ang = x_sql_parser.get_nom_obj_sql(c, sufix=sufix_ang)
 384                if col_ang in cols:
 385                    cols_ang[col_ang] = c
 386
 387            return cols_ang
 388
 389        @property
 390        def cols(self):
 391            """
 392            Devuelve lista con el nombre de la columnas de la fila
 393
 394            Returns:
 395                list con nombres columnas
 396            """
 397            return self._fields
 398
 399    return row_cursor
 400
 401
 402def get_pk_tab(con_db, nom_tab_or_view):
 403    """
 404    Retorna la PRIMARY KEY de una tabla o vista de Oracle
 405
 406    Args:
 407        con_db:
 408        nom_tab_or_view:
 409
 410    Returns:
 411        lista nombre columnas clave
 412    """
 413    nom_pk_cache = get_nom_conexion(con_db) + ":" + nom_tab_or_view.upper()
 414    noms_camps_pk = __cache_pks_tab.get(nom_pk_cache)
 415    if not noms_camps_pk:
 416        sql_keys = "SELECT cols.column_name " \
 417                   "FROM user_constraints cons, user_cons_columns cols " \
 418                   "WHERE cols.table_name = :1 " \
 419                   "AND cons.constraint_type = 'P' " \
 420                   "AND cons.constraint_name = cols.constraint_name " \
 421                   "AND cons.owner = cols.owner " \
 422                   "ORDER BY cols.table_name, cols.position"
 423
 424        noms_camps_pk = [fila_val.COLUMN_NAME for fila_val in
 425                         iter_execute_fetch_sql(con_db, sql_keys, nom_tab_or_view.upper())]
 426        __cache_pks_tab[nom_pk_cache] = noms_camps_pk
 427
 428    return noms_camps_pk
 429
 430
 431def get_tips_geom_tab(con_db, nom_tab_or_view):
 432    """
 433    Retorna diccionario con nombre del campo como indice y los tipos de campos geométricos
 434    (class_tip_geom) registrados en la global __class_tips_geom_ora como clases 'c_desc_tip_geom_?' siendo ?
 435    el LAYER_GTYPE del indice de Oracle
 436
 437    Args:
 438        con_db:
 439        nom_tab_or_view:
 440
 441    Returns:
 442        dict indexado por nombre columnas geometria y la clase de geometria (__class_tips_geom_ora)
 443    """
 444    nom_con = get_nom_conexion(con_db)
 445
 446    tips_geom_con = __cache_tips_geom_tab.get(nom_con)
 447    if not tips_geom_con:
 448        tips_geom_con = __cache_tips_geom_tab[nom_con] = {}
 449        sql_tip_geom = "select  /*+ result_cache */ " \
 450                       "        T_COLS.TABLE_NAME, " \
 451                       "        T_COLS.COLUMN_NAME, " \
 452                       "        V_IDX_META.SDO_LAYER_GTYPE GTYPE, " \
 453                       "        V_G_META.SRID " \
 454                       "from    user_tab_columns t_cols," \
 455                       "        user_sdo_geom_metadata v_g_meta," \
 456                       "        user_sdo_index_info v_idx_info," \
 457                       "        user_sdo_index_metadata v_idx_meta " \
 458                       "where v_g_meta.table_name = t_cols.table_name and " \
 459                       "v_g_meta.column_name = t_cols.column_name and" \
 460                       "    v_idx_info.table_name = t_cols.table_name and " \
 461                       "v_idx_info.column_name = t_cols.column_name and" \
 462                       "    v_idx_meta.sdo_index_name = v_idx_info.index_name"
 463
 464        for reg_tip_geom in iter_execute_fetch_sql(con_db, sql_tip_geom):
 465            nom_cache = reg_tip_geom.TABLE_NAME.upper()
 466            cache_tips_geom = tips_geom_con.get(nom_cache)
 467            if not cache_tips_geom:
 468                cache_tips_geom = {}
 469                tips_geom_con[nom_cache] = cache_tips_geom
 470
 471            tip_geom = class_tip_geom(reg_tip_geom.GTYPE)(*reg_tip_geom.vals().values())
 472
 473            nom_camp_geom = reg_tip_geom.COLUMN_NAME.upper()
 474            cache_tips_geom[nom_camp_geom] = tip_geom
 475
 476    tips_geom = tips_geom_con.get(nom_tab_or_view.upper())
 477
 478    return tips_geom if tips_geom else {}
 479
 480
 481def get_row_desc_tab(con_db, nom_tab_or_view):
 482    """
 483    Retorna el descriptor (cursor con los tipos de campo para cada columna) de una tabla o vista de Oracle
 484
 485    Args:
 486        con_db:
 487        nom_tab_or_view:
 488
 489    Returns:
 490        namedtuple ó clase fila con las columnas y sus tipos
 491    """
 492    nom_dd_cache = get_nom_conexion(con_db) + ":" + nom_tab_or_view.upper()
 493    tab_desc = __cache_row_desc_tab.get(nom_dd_cache)
 494    if not tab_desc:
 495        row_class_tab = get_row_class_tab(con_db, nom_tab_or_view)
 496
 497        camps_dd = row_class_tab.ora_descriptor
 498        dict_camps = camps_dd._asdict()
 499        dict_camps_geom = row_class_tab.geoms()
 500        if dict_camps_geom:
 501            for nom_camp, tip_camp_geom in dict_camps_geom.items():
 502                dict_camps[nom_camp] = tip_camp_geom
 503
 504        tab_desc = row_class_tab(*dict_camps.values())
 505        __cache_row_desc_tab[nom_dd_cache] = tab_desc
 506
 507    return tab_desc
 508
 509
 510def get_tip_camp(con_db, nom_tab_or_view, nom_camp):
 511    """
 512    Devuelve el tipo de campo para la tabla_vista y campo especificado. En el caso de campos alfanuméricos
 513    devuelve los tipos de cx_Oracle relacionados (cx_Oracle.NUMBER, cx_Oracle.STRING, ...) y para las
 514    geométricas del tipo CLASS_TIP_GEOM
 515
 516    Args:
 517        con_db:
 518        nom_tab_or_view:
 519        nom_camp:
 520
 521    Returns:
 522        object: (cx_Oracle.NUMBER, cx_Oracle.STRING, ...) o si geometria tipo en __class_tips_geom_ora
 523    """
 524    nom_tab_or_view = nom_tab_or_view.upper()
 525    nom_camp = nom_camp.upper()
 526
 527    dd_tab = get_row_desc_tab(con_db, nom_tab_or_view)
 528    if dd_tab:
 529        return getattr(dd_tab, nom_camp, None)
 530
 531
 532def sql_tab(nom_tab_or_view, filter_sql=None, columns=None):
 533    """
 534    Devuelve sql para tabla o vista
 535
 536    Args:
 537        nom_tab_or_view:
 538        filter_sql:
 539        columns (list): Lista nombre de columnas a mostrar. Default '*' (todas)
 540
 541    Returns:
 542        str: con select sql para tabla o vista
 543    """
 544    cols = "*"
 545    if columns:
 546        cols = ",".join(columns)
 547
 548    sql_tab = "select {cols} from {taula} TAB".format(
 549        taula=nom_tab_or_view,
 550        cols=cols)
 551
 552    if filter_sql:
 553        sql_tab += " where " + filter_sql
 554
 555    return sql_tab
 556
 557
 558def get_row_class_tab(con_db, nom_tab_or_view):
 559    """
 560    Retorna clase que hereda de namedtuple para instanciar con los valores de un nuevo registro
 561
 562    Args:
 563        con_db:
 564        nom_tab_or_view:
 565
 566    Returns:
 567        object: clase que hereda del namedtuple para una fila
 568    """
 569    nom_tab_or_view = nom_tab_or_view.upper()
 570    nom_row_class_tab_cache = get_nom_conexion(con_db) + ":" + nom_tab_or_view
 571    row_class_tab = __cache_row_class_tab.get(nom_row_class_tab_cache)
 572
 573    if not row_class_tab:
 574        curs = con_db.cursor()
 575        curs.execute(sql_tab(nom_tab_or_view))
 576        ora_desc = get_row_descriptor(curs, nom_tab_or_view)
 577
 578        row_cursor_cls = get_row_class_cursor(ora_desc, con_db)
 579
 580        class row_table(row_cursor_cls):
 581            """
 582            Clase para fila devuelta por SQL sobre tabla
 583            """
 584            nom_tab = nom_tab_or_view
 585
 586            def __repr__(self):
 587                """
 588                built_in que actua cuando se representa clase como STRING
 589
 590                Returns:
 591                    str
 592                """
 593                repr_txt = nom_tab_or_view + "({pk_vals})"
 594                pk_txt = []
 595                for i, v in self.pk_vals().items():
 596                    pk_txt.append(str(i) + "=" + str(v))
 597
 598                return repr_txt.format(pk_vals=",".join(pk_txt))
 599
 600            @staticmethod
 601            def pk():
 602                """
 603                Retorna lista con los nombres de campos clave
 604
 605                Returns:
 606                    list con nombres campos clave
 607                """
 608                return get_pk_tab(con_db, nom_tab_or_view)
 609
 610            def pk_vals(self):
 611                """
 612                Retorna OrderedDict con los pares nombres:valor para los campos clave
 613
 614                Returns:
 615                    OrderedDict
 616                """
 617                pk_vals = OrderedDict.fromkeys(self.pk())
 618                for k in self.pk():
 619                    pk_vals[k] = self.vals().get(k)
 620
 621                return pk_vals
 622
 623            @staticmethod
 624            def geoms():
 625                """
 626                Devuelve diccionario {nom_camp_geom:tip_geom} para todas las columnas de la tabla que son de
 627                tipo geométrico.
 628                Lo deduce de la definición de la tabla independientemente de si la columna está informada o no
 629
 630                Returns:
 631                    dict {nom_geom:tip_geom} (vease funcion clas_tip_geom())
 632                """
 633                return get_tips_geom_tab(con_db, nom_tab_or_view)
 634
 635            def geoms_vals(self):
 636                """
 637                Valores de los campos geometricos
 638
 639                Returns:
 640                    dict
 641                """
 642                vals = self.vals()
 643                return {nc: vals[nc] for nc in self.geoms()}
 644
 645            def as_xml(self, excluded_cols=[],
 646                       as_etree_elem=False):
 647                """
 648                Devuelve row como XML
 649
 650                Args:
 651                    excluded_cols: lista de nombre de columnas que se quieren excluir
 652                    as_etree_elem {bool} (default=False): Si True devuelve registro como objeto etree.Element. Si False (por defecto) como str en formato XML
 653
 654                Returns:
 655                    str (formato XML) OR etree.Element
 656                """
 657                return super(row_table, self).as_xml(nom_tab_or_view,
 658                                                     self.pk_vals(),
 659                                                     excluded_cols,
 660                                                     as_etree_elem)
 661
 662            @staticmethod
 663            def get_row_desc():
 664                return get_row_desc_tab(con_db, nom_tab_or_view)
 665
 666            @staticmethod
 667            def alfas(include_pk=True):
 668                """
 669                Devuelve diccionario {nom_camp:tip_camp} para las columnas alfanuméricas (NO son geométricas)
 670                Lo deduce de la definición de la tabla independientemente de si la columna está informada o no
 671
 672                Args:
 673                    include_pk: incluir primary key
 674
 675                Returns:
 676                    dict {nom_camp:tip_camp}
 677                """
 678                return {nc: tc
 679                        for nc, tc in get_row_desc_tab(con_db, nom_tab_or_view).vals().items()
 680                        if nc not in get_tips_geom_tab(con_db, nom_tab_or_view) and
 681                        (include_pk or nc not in get_pk_tab(con_db, nom_tab_or_view))}
 682
 683            def alfas_vals(self):
 684                """
 685                Devuelve diccionario con valores campos alfanuméricos
 686
 687                Returns:
 688                    dict {nom_camp:val_camp}
 689                """
 690                vals = self.vals()
 691                return {nc: vals[nc] for nc in self.alfas()}
 692
 693        row_class_tab = row_table
 694        __cache_row_class_tab[nom_row_class_tab_cache] = row_class_tab
 695
 696    return row_class_tab
 697
 698
 699def get_row_factory(curs, a_row_class=None):
 700    """
 701    Retorna funcion para crear instancia clase a partir de los valores de una fila
 702
 703    Args:
 704        curs:
 705        a_row_class:
 706
 707    Returns:
 708        function
 709    """
 710    if not a_row_class:
 711        cursor_desc = get_row_descriptor(curs)
 712        con_db = curs.connection
 713        a_row_class = get_row_class_cursor(cursor_desc, con_db)
 714
 715    def row_factory_func(*vals_camps):
 716        return a_row_class(*vals_camps)
 717
 718    return row_factory_func
 719
 720
 721def get_row_cursor(curs, rowfactory=None):
 722    """
 723    Retorna fila como clase que construye el rowfactory. Por defecto si no se informa esta clase heredará
 724    de 'namedtuple' con los campos como items más funcionalidad relacionada con la conexion y el descriptor de la fila
 725    con los tipos de campo para cada columna
 726
 727    Args:
 728        curs:
 729        rowfactory:
 730
 731    Returns:
 732
 733    """
 734    if not rowfactory and not curs.rowfactory:
 735        rowfactory = get_row_factory(curs)
 736
 737    if rowfactory:
 738        curs.rowfactory = rowfactory
 739
 740    row = curs.fetchone()
 741
 742    return row
 743
 744
 745def iter_execute_fetch_sql(con_db, sql_str, *args_sql, **extra_params):
 746    """
 747    Itera y devuelve cada fila devuelta para la consulta sql_str
 748
 749    Args:
 750        con_db:
 751        sql_str:
 752        *args_sql:
 753        **extra_params: { "row_class": clase que se utilizará para cada fila. Vease get_row_factory()
 754                          "as_format": formato en el que se devuelve cada fila. Las clases base row_cursor y row_table
 755                                    (vease get_row_class_cursor() y get_row_class_tab()) responden por defecto a
 756                                    "as_xml()" y "as_json()" }
 757
 758    Returns:
 759        row_class o string en formato especificado en **extra_params["as_format"]
 760    """
 761    curs = None
 762
 763    try:
 764        curs = new_cursor(con_db,
 765                          input_handler=extra_params.pop("input_handler",
 766                                                         m_sdo_geom.get_sdo_input_handler()),
 767                          output_handler=extra_params.pop("output_handler",
 768                                                          m_sdo_geom.get_output_handler(
 769                                                              con_db,
 770                                                              extra_params.pop("geom_format", None))))
 771
 772        curs.execute(sql_str,
 773                     args_sql)
 774
 775        row_class = extra_params.pop("row_class", None)
 776        row_factory = None
 777        if row_class:
 778            row_factory = get_row_factory(curs, row_class)
 779
 780        reg = get_row_cursor(curs, rowfactory=row_factory)
 781        while reg is not None:
 782            if "as_format" in extra_params:
 783                f_format = extra_params["as_format"]
 784                if f_format:
 785                    reg = getattr(reg, f_format)()
 786
 787            yield reg
 788
 789            reg = get_row_cursor(curs)
 790
 791    except:
 792        if curs is not None:
 793            curs.close()
 794        raise
 795
 796    if curs is not None:
 797        curs.close()
 798
 799
 800def execute_fetch_sql(con_db, sql_str, *args_sql, **extra_params):
 801    """
 802    Devuelve la primera iteración sobre la consulta sql_str
 803
 804    Args:
 805        con_db:
 806        sql_str:
 807        *args_sql:
 808        **extra_params: { "row_class": clase que se utilizará para cada fila. Vease get_row_factory()
 809                          "as_format": formato en el que se devuelve cada fila. Las clases base row_cursor y row_table
 810                                    (vease get_row_class_cursor() y get_row_class_tab()) responden por defecto a
 811                                    "as_xml()" y "as_json()" }
 812
 813    Returns:
 814        row_class o string en formato especificado en **extra_params["as_format"]
 815    """
 816    reg = None
 817    for row in iter_execute_fetch_sql(con_db,
 818                                      sql_str,
 819                                      *args_sql,
 820                                      input_handler=extra_params.pop("input_handler", None),
 821                                      output_handler=extra_params.pop("output_handler", None),
 822                                      row_class=extra_params.pop("row_class", None),
 823                                      **extra_params):
 824        reg = row
 825        break
 826
 827    return reg
 828
 829
 830def dict_as_sql_bind_and_params(dict_vals, bool_rel="and", filter_oper="="):
 831    """
 832    A partir de un dict de {nom_camps:value_camps} devuelve un string en forma
 833    de SQL FILTER bindind (camp_a = :1 and camp_b = :2) y la lista de params que se asignarán via binding
 834    El BOOL_REL podrá ser 'AND', 'OR' o ',' para el SET de los updates
 835    El FILTER_OPER podrá ser '=', '!=' o ':=' para el SET de los updates
 836
 837    Args:
 838        dict_vals:
 839        bool_rel:
 840        filter_oper:
 841
 842    Returns:
 843        str, [params*]
 844    """
 845    query_elems = {}
 846    for nom_camp, val_clau in dict_vals.items():
 847        sql_str = str(nom_camp) + " " + filter_oper + " :" + str(nom_camp)
 848        query_elems[sql_str] = val_clau
 849
 850    sql = (" " + bool_rel.strip().upper() + " ").join(query_elems.keys())
 851
 852    return sql, list(query_elems.values())
 853
 854
 855class SerializableGenerator(list):
 856    """Generator that is serializable by JSON
 857
 858    It is useful for serializing huge data by JSON
 859    It can be used in a generator of json chunks used e.g. for a stream
 860    ('[1', ']')
 861    # >>> for chunk in iter_json:
 862    # ...     stream.write(chunk)
 863    # >>> SerializableGenerator((x for x in range(3)))
 864    # [<generator object <genexpr> at 0x7f858b5180f8>]
 865    """
 866
 867    def __init__(self, iterable):
 868        super().__init__()
 869        tmp_body = iter(iterable)
 870        try:
 871            self._head = iter([next(tmp_body)])
 872            self.append(tmp_body)
 873        except StopIteration:
 874            self._head = []
 875
 876    def __iter__(self):
 877        return itertools.chain(self._head, *self[:1])
 878
 879
 880def geojson_from_gen_ora_sql(generator_sql, as_string=False):
 881    """
 882    Devuelve diccionario geojson para un generator de rows de Oracle
 883
 884    Args:
 885        generator_sql (function generator):
 886        as_string (bool): (opcional) indica si se querrá el geojson como un string
 887
 888    Returns:
 889        geojson (dict ó str)
 890    """
 891    vals = {"type": "FeatureCollection",
 892            "features": [getattr(r, "__geo_interface__")
 893                         for r in generator_sql
 894                         if getattr(r, "__geo_interface__")]}
 895
 896    ret = vals
 897    if as_string:
 898        ret = json.dumps(vals,
 899                         ensure_ascii=False)
 900
 901    return ret
 902
 903
 904def vector_file_from_gen_ora_sql(file_path, vector_format, func_gen, zipped=False, indent_json=None, cols_csv=None,
 905                                 tip_cols_csv=None):
 906    """
 907    A partir del resultado de una query SQL devuelve un file_object formateado segun formato
 908
 909    Args:
 910        file_path (str): path del fichero a grabar
 911        vector_format (str): tipo formato (CSV, JSON, GEOJSON)
 912        func_gen (generator function): funcion que devuelva filas sql en forma de row_cursor o row_table
 913                                      (vease funciones generator_rows_sql() o generator_rows_table())
 914        zipped (bool=False): Devuelve fichero en un fichero comprimido (.zip)
 915        indent_json (int):
 916        cols_csv (list): Lista de columnes del CSV
 917        tip_cols_csv (list): Lista de tipos de columnas para CSV.
 918                             Revisar especificacion en https://giswiki.hsr.ch/GeoCSV
 919    Returns:
 920        str: pathfile del fichero generado
 921    """
 922    vector_format = vector_format.lower()
 923    newline = None if vector_format != "csv" else ""
 924    file_path_csvt = None
 925    str_csvt = None
 926    dir_base = os.path.dirname(file_path)
 927    if dir_base:
 928        os.makedirs(dir_base, exist_ok=True)
 929
 930    # Se hace en memoria o recursos locales (SpooledTemporaryFile) para evitar trabajar lo minimo en la red
 931    # si se da el caso en el path fichero del fichero indicado. Cuando se quiere grabar en recurso local el tiempo
 932    # perdido por utilizar un fichero temporal debería ser despreciable comparado con la complejidad añadida al código
 933    # para decidir si usar o no el SpooledTemporaryFile
 934    with SpooledTemporaryFile(mode="w+", encoding="utf-8", newline=newline) as temp_file:
 935        if vector_format == "geojson":
 936            json.dump(geojson_from_gen_ora_sql(func_gen),
 937                      temp_file,
 938                      ensure_ascii=False,
 939                      indent=indent_json,
 940                      cls=geojson_encoder)
 941        elif vector_format == "json":
 942            json.dump(SerializableGenerator((r.vals() for r in func_gen)),
 943                      temp_file,
 944                      ensure_ascii=False,
 945                      indent=indent_json,
 946                      cls=geojson_encoder)
 947        elif vector_format == "csv":
 948            writer = csv.DictWriter(temp_file, fieldnames=cols_csv)
 949            writer.writeheader()
 950
 951            for r in func_gen:
 952                writer.writerow(r.vals())
 953
 954        temp_file.seek(0)
 955        if tip_cols_csv:
 956            file_path_csvt = ".".join((os.path.splitext(file_path)[0], "csvt"))
 957            str_csvt = ",".join(tip_cols_csv)
 958
 959        if zipped:
 960            file_path_res = "{}.zip".format(os.path.splitext(file_path)[0])
 961            with SpooledTemporaryFile() as zip_temp_file:
 962                with ZipFile(zip_temp_file, "w", compression=ZIP_DEFLATED, allowZip64=True) as my_temp_zip:
 963                    my_temp_zip.writestr(zinfo_or_arcname=os.path.basename(file_path), data=temp_file.read())
 964                    if str_csvt:
 965                        my_temp_zip.writestr(zinfo_or_arcname=os.path.basename(file_path_csvt), data=str_csvt)
 966
 967                zip_temp_file.seek(0)
 968                with open(file_path_res, mode="wb") as file_res:
 969                    shutil.copyfileobj(zip_temp_file, file_res)
 970        else:
 971            file_path_res = file_path
 972            with open(file_path_res, mode="w", encoding="utf-8") as file_res:
 973                shutil.copyfileobj(temp_file, file_res)
 974            if str_csvt:
 975                with open(file_path_csvt, mode="w") as csvt_file:
 976                    csvt_file.write(str_csvt)
 977
 978    return file_path_res
 979
 980
 981class geojson_encoder(json.JSONEncoder):
 982    """
 983    Class Encoder to parser SDO_GEOM to GEOJSON
 984    """
 985    __num_decs__ = 9
 986
 987    def default(self, obj_val):
 988        """
 989        Redefine default para tratar las geometrias SDO_GEOM y convertirlas a geojson y las fechas a iso_format
 990        Args:
 991            obj_val: valor
 992
 993        Returns:
 994            object encoded
 995        """
 996        if isinstance(obj_val, m_sdo_geom.sdo_geom):
 997            return obj_val.as_geojson()
 998        elif isinstance(obj_val, (datetime.datetime, datetime.date)):
 999            return obj_val.isoformat()
1000        elif isinstance(obj_val, cx_Oracle.LOB):
1001            return obj_val.read()
1002        else:
1003            return json.JSONEncoder.default(self, obj_val)
1004
1005
1006def print_to_log_exception(a_type_exc=Exception, lanzar_exc=False):
1007    """
1008    Decorator para imprimir en el log una excepción capturada por un metodo de la clase
1009
1010    Returns:
1011        function
1012    """
1013
1014    def decor_print_log_exception(func):
1015        @wraps(func)
1016        def meth_wrapper(cls, *args, **kwargs):
1017            try:
1018                return func(cls, *args, **kwargs)
1019            except a_type_exc:
1020                error_type, error_instance, traceback = sys.exc_info()
1021
1022                error_msg = "Error al executar funció '{clas}.{func}()' \n" \
1023                            "Arguments: {args}\n" \
1024                            "Fitxer:    {file}".format(
1025                    file=inspect.getmodule(func).__file__,
1026                    clas=cls.__class__.__name__,
1027                    func=func.__name__,
1028                    args=", ".join(["'{}'".format(arg) for arg in args] +
1029                                   ["'{}={}'".format(a, b) for a, b in kwargs.items()]))
1030
1031                if hasattr(error_instance, "output"):
1032                    error_msg += "\n" \
1033                                 "Output: {}".format(error_instance.output)
1034
1035                cls.print_log_exception(error_msg)
1036                if lanzar_exc:
1037                    raise error_instance
1038
1039        return meth_wrapper
1040
1041    return decor_print_log_exception
1042
1043
1044class gestor_oracle(object):
1045    """
1046    Clase que gestionará distintas conexiones a Oracle y facilitará operaciones sobre la BBDD
1047    """
1048    tip_number = cx_Oracle.NUMBER
1049    tip_string = cx_Oracle.STRING
1050    tip_clob = cx_Oracle.CLOB
1051    tip_blob = cx_Oracle.BLOB
1052    tip_date = cx_Oracle.DATETIME
1053    tip_fix_char = cx_Oracle.FIXED_CHAR
1054
1055    __slots__ = 'nom_con_db', '__con_db__', '__user_con_db__', \
1056        '__psw_con_db__', '__dsn_ora__', '__call_timeout__', '__schema_con_db__', 'logger'
1057
1058    def __init__(self, user_ora, psw_ora, dsn_ora, a_logger=None, call_timeout: int = None, schema_ora=None):
1059        """
1060        Inicializa gestor de Oracle para una conexion cx_Oracle a Oracle
1061        Se puede pasar por parametro un logger o inicializar por defecto
1062
1063        Args:
1064            user_ora {str}: Usuario/schema Oracle
1065            psw_ora {str}: Password usuario
1066            dsn_ora {str}: DSN Oracle (Nombre instancia/datasource de Oracle
1067                    según TSN o string tal cual devuelve cx_Oracle.makedsn())
1068            call_timeout (int=None): miliseconds espera per transaccio
1069            a_logger:
1070            schema_ora(str=None): indicate scheme when it is different from the user, default schema = user
1071        """
1072        self.__con_db__ = None
1073        self.__call_timeout__ = call_timeout
1074        self.logger = a_logger
1075        self.__set_logger()
1076        self.__set_conexion(user_ora, psw_ora, dsn_ora, schema_ora=schema_ora)
1077
1078    def __del__(self):
1079        """
1080        Cierra la conexion al matar la instancia
1081        """
1082        try:
1083            if hasattr(self, "__con_db__"):
1084                self.__con_db__.close()
1085        except:
1086            pass
1087
1088    def __repr__(self):
1089        """
1090        built_in que actua cuando se representa clase como STRING
1091
1092        Returns:
1093            str
1094        """
1095        repr_txt = "{}".format(self.nom_con_db)
1096
1097        return repr_txt
1098
1099    @staticmethod
1100    def log_dir():
1101        """
1102        Devuelve el directorio donde irán los logs indicado en la funcion apb_logging.logs_dir()
1103
1104        Returns:
1105            {str} - path del directorio de logs
1106        """
1107        return utils_logging.logs_dir(True)
1108
1109    def log_name(self):
1110        """
1111        Devuelve el nombre del fichero de log por defecto
1112
1113        Returns:
1114            {str} - Nombre fichero log por defecto
1115        """
1116        return self.__class__.__name__
1117
1118    def log_file_name(self):
1119        return "{}.(LOG_LEVEL).log".format(os.path.join(self.log_dir(), self.log_name()))
1120
1121    def __set_logger(self):
1122        """
1123        Asigna el LOGGER po defecto si este no se ha informado al inicializar el gestor
1124
1125        Returns:
1126        """
1127        if self.logger is None:
1128            self.logger = utils_logging.get_file_logger(self.log_name(), dir_log=self.log_dir())
1129
1130    def path_logs(self, if_exist=True):
1131        """
1132        Devuelve lista paths base de los logs vinculados al gestor
1133
1134        Args:
1135            if_exist (bool): Devuelve los paths si el fichero existe
1136
1137        Returns:
1138            list:
1139        """
1140        return logger_path_logs(self.logger)
1141
1142    def print_log(self, msg):
1143        """
1144        Sobre el logger escribe mensaje de info
1145
1146        Args:
1147            msg {str}: String con el mensaje
1148        """
1149        self.logger.info(msg)
1150
1151    def print_log_error(self, msg):
1152        """
1153        Sobre el logger escribe mensaje de error
1154
1155        Args:
1156            msg {str}: String con el mensaje
1157        """
1158        self.logger.error(msg)
1159
1160    def print_log_exception(self, msg):
1161        """
1162        Sobre el logger escribe excepcion
1163
1164        Args:
1165            msg {str}: String con el mensaje
1166        """
1167        self.logger.exception(msg)
1168
1169    @print_to_log_exception(lanzar_exc=True)
1170    def __set_conexion(self, user_ora, psw_ora, dsn_ora, schema_ora=None):
1171        """
1172        Añade conexion Oracle al gestor a partir de nombre de usuario/schema (user_ora), contraseña (psw_ora) y
1173        nombre datasource de la bbdd según tns_names (ds_ora).
1174
1175        La conexión quedará registrada como 'user_ora@ds_ora'
1176
1177        Args:
1178            user_ora {str}: Usuario/schema Oracle
1179            psw_ora {str}: Password usuario
1180            dsn_ora {str}: DSN Oracle (Nombre instancia/datasource de Oracle según TSN o string tal cual devuelve cx_Oracle.makedsn())
1181            schema_ora(str=None): indicate scheme when it is different from the user, default schema = user
1182
1183        """
1184        nom_con = "@".join((user_ora.upper(), dsn_ora.upper()))
1185        self.nom_con_db = nom_con
1186        self.__user_con_db__ = user_ora
1187        self.__psw_con_db__ = psw_ora
1188        self.__schema_con_db__ = schema_ora
1189        self.__dsn_ora__ = dsn_ora
1190        self.__con_db__ = get_oracle_connection(user_ora, psw_ora, dsn_ora, self.__call_timeout__, schema_ora=schema_ora)
1191
1192    @property
1193    @print_to_log_exception(cx_Oracle.Error, lanzar_exc=True)
1194    def con_db(self):
1195        """
1196        Return a cx_Oracle Conection live
1197        
1198        Returns:
1199            cx_Oracle.Connection
1200        """
1201        reconnect = False
1202        if (con_ora := self.__con_db__) is not None:
1203            try:
1204                con_ora.ping()
1205            except cx_Oracle.Error as exc:
1206                # Borramos las entradas de cache asociadas a la conexión que no responde
1207                del_cache_rel_con_db(get_nom_conexion(con_ora))
1208                try:
1209                    con_ora.close()
1210                except cx_Oracle.Error:
1211                    pass
1212                self.__con_db__ = con_ora = None
1213
1214        if con_ora is None:
1215            self.__set_conexion(
1216                self.__user_con_db__,
1217                self.__psw_con_db__,
1218                self.__dsn_ora__, schema_ora=self.__schema_con_db__)
1219
1220            con_ora = self.__con_db__
1221
1222        return con_ora
1223
1224    @print_to_log_exception(cx_Oracle.DatabaseError)
1225    def exec_trans_db(self, sql_str, *args_sql, **types_sql_args):
1226        """
1227        Ejecuta transaccion SQL
1228
1229        Args:
1230            sql_str (str): sql transaction (update, insert, delete}
1231            *args_sql: Lista argumentos a pasar
1232            **types_sql_args (OPCIONAL): Lista tipos cx_Oracle para cada argumento
1233
1234        Returns:
1235            ok {bool}: Si ha ido bien True si no False
1236        """
1237        curs_db = None
1238        try:
1239            curs_db = new_cursor(self.con_db,
1240                                 input_handler=m_sdo_geom.get_sdo_input_handler())
1241
1242            curs_db.setinputsizes(*types_sql_args.values())
1243
1244            curs_db.execute(sql_str,
1245                            args_sql)
1246        finally:
1247            if curs_db:
1248                curs_db.close()
1249
1250        return True
1251
1252    @print_to_log_exception(cx_Oracle.DatabaseError)
1253    def exec_script_plsql(self, sql_str):
1254        """
1255        Ejecuta script SQL
1256
1257        Args:
1258            sql_str {str}: sql script
1259
1260        Returns:
1261            ok {bool}: Si ha ido bien True si no False
1262        """
1263        curs_db = None
1264        try:
1265            curs_db = new_cursor(self.con_db)
1266            curs_db.execute(sql_str)
1267        finally:
1268            if curs_db:
1269                curs_db.close()
1270
1271        return True
1272
1273    @print_to_log_exception(cx_Oracle.DatabaseError)
1274    def callfunc_sql(self, nom_func, ret_cx_ora_tipo, *args_func):
1275        """
1276        Ejecuta funcion PL/SQL y retorna el valor
1277
1278        Args:
1279            nom_func (str): Nombre de la funcion PL/SQL
1280            ret_cx_ora_tipo (cx_Oracle TIPO): El retorno de la función en cx_Oracle (cx_Oracle.NUMBER,
1281                                            cx_Oracle.STRING,...)
1282            *args_func: Argumentos de la funcion PL/SQL
1283
1284        Returns:
1285            Valor retornado por la función PL/SQL
1286        """
1287        curs = None
1288        try:
1289            curs = new_cursor(self.con_db)
1290            ret = curs.callfunc(nom_func,
1291                                ret_cx_ora_tipo,
1292                                args_func)
1293        finally:
1294            if curs:
1295                curs.close()
1296
1297        return ret
1298
1299    @print_to_log_exception(cx_Oracle.DatabaseError)
1300    def callproc_sql(self, nom_proc, *args_proc):
1301        """
1302        Ejecuta procedimiento PL/SQL
1303
1304        Args:
1305            nom_proc (str): Nombre del procedimiento PL/SQL
1306            *args_proc: Argumentos del procedimiento
1307
1308        Returns:
1309            ok {bool}: Si ha ido bien True si no False
1310        """
1311        curs = None
1312        try:
1313            curs = new_cursor(self.con_db)
1314            curs.callproc(nom_proc,
1315                          args_proc)
1316        finally:
1317            if curs:
1318                curs.close()
1319
1320        return True
1321
1322    @print_to_log_exception(cx_Oracle.DatabaseError)
1323    def row_sql(self, sql_str, *args_sql, **extra_params):
1324        """
1325        Retorna la fila resultante de la query sql SQL_STR con los parámetros *ARGS_SQL.
1326        Se puede informar **EXTRA_PARAMS con algunos de los siguientes parámetros:
1327            INPUT_HANDLER funcion que tratará los bindings de manera específica
1328            OUTPUT_HANLER funcion que tratará los valores de las columnas de modo específico
1329            ROW_CLASS define que con que clase se devolverá cada fila. Por defecto la que devuleve la funcion
1330                      'apb_cx_oracle_spatial.get_row_class_cursor()'
1331            AS_FORMAT (as_xml, as_json, as_geojson) devuelve fila en el formato especificado. La row_class deberá
1332                        responder a esas funciones
1333
1334        Args:
1335            sql_str:
1336            *args_sql:
1337            **extra_params:
1338
1339        Returns:
1340            object (instancia que debería ser o heredar de las clases row_cursor o row_table)
1341        """
1342        return execute_fetch_sql(self.con_db,
1343                                 sql_str,
1344                                 *args_sql,
1345                                 input_handler=extra_params.pop("input_handler", None),
1346                                 output_handler=extra_params.pop(
1347                                     "output_handler",
1348                                     m_sdo_geom.get_output_handler(self.con_db,
1349                                                                   extra_params.pop("geom_format", None))),
1350                                 row_class=extra_params.pop("row_class", None),
1351                                 **extra_params)
1352
1353    @print_to_log_exception(cx_Oracle.DatabaseError)
1354    def generator_rows_sql(self, sql_str, *args_sql, **extra_params):
1355        """
1356        Ejecuta consulta SQL de forma iterativa retornando cada fila como un objeto row_class (por defecto row_cursor)
1357
1358        Se puede informar **EXTRA_PARAMS con algunos de los siguientes parámetros:
1359            INPUT_HANDLER funcion que tratará los bindings de manera específica
1360            OUTPUT_HANLER funcion que tratará los valores de las columnas de modo específico
1361            ROW_CLASS define que con que clase se devolverá cada fila. Por defecto la que devuleve la funcion
1362                      'apb_cx_oracle_spatial.get_row_class_cursor()'
1363            AS_FORMAT (as_xml, as_json, as_geojson) devuelve fila en el formato especificado. La row_class deberá
1364                        responder a esas funciones
1365
1366        Args:
1367            sql_str:
1368            *args_sql:
1369            **extra_params:
1370
1371        Returns:
1372            object (instancia que debería ser o heredar de las clases row_cursor o row_table)
1373        """
1374        for reg in iter_execute_fetch_sql(self.con_db,
1375                                          sql_str,
1376                                          *args_sql,
1377                                          input_handler=extra_params.pop("input_handler", None),
1378                                          output_handler=extra_params.pop(
1379                                              "output_handler",
1380                                              m_sdo_geom.get_output_handler(self.con_db,
1381                                                                            extra_params.pop("geom_format", None))),
1382                                          row_class=extra_params.pop("row_class", None),
1383                                          **extra_params):
1384            yield reg
1385
1386    def rows_sql(self, sql_str, *args_sql):
1387        """
1388        Vease funcion 'generator_rows_sql()'
1389        Args:
1390            sql_str:
1391            *args_sql:
1392
1393        Returns:
1394            list
1395        """
1396        return list(self.generator_rows_sql(sql_str, *args_sql))
1397
1398    def get_primary_key_table(self, nom_tab_or_view):
1399        """
1400        Retorna lista con las columnas que conforman la primary key de una tabla/vista
1401        Args:
1402            nom_tab_or_view:
1403
1404        Returns:
1405            list con campos clave
1406        """
1407        return get_pk_tab(self.con_db, nom_tab_or_view)
1408
1409    @print_to_log_exception(lanzar_exc=True)
1410    def get_dd_table(self, nom_tab_or_view):
1411        """
1412        Retorna instancia row_table con los tipos para cada columna
1413        Args:
1414            nom_tab_or_view:
1415
1416        Returns:
1417            object de clase row_table con los tipos de cada columna como valores
1418        """
1419        return get_row_desc_tab(self.con_db, nom_tab_or_view)
1420
1421    def generator_rows_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql, **extra_params):
1422        """
1423        Retorna los registros que cumplan con el FILTER_SQL sobre la tabla o vista indicada.
1424
1425        La tabla se puede referenciar en el filtro con el alias 'TAB'
1426
1427        Si el FILTER_SQL utiliza valores por binding (:1, :2,...) estos se indicaran por orden en ARGS_FILTER_SQL
1428
1429        Args:
1430            nom_tab_or_view: Nombre de la tabla o vista sobre la que se hará la consulta
1431            filter_sql: Filtre sobre la taula
1432            args_filter_sql: Valors en ordre de binding per passar
1433            extra_params: Vease generator_rows_sql()
1434
1435        Yields:
1436             row_table: regs. clase row_table (mirar get_row_class_tab())
1437        """
1438        for reg in self.generator_rows_sql(sql_tab(nom_tab_or_view,
1439                                                   filter_sql),
1440                                           *args_filter_sql,
1441                                           row_class=extra_params.pop(
1442                                               "row_class",
1443                                               get_row_class_tab(self.con_db, nom_tab_or_view)),
1444                                           **extra_params):
1445            yield reg
1446
1447    def generator_rows_interact_geom(self, nom_tab, a_sdo_geom, cols_geom=None, geom_format=None):
1448        """
1449        Retorna las filas de una tabla que interactuan con una geometria
1450
1451        Args:
1452            nom_tab: nombre de la tabla
1453            a_sdo_geom: geometria clase sdo_geom
1454            cols_geom (default=None): Lista con nombre de columnas geométricas sobre las que se quiere aplicar filtro
1455            geom_format:
1456        Yields:
1457            row_table: regs. clase row_table (mirar get_row_class_tab())
1458        """
1459        # Uso de " <>'FALSE'" por fallo de Oracle usando "= 'TRUE'"
1460        filter_interact_base = "SDO_ANYINTERACT({camp_geom}, :1) <> 'FALSE'"
1461
1462        if not cols_geom:
1463            cols_geom = get_tips_geom_tab(self.con_db, nom_tab).keys()
1464
1465        if cols_geom:
1466            filter_sql = " OR ".join([filter_interact_base.format(camp_geom=ng) for ng in cols_geom])
1467            for reg in self.generator_rows_table(nom_tab, filter_sql, a_sdo_geom.as_ora_sdo_geometry(),
1468                                                 geom_format=geom_format):
1469                yield reg
1470
1471    def rows_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql):
1472        """
1473        Vease funcion 'generator_rows_table()'
1474
1475        Args:
1476            nom_tab_or_view:
1477            filter_sql:
1478            *args_filter_sql:
1479
1480        Returns:
1481            dict
1482        """
1483        gen_tab = self.generator_rows_table(nom_tab_or_view,
1484                                            filter_sql,
1485                                            *args_filter_sql)
1486        pk_tab = self.get_primary_key_table(nom_tab_or_view)
1487        l_pk = len(pk_tab)
1488        if l_pk == 0:
1489            return [r for r in gen_tab]
1490        else:
1491            def f_key(r):
1492                return getattr(r, pk_tab[0])
1493
1494            if l_pk > 1:
1495                def f_key(r): return tuple(map(lambda nf: getattr(r, nf), pk_tab))
1496            return {f_key(r): r for r in gen_tab}
1497
1498    def row_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql, **extra_params):
1499        """
1500        Retorna primer registro para el FILTER_SQL sobre la tabla o vista indicada
1501        Si el FILTER_SQL utiliza valores por binding (:1, :2,...) estos se indicaran por orden en ARGS_FILTER_SQL
1502
1503        Args:
1504            nom_tab_or_view:
1505            filter_sql:
1506            *args_filter_sql:
1507            **extra_params:
1508
1509        Returns:
1510            object de la clase row_table o especificada en **extra_params['row_class']
1511        """
1512        gen = self.generator_rows_table(nom_tab_or_view,
1513                                        filter_sql,
1514                                        *args_filter_sql,
1515                                        **extra_params)
1516        return next(gen, None)
1517
1518    def row_table_at(self, nom_tab_or_view, *vals_key):
1519        """
1520        Devuelve row_tabla_class para el registro que de la tabla_vista que cumpla con la clave
1521
1522        Args:
1523            nom_tab_or_view:
1524            *vals_key:
1525
1526        Returns:
1527            object de la clase row_table o especificada en **extra_params['row_class']
1528        """
1529        return self.exist_row_tab(nom_tab_or_view,
1530                                  {nc: val for nc, val in zip(self.get_primary_key_table(nom_tab_or_view),
1531                                                              vals_key)})
1532
1533    def test_row_table(self, row_tab, a_sql, *args_sql):
1534        """
1535        Testea un registro de tabla (clase rwo_table) cumpla con sql indicado
1536
1537        Args:
1538            row_tab: registro de tabla en forma de clase row_table
1539            a_sql: string con sql a testear
1540
1541        Returns:
1542            bool: True o False según cumpla con el SQL indicado
1543        """
1544        sql_pk = dict_as_sql_bind_and_params(row_tab.pk_vals())
1545        query_sql = "{} AND ({})".format(sql_pk[0], a_sql)
1546
1547        ret = False
1548        if self.row_table(row_tab.nom_tab, query_sql, *(tuple(sql_pk[1]) + tuple(args_sql))):
1549            ret = True
1550
1551        return ret
1552
1553    def insert_row_tab(self, nom_tab, dict_vals_param=None, dict_vals_str=None, pasar_nulls=False):
1554        """
1555        Inserta registro en la tabla indicada. Los valores para cada columna se pasarán a través de dict_vals_param
1556        como bindings o a través de dict_vals_str directemente en el string del sql ejecutado
1557        Args:
1558            nom_tab: nombre de la tabla
1559            dict_vals_param: diccionario indexado por columnas-valores. Los valores se pasarán como bindings
1560            dict_vals_str: diccionario indexado por columnas-valores a asignar como strings. El valor se pasa
1561                        directamente como asignacion en la senetencia sql
1562            pasar_nulls: (opcional) por defecto False. Indica si los valores NULL se asignarán o no
1563
1564        Returns:
1565            row_table (si genera el registro) o False si va mal la operación
1566        """
1567        if not dict_vals_param:
1568            dict_vals_param = {}
1569        if not dict_vals_str:
1570            dict_vals_str = {}
1571
1572        ora_dict_vals_param = self.get_vals_tab_for_transdb(nom_tab, dict_vals_param, pasar_nulls=pasar_nulls)
1573
1574        if pasar_nulls:
1575            # Se revisa que en un campo geometrico se asigne None y por lo tanto se asigne valor via STR
1576            geoms_null = [ng for ng, val in ora_dict_vals_param.items()
1577                          if not val and self.get_tip_camp_geom(nom_tab, ng)]
1578            if geoms_null:
1579                keys_str = [nc.upper() for nc in dict_vals_str.keys()]
1580                for gn in geoms_null:
1581                    ora_dict_vals_param.pop(gn)
1582                    if gn.upper() not in keys_str:
1583                        dict_vals_str[gn.upper()] = "NULL"
1584
1585        params = []
1586        nom_camps = []
1587        vals_camps = []
1588        for nom_camp, val_camp in ora_dict_vals_param.items():
1589            if val_camp is None:
1590                continue
1591            nom_camps.append(nom_camp)
1592            vals_camps.append(":" + nom_camp)
1593            params.append(val_camp)
1594
1595        for nom_camp, val_camp_str in dict_vals_str.items():
1596            if val_camp_str is None:
1597                continue
1598            nom_camps.append(nom_camp)
1599            vals_camps.append(str(val_camp_str))
1600
1601        row_desc_tab = get_row_desc_tab(self.con_db, nom_tab)
1602        pk_binds = {k: new_cursor(self.con_db).var(ora_tip_camp) for k, ora_tip_camp in row_desc_tab.pk_vals().items()}
1603        if not pk_binds:
1604            pk_binds = {'ROWID': new_cursor(self.con_db).var(cx_Oracle.ROWID)}
1605        str_pk_camps = ",".join(pk_binds.keys())
1606        str_pk_binds = ",".join(list(map(lambda x: ":ret_" + str(x), pk_binds.keys())))
1607        params += list(pk_binds.values())
1608
1609        a_sql_res = f"insert into {nom_tab}({','.join(nom_camps)}) values({','.join(vals_camps)}) " \
1610                    f"returning {str_pk_camps} into {str_pk_binds}"
1611
1612        ok = self.exec_trans_db(a_sql_res, *params)
1613        if ok:
1614            pk_vals = {k: curs_var.getvalue(0)[0] for k, curs_var in pk_binds.items()}
1615            return self.exist_row_tab(nom_tab, pk_vals)
1616
1617        return ok
1618
1619    def update_row_tab(self, nom_tab, dict_clau_reg, dict_vals_param=None, dict_vals_str=None, pasar_nulls=None):
1620        """
1621        Actualiza registro en la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}
1622        Los valores para cada columna se pasarán a través de dict_vals_param como bindings o a través de
1623        dict_vals_str directemente en el string del sql ejecutado
1624
1625        Args:
1626            nom_tab: nombre de la tabla
1627            dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
1628            dict_vals_param: diccionario indexado por columnas-valores. Los valores se pasarán como bindings
1629            dict_vals_str: diccionario indexado por columnas-valores a asignar como strings. El valor se pasa
1630                        directamente como asignacion en la senetencia sql
1631            pasar_nulls: (opcional) por defecto False. Indica si los valores NULL se asignarán o no
1632
1633        Returns:
1634            row_table (si genera el registro) o False si va mal la operación
1635        """
1636        if not dict_vals_param:
1637            dict_vals_param = {}
1638        if not dict_vals_str:
1639            dict_vals_str = {}
1640
1641        ora_dict_clau_reg = self.get_vals_tab_for_transdb(nom_tab, dict_clau_reg)
1642        ora_dict_vals_param = self.get_vals_tab_for_transdb(nom_tab, dict_vals_param, pasar_nulls=pasar_nulls)
1643
1644        if pasar_nulls:
1645            # Se revisa que en un campo geometrico se asigne None y por lo tanto se asigne valor via STR
1646            geoms_null = [ng for ng, val in ora_dict_vals_param.items()
1647                          if not val and self.get_tip_camp_geom(nom_tab, ng)]
1648            if geoms_null:
1649                keys_str = [nc.upper() for nc in dict_vals_str.keys()]
1650                for gn in geoms_null:
1651                    ora_dict_vals_param.pop(gn)
1652                    if gn.upper() not in keys_str:
1653                        dict_vals_str[gn.upper()] = "NULL"
1654
1655        (sql_set_camps, params_set_camps) = dict_as_sql_bind_and_params(ora_dict_vals_param,
1656                                                                        ",", "=")
1657
1658        (query_clau, params_filter) = dict_as_sql_bind_and_params(ora_dict_clau_reg)
1659
1660        params = params_set_camps + params_filter
1661
1662        for nom_camp, val_camp_str in dict_vals_str.items():
1663            if sql_set_camps:
1664                sql_set_camps += " , "
1665            sql_set_camps += nom_camp + "=" + val_camp_str
1666
1667        ok = None
1668        if sql_set_camps:
1669            a_sql_res = f"update {nom_tab} set {sql_set_camps} where {query_clau}"
1670
1671            ok = self.exec_trans_db(a_sql_res, *params)
1672
1673            if ok:
1674                return self.exist_row_tab(nom_tab, dict_clau_reg)
1675
1676        return ok
1677
1678    def remove_row_tab(self, nom_tab, dict_clau_reg):
1679        """
1680        Borra registro en la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}
1681
1682        Args:
1683            nom_tab: nombre de la tabla
1684            dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
1685
1686        Returns:
1687            bool según vaya la operación
1688        """
1689        ora_dict_clau_reg = self.get_vals_tab_for_transdb(nom_tab, dict_clau_reg)
1690
1691        a_sql_tmpl = "delete {nom_tab} where {query_clau}"
1692
1693        (sql_filter, params) = dict_as_sql_bind_and_params(ora_dict_clau_reg)
1694
1695        a_sql_res = a_sql_tmpl.format(nom_tab=nom_tab,
1696                                      query_clau=sql_filter)
1697
1698        return self.exec_trans_db(a_sql_res, *params)
1699
1700    def exist_row_tab(self, nom_tab, dict_clau_reg, **extra_params):
1701        """
1702        Devuelve registro de la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}
1703
1704        Args:
1705            nom_tab: nombre de la tabla
1706            dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
1707            **extra_params:
1708
1709        Returns:
1710            object de la clase row_table o especificada en **extra_params['row_class']
1711        """
1712        ora_dict_clau_reg = self.get_vals_tab_for_transdb(nom_tab, dict_clau_reg)
1713
1714        (filter_sql, params) = dict_as_sql_bind_and_params(ora_dict_clau_reg, "and", "=")
1715
1716        return self.row_table(nom_tab, filter_sql, *params, **extra_params)
1717
1718    def get_vals_tab_for_transdb(self, nom_tab, dict_camps_vals, pasar_nulls=True):
1719        """
1720        Para un tabla y diccionario columnas-valores devuelve diccionario indexado por las columnas con los valores
1721        convertidos a formato cx_Oracle según tipo de cada columna en Oracle
1722        Args:
1723            nom_tab: nombre de la tabla
1724            dict_camps_vals: diccionario indexado por columnas-valor a convertir a formato cx_Oracle
1725            pasar_nulls: (opcional) por defecto convertirá los None a NULL de Oracle
1726
1727        Returns:
1728            dict indexado por columnas con los valores convertidos a tipo cx_Oracle
1729        """
1730        dd_tab = get_row_desc_tab(self.con_db, nom_tab)
1731
1732        # Retorna dict con los campos a pasar por parametro
1733        d_params = {}
1734
1735        # Los nombres de campo siempre se buscarán en mayúsculas
1736        dict_camps_vals = {k.upper(): v for k, v in dict_camps_vals.items()}
1737
1738        for camp, tip_camp in dd_tab.vals().items():
1739            if camp not in dict_camps_vals:
1740                continue
1741
1742            val_camp = dict_camps_vals.get(camp)
1743            if not pasar_nulls and val_camp is None:
1744                continue
1745
1746            if isinstance(val_camp, m_sdo_geom.sdo_geom):
1747                var = val_camp.as_ora_sdo_geometry()
1748            else:
1749                try:
1750                    var = new_cursor(self.con_db).var(tip_camp)
1751                    var.setvalue(0, val_camp)
1752                except:
1753                    var = val_camp
1754
1755            d_params[camp] = var
1756
1757        return d_params
1758
1759    @print_to_log_exception()
1760    def run_sql_script(self, filename):
1761        """
1762        Ejecuta slq script (filename) sobre SQLPLUS
1763
1764        Args:
1765            filename: path del sql script
1766
1767        Returns:
1768
1769        """
1770        user_ora = self.con_db.username
1771        ds_ora = self.con_db.dsn
1772        nom_con = self.nom_con_db
1773        psw_ora = self.__psw_con_db__
1774        if psw_ora is None:
1775            print("ERROR - Conexión '" + nom_con + "' no está añadida al gestor!!")
1776            return
1777
1778        with open(filename, 'rb') as a_file:
1779            a_sql_command = a_file.read()
1780
1781        con_db_str = user_ora + "/" + psw_ora + "@" + ds_ora
1782        sqlplus = Popen(['sqlplus', '-S', con_db_str], stdin=PIPE, stdout=PIPE, stderr=PIPE)
1783        # sqlplus.stdin.write(a_sql_command)
1784
1785        (stdout, stderr) = sqlplus.communicate(a_sql_command)
1786
1787        self.print_log("Resultado lanzar script '{}': \n"
1788                       "{}".format(filename,
1789                                   stdout.decode("utf-8")))
1790
1791        if sqlplus is not None:
1792            sqlplus.terminate()
1793
1794    @staticmethod
1795    def get_nom_obj_sql(nom_base, prefix="", sufix=""):
1796        """
1797        Retorna nombre propuesto con prefijo/sufijos formateado para que cumpla longitud máxima de 32 caracteres en
1798        objetos sql Oracle
1799
1800        Args:
1801            nom_base: nombre propuesto
1802            prefix: (opc) prefijo
1803            sufix: (opc) sufijo
1804
1805        Returns:
1806            str formateado
1807        """
1808        return x_sql_parser.get_nom_obj_sql(nom_base, prefix, sufix)
1809
1810    def iter_sdo_gtypes_vals_camp_tab(self, nom_taula, nom_camp):
1811        """
1812        Retorna los distintos tipos de Geometria (codigo entero que define el tipo SDO_GTYPE)
1813        que se encuentran dentro de la columna sdo_geometry de una tabla
1814
1815        Args:
1816            nom_taula: nombre de la tabla
1817            nom_camp: nombre campo geometrico
1818
1819        Returns:
1820            int definiendo tipo de geometría
1821        """
1822        sql_tip_geoms = f"select distinct(tab.{nom_camp}.Get_GType()) as tip_geom from {nom_taula} tab " \
1823                        f"where {nom_camp} is not null"
1824
1825        for reg in self.generator_rows_sql(sql_tip_geoms):
1826            yield reg.TIP_GEOM
1827
1828    def iter_distinct_vals_camp_tab(self, nom_taula, nom_camp, filter_sql=None):
1829        """
1830        Retorna los distintos valores de la columna de una tabla
1831
1832        Args:
1833            nom_taula (str): Nombre de tabla
1834            nom_camp (str): Nombre de campo
1835            filter_sql(str): Filtro SQL sobre la tabla indicada
1836
1837        Returns:
1838            {str}: Itera los distintos valores encontrados en el campo indicado
1839        """
1840        sql_distinct_vals = f"select distinct(tab.{nom_camp}) as VAL from {nom_taula} tab"
1841        if filter_sql:
1842            sql_distinct_vals += " where " + filter_sql
1843
1844        for reg in self.generator_rows_sql(sql_distinct_vals):
1845            yield reg.VAL
1846
1847    def get_tip_camp_geom(self, nom_tab_or_view, nom_camp_geom):
1848        """
1849        Retorna el tipo de campo geométrico (class_tip_geom) registrados en la global __class_tips_geom_ora
1850        para el campo indicado
1851
1852        Args:
1853            nom_tab_or_view: nombre tabla/vista
1854            nom_camp_geom: nombre campo geom
1855
1856        Returns:
1857            namedtuple: con atributos ['TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID'])
1858        """
1859        tips_geom_tab = get_tips_geom_tab(self.con_db, nom_tab_or_view)
1860        if tips_geom_tab:
1861            return tips_geom_tab.get(nom_camp_geom.upper())
1862
1863    def get_epsg_for_srid(self, srid):
1864        """
1865        Rertorna WKT con la definicion del SRID dado
1866        """
1867        return self.callfunc_sql('SDO_CS.MAP_ORACLE_SRID_TO_EPSG', cx_Oracle.NUMBER, srid)
1868
1869    def get_gtype_camp_geom(self, nom_tab_or_view, nom_camp_geom):
1870        """
1871        Retorna el tipo GTYPE (int) de la geometria
1872
1873        Args:
1874            nom_tab_or_view: nombre tabla/vista
1875            nom_camp_geom: nombre campo geom
1876
1877        Returns:
1878            int
1879        """
1880        gtype = 0
1881        g_tip_ora = self.get_tip_camp_geom(nom_tab_or_view, nom_camp_geom)
1882        if g_tip_ora:
1883            gtype = GTYPES_ORA.index(g_tip_ora.GTYPE)
1884
1885        return gtype
1886
1887    @staticmethod
1888    def verificar_path_vector_file(nom_tab_or_view, dir, file_name, ext, zipped):
1889        """
1890        Compone el/los path para el/los vector_file de una tabla y determina si exists
1891        Args:
1892            nom_tab_or_view:
1893            dir:
1894            file_name:
1895            ext:
1896            zipped:
1897
1898        Returns:
1899            file_path (str), file_path_zip (str), exists (bool)
1900        """
1901        if file_name and not file_name.endswith(ext):
1902            file_name = ".".join((file_name, ext))
1903        elif not file_name:
1904            file_name = ".".join((nom_tab_or_view, ext)).lower()
1905
1906        file_path = os.path.join(dir, file_name)
1907        file_path_zip = None
1908        if zipped:
1909            file_path_zip = "{}.zip".format(os.path.splitext(file_path)[0])
1910
1911        exists = (os.path.exists(file_path) and not file_path_zip) or (file_path_zip and os.path.exists(file_path_zip))
1912
1913        return file_path, file_path_zip, exists
1914
1915    @print_to_log_exception()
1916    def create_json_tab_or_view(self, nom_tab_or_view, dir='.', file_name=None, overwrite=True, cols=None, zipped=True,
1917                                filter_sql=None, *args_sql):
1918        """
1919
1920        Args:
1921            nom_tab_or_view (str): Nombre tabla o vista
1922            dir (str):
1923            file_name (str):
1924            overwrite (bool):
1925            cols (list): columnas
1926            zipped (bool):
1927            filter_sql (str):
1928            *args_sql: lista de argumentos a pasar al filtro sql
1929        Returns:
1930            file_path (str)
1931        """
1932        file_path, file_path_zip, exists = self.verificar_path_vector_file(
1933            nom_tab_or_view, dir, file_name, "json", zipped)
1934
1935        if overwrite or not exists:
1936            # Se calculan las columnas para hacer get de la fila con el orden en las columnas de la tabla
1937            if not cols:
1938                dd_tab = self.get_dd_table(nom_tab_or_view)
1939                cols = dd_tab.cols
1940            sql = sql_tab(nom_tab_or_view,
1941                          filter_sql=filter_sql,
1942                          columns=cols)
1943
1944            file_path_res = vector_file_from_gen_ora_sql(file_path, "json", self.generator_rows_sql(sql, *args_sql),
1945                                                         zipped=zipped)
1946        else:
1947            file_path_res = file_path if not zipped else file_path_zip
1948
1949        return file_path_res
1950
1951    @print_to_log_exception()
1952    def create_csv_tab_or_view(self, nom_tab_or_view, dir='.', file_name=None, overwrite=True, cols=None, zipped=True,
1953                               filter_sql=None, *args_sql):
1954        """
1955
1956        Args:
1957            nom_tab_or_view (str): Nombre tabla o vista
1958            dir (str):
1959            file_name (str):
1960            overwrite (bool):
1961            cols (list): columnas
1962            zipped (bool):
1963            filter_sql (str):
1964            *args_sql: lista de argumentos a pasar al filtro sql
1965
1966        Returns:
1967            file_path_res (str)
1968        """
1969        file_path, file_path_zip, exists = self.verificar_path_vector_file(
1970            nom_tab_or_view, dir, file_name, "csv", zipped)
1971
1972        if overwrite or not exists:
1973            if not cols:
1974                dd_tab = self.get_dd_table(nom_tab_or_view)
1975                cols = dd_tab.cols
1976            sql = sql_tab(nom_tab_or_view,
1977                          filter_sql=filter_sql,
1978                          columns=cols)
1979
1980            # Para el formato geocsv que acepta GDAL se añade fichero con los tipos de columna
1981            tip_cols_csv = []
1982            for col in cols:
1983                r_tip_col = self.row_table("user_tab_columns",
1984                                           "table_name = :1 and column_name = :2",
1985                                           nom_tab_or_view.upper(), col.upper())
1986                dtype = r_tip_col.DATA_TYPE
1987                dlength = r_tip_col.DATA_LENGTH
1988                dprecision = r_tip_col.DATA_PRECISION
1989                dscale = r_tip_col.DATA_SCALE
1990
1991                if dtype == "DATE":
1992                    tip_cols_csv.append('"DateTime"')
1993                elif dtype == "FLOAT":
1994                    tip_cols_csv.append('"Real({}.{})"'.format(dlength, dprecision))
1995                elif dtype == "NUMBER":
1996                    if dscale and dscale != 0:
1997                        tip_cols_csv.append('"Real({}.{})"'.format(dprecision, dscale))
1998                    elif dprecision:
1999                        tip_cols_csv.append('"Integer({})"'.format(dprecision))
2000                    else:
2001                        tip_cols_csv.append('"Real(10.8)"')
2002                elif dtype == "SDO_GEOMETRY":
2003                    tip_cols_csv.append('"WKT"')
2004                else:
2005                    tip_cols_csv.append('"String({})"'.format(round(dlength * 1.25)))
2006
2007            file_path_res = vector_file_from_gen_ora_sql(file_path, "csv",
2008                                                         self.generator_rows_sql(sql, *args_sql, geom_format="as_wkt"),
2009                                                         zipped=zipped, cols_csv=cols, tip_cols_csv=tip_cols_csv)
2010        else:
2011            file_path_res = file_path if not zipped else file_path_zip
2012
2013        return file_path_res
2014
2015    @print_to_log_exception()
2016    def create_geojsons_tab_or_view(self, nom_tab_or_view, dir='.', file_name_prefix=None, by_geom=False,
2017                                    dir_topojson=None, overwrite=True, cols=None,
2018                                    filter_sql=None, *args_sql):
2019        """
2020
2021        Args:
2022            nom_tab_or_view (str): Nombre tabla (vigente o versionada) para entidad GIS
2023            dir (str="."):
2024            file_name_prefix (str=None): (opcional) prefijo del fichero
2025            by_geom (bool=False): (Opcional) si se querrán los geojsons por geometria. Si no se saca un unico geojson con
2026                            la columna geometry como una GeometryCollection si la tabla es multigeom
2027            dir_topojson (str=None): path donde irán las conversiones
2028            overwrite (bool=True):
2029            cols (list=None):
2030            filter_sql (str=None):
2031            *args_sql: lista de argumentos a pasar al filtro sql
2032
2033        Returns:
2034            ok (bool)
2035        """
2036        ext = "geo.json"
2037        sqls = {}
2038        dd_tab = self.get_dd_table(nom_tab_or_view)
2039        if not cols:
2040            cols = dd_tab.cols
2041
2042        if by_geom:
2043            c_alfas = [cn for cn in dd_tab.alfas() if cn in cols]
2044            c_geoms = [cn for cn in dd_tab.geoms() if cn in cols]
2045            for c_geom in c_geoms:
2046                sqls[c_geom] = sql_tab(nom_tab_or_view, filter_sql, c_alfas + [c_geom])
2047        else:
2048            sqls[None] = sql_tab(nom_tab_or_view, filter_sql, cols)
2049
2050        if not file_name_prefix:
2051            file_name_prefix = nom_tab_or_view
2052
2053        for ng, sql in sqls.items():
2054            file_name = file_name_prefix
2055            if ng:
2056                file_name = "-".join((file_name, ng))
2057
2058            file_name = ".".join((file_name, ext)).lower()
2059            file_path = os.path.join(dir, file_name)
2060
2061            if overwrite or not os.path.exists(file_path):
2062                file_path = vector_file_from_gen_ora_sql(file_path, "geojson",
2063                                                         self.generator_rows_sql(sql, *args_sql))
2064
2065            if by_geom and dir_topojson and file_path:
2066                tip_geom = getattr(dd_tab, ng).GTYPE
2067                simplify = True
2068                if tip_geom.endswith("POINT"):
2069                    simplify = False
2070
2071                topojson_utils.geojson_to_topojson(file_path, dir_topojson,
2072                                                   simplify=simplify,
2073                                                   overwrite=overwrite)
2074
2075        return True
2076
2077
2078if __name__ == '__main__':
2079    import fire
2080
2081    sys.exit(fire.Fire())
GTYPES_ORA = ['DEFAULT', 'POINT', 'LINE', 'POLYGON', 'COLLECTION', 'MULTIPOINT', 'MULTILINE', 'MULTIPOLYGON']
def class_tip_geom(gtype_ora='DEFAULT'):
50def class_tip_geom(gtype_ora="DEFAULT"):
51    """
52    Retorna NAMEDTUPLE 'cursor_desc_tip_geom_GTYPE' con columnas
53    'TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID'
54
55    Args:
56        gtype_ora: tipo geometrias como claves en __class_tips_geom_ora
57
58    Returns:
59        namedtuple('TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID')
60
61    """
62    gtype_ora = gtype_ora.upper()
63
64    if gtype_ora not in __class_tips_geom_ora:
65        gtype_ora = "DEFAULT"
66
67    return __class_tips_geom_ora.get(gtype_ora)

Retorna NAMEDTUPLE 'cursor_desc_tip_geom_GTYPE' con columnas 'TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID'

Arguments:
  • gtype_ora: tipo geometrias como claves en __class_tips_geom_ora
Returns:

namedtuple('TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID')

def del_cache_rel_con_db(con_db_name: str):
77def del_cache_rel_con_db(con_db_name: str):
78    """
79    Borra las Caches para atributos de tablas_o_vistas de Oracle
80    Args:
81        con_db_name: nom de la connexió
82
83    Returns:
84
85    """
86    all_cache = [__cache_pks_tab, __cache_row_desc_tab, __cache_tips_geom_tab, __cache_row_class_tab]
87    for cache_dic in all_cache:
88        # alamcenamos las claves porque no se puede eliminar del diccionario mientras se itera
89        keys_remove = []
90        for key in cache_dic:
91            if key.startswith(con_db_name):
92                keys_remove.append(key)
93        for key_r in keys_remove:
94            del cache_dic[key_r]

Borra las Caches para atributos de tablas_o_vistas de Oracle

Arguments:
  • con_db_name: nom de la connexió

Returns:

def get_oracle_connection(user_ora, psw_ora, dsn_ora=None, call_timeout=None, schema_ora=None):
 97def get_oracle_connection(user_ora, psw_ora, dsn_ora=None, call_timeout=None, schema_ora=None):
 98    """
 99    Return cx_Oracle Connection
100    Args:
101        user_ora (str):
102        psw_ora (str):
103        dsn_ora (str=None):
104        call_timeout (int=None): miliseconds
105        schema_ora(str=None): indicate scheme when it is different from the user, default schema = user
106
107    Returns:
108        cx_Oracle.Connection
109    """
110    connection = cx_Oracle.Connection(user_ora, psw_ora, dsn_ora, encoding="UTF-8", nencoding="UTF-8")
111    if call_timeout:
112        connection.call_timeout = call_timeout
113
114    if schema_ora:
115        connection.current_schema = schema_ora
116
117    return connection

Return cx_Oracle Connection

Arguments:
  • user_ora (str):
  • psw_ora (str):
  • dsn_ora (str=None):
  • call_timeout (int=None): miliseconds
  • schema_ora(str=None): indicate scheme when it is different from the user, default schema = user
Returns:

cx_Oracle.Connection

def get_nom_conexion(con_db):
120def get_nom_conexion(con_db):
121    """
122    Devuelve el nombre de la conexion a Oracle
123
124    Args:
125        con_db:
126
127    Returns:
128
129    """
130    return "@".join((con_db.username.upper(), con_db.dsn.upper()))

Devuelve el nombre de la conexion a Oracle

Arguments:
  • con_db:

Returns:

def new_cursor(con_db, input_handler=None, output_handler=None):
133def new_cursor(con_db, input_handler=None, output_handler=None):
134    """
135    Retorna cx_Oracle.Cursor con los handlers pasados por parámetro
136
137    Args:
138        con_db:
139        input_handler:
140        output_handler:
141
142    Returns:
143        cx_Oracle.cursor
144    """
145    try:
146        curs = con_db.cursor()
147
148        if input_handler:
149            curs.inputtypehandler = input_handler
150
151        if output_handler:
152            curs.outputtypehandler = output_handler
153
154        return curs
155
156    except cx_Oracle.Error as exc:
157        print("!!ERROR!! - Error al instanciar Cursor para la conexion Oracle {}\n"
158              "     Error: {}".format(get_nom_conexion(con_db), exc))
159        raise

Retorna cx_Oracle.Cursor con los handlers pasados por parámetro

Arguments:
  • con_db:
  • input_handler:
  • output_handler:
Returns:

cx_Oracle.cursor

def get_row_descriptor(curs, nom_base_desc=None):
162def get_row_descriptor(curs, nom_base_desc=None):
163    """
164    Retorna instancia namedtuple indexada por las columnas de la query ejecutada en el cursor con los
165    tipos de columna por valores.
166
167    Si se pasa nom_base_desc se hará heredar el namedtuple de esa clase
168
169    Args:
170        curs:
171        nom_base_desc:
172
173    Returns:
174
175    """
176    ora_desc = None
177    dd_reg = curs.description
178    dict_camps = OrderedDict.fromkeys([def_col[0].replace(" ", "_") for def_col in dd_reg])
179    if nom_base_desc:
180        # Por si viene nombre de tabla con esquema delante nos quedamos solo con la ultima parte
181        nom_base_desc = nom_base_desc.split(".")[-1]
182        nom_base_desc += "_cursor"
183    else:
184        nom_base_desc = "cursor_" + str(id(curs))
185
186    try:
187        nt_class = namedtuple(nom_base_desc,
188                              list(dict_camps))
189        ora_desc = nt_class(*[def_cols[1] for def_cols in dd_reg])
190    except ValueError:
191        raise Exception("!ERROR! - No se puede crear descriptor de fila para el SQL especificado. "
192                        "El nombre de la columna {} no es válido".format(str(sys.exc_info()[1]).split(":")[1]),
193                        sys.exc_info())
194
195    return ora_desc

Retorna instancia namedtuple indexada por las columnas de la query ejecutada en el cursor con los tipos de columna por valores.

Si se pasa nom_base_desc se hará heredar el namedtuple de esa clase

Arguments:
  • curs:
  • nom_base_desc:

Returns:

def get_row_class_cursor(cursor_desc, con_db):
198def get_row_class_cursor(cursor_desc, con_db):
199    """
200    Retorna clase de fila por defecto (class row_cursor) para un cursor
201
202    Args:
203        cursor_desc: (opcional) clase de la que se quiera heredar
204        con_db: conexión cx_Oracle
205
206    Returns:
207        class (default = row_cursor)
208    """
209    cls_curs_dsc = type(cursor_desc)
210
211    class row_cursor(cls_curs_dsc):
212        """
213        Clase para fila devuelta por query SQL (para queries que NO sean sobre una tabla/vista)
214        """
215        ora_descriptor = cursor_desc
216        con_ora = con_db
217
218        def vals(self):
219            """
220            Valores de la fila
221
222            Returns:
223                OrderedDict
224            """
225            return self._asdict()
226
227        def as_xml(self,
228                   nom_root_xml="root_reg_sql",
229                   atts_root_xml=None,
230                   excluded_cols=None,
231                   as_etree_elem=False):
232            """
233            Devuelve fila en formato XML
234
235            Args:
236                nom_root_xml: define el nombre del TAG root del XML. Por defecto 'root_reg_sql'
237                atts_root_xml: lista que define las columnas que se asignarán
238                               como atributos del TAG root
239                excluded_cols: lista con nombres de columnas que no se quieran incluir
240                as_etree_elem (default=False): Si True devuelve registro como objeto etree.Element.
241                                              Si False (por defecto) como str en formato XML
242
243            Returns:
244                str (formato XML) OR etree.Element
245            """
246            if not excluded_cols:
247                excluded_cols = list()
248
249            atts_xml = None
250            d_xml_elems = self.xml_elems()
251            if atts_root_xml:
252                atts_xml = OrderedDict({att.lower(): d_xml_elems[att.lower()]
253                                        for att in atts_root_xml})
254
255            tab_elem = etree.Element(nom_root_xml.lower(), atts_xml)
256            excluded_cols = [c.lower() for c in excluded_cols]
257            for tag, xml_val in d_xml_elems.items():
258                if tag in excluded_cols:
259                    continue
260
261                try:
262                    camp_elem = etree.SubElement(tab_elem, tag.lower())
263                    camp_elem.text = xml_val
264                except:
265                    print("!ERROR! - No se ha podido añadir el campo '",
266                          tag, "' al XML", sep="")
267
268            ret = tab_elem
269
270            if not as_etree_elem:
271                ret = etree.tostring(tab_elem, encoding="unicode")
272
273            return ret
274
275        def xml_elems(self):
276            """
277            Itera los campos del registro y devuelve su nombre y su valor parseado para XML
278
279            Yields:
280                n_camp, val_camp
281            """
282            d_xml_elems = dict()
283            for camp, val in self.vals().items():
284                ret_val = ""
285                if isinstance(val, datetime.datetime):
286                    ret_val = val.strftime("%Y-%m-%dT%H:%M:%S")
287                elif isinstance(val, m_sdo_geom.sdo_geom):
288                    ret_val = val.as_wkt()
289                elif val:
290                    ret_val = str(val)
291
292                d_xml_elems[camp.lower()] = ret_val
293
294            return d_xml_elems
295
296        def as_json(self):
297            """
298            Retorna la fila como JSON con las geometrias en formato GeoJson
299
300            Returns:
301                str con la fila en formato json
302            """
303            return json.dumps(self.vals(),
304                              ensure_ascii=False,
305                              cls=geojson_encoder)
306
307        def as_geojson(self):
308            """
309            Retorna la fila como GEOJSON
310
311            Returns:
312                str con la fila en formato geojson
313            """
314            return json.dumps(self.__geo_interface__,
315                              ensure_ascii=False,
316                              cls=geojson_encoder)
317
318        @property
319        def __geo_interface__(self):
320            """
321            GeoJSON-like protocol for geo-spatial para toda una fila devuelta por query SQL
322
323            Si la fila tiene varias geometrias devolverá geojson como "GeometryCollection"
324
325            Returns:
326                dict con las geometrias y campos alfanuméricos (properties de Feature) en formato geojson
327            """
328            geos = dict(**self.sdo_geoms)
329            num_geos = len(geos)
330            geom = None
331            if num_geos == 1:
332                geom = next(iter(geos.values()))
333            elif num_geos > 1:
334                geom = {"type": "GeometryCollection",
335                        "geometries": list(geos.values())}
336
337            if geom:
338                return dict({"type": "Feature",
339                             "geometry": geom,
340                             "properties": {nc: val for nc, val in self.vals().items() if nc not in geos}})
341
342        @property
343        def sdo_geoms(self):
344            """
345            Devuelve diccionario indexado por los nombres de columnas que tienen valor geometrico de la clase sdo_geom
346
347            Returns:
348                dict {nom_column:sdo_geom}
349            """
350            return {nc: g.__geo_interface__
351                    for nc, g in self.vals().items() if isinstance(g, m_sdo_geom.sdo_geom)}
352
353        def geometries(self, as_format=None):
354            """
355            Itera por los campos geométricos informados
356
357            Args:
358                as_format (default=None): Se puede informar con nombre funcion 'as_XXXX' a la que responda SDO_GEOM
359
360            Yields:
361                nom_camp, geom
362            """
363            for ng, g in self.vals().items():
364                if isinstance(g, m_sdo_geom.sdo_geom):
365                    g_val = g
366                    if as_format:
367                        g_val = getattr(g, as_format)()
368
369                    yield ng, g_val
370
371        @property
372        def cols_pts_angles(self):
373            """
374            Devuelve diccionario de campos angulo para geometrias puntuales con la clave el nombre del campo angulo y
375            el valor el nombre del campo puntual al que hace referencia
376
377            Returns:
378                dict
379            """
380            sufix_ang = "_ANG"
381            cols = self.cols
382            cols_ang = {}
383            for c in (c for c, gd in self.geoms_vals().items() if gd.GTYPE.endswith('POINT')):
384                col_ang = x_sql_parser.get_nom_obj_sql(c, sufix=sufix_ang)
385                if col_ang in cols:
386                    cols_ang[col_ang] = c
387
388            return cols_ang
389
390        @property
391        def cols(self):
392            """
393            Devuelve lista con el nombre de la columnas de la fila
394
395            Returns:
396                list con nombres columnas
397            """
398            return self._fields
399
400    return row_cursor

Retorna clase de fila por defecto (class row_cursor) para un cursor

Arguments:
  • cursor_desc: (opcional) clase de la que se quiera heredar
  • con_db: conexión cx_Oracle
Returns:

class (default = row_cursor)

def get_pk_tab(con_db, nom_tab_or_view):
403def get_pk_tab(con_db, nom_tab_or_view):
404    """
405    Retorna la PRIMARY KEY de una tabla o vista de Oracle
406
407    Args:
408        con_db:
409        nom_tab_or_view:
410
411    Returns:
412        lista nombre columnas clave
413    """
414    nom_pk_cache = get_nom_conexion(con_db) + ":" + nom_tab_or_view.upper()
415    noms_camps_pk = __cache_pks_tab.get(nom_pk_cache)
416    if not noms_camps_pk:
417        sql_keys = "SELECT cols.column_name " \
418                   "FROM user_constraints cons, user_cons_columns cols " \
419                   "WHERE cols.table_name = :1 " \
420                   "AND cons.constraint_type = 'P' " \
421                   "AND cons.constraint_name = cols.constraint_name " \
422                   "AND cons.owner = cols.owner " \
423                   "ORDER BY cols.table_name, cols.position"
424
425        noms_camps_pk = [fila_val.COLUMN_NAME for fila_val in
426                         iter_execute_fetch_sql(con_db, sql_keys, nom_tab_or_view.upper())]
427        __cache_pks_tab[nom_pk_cache] = noms_camps_pk
428
429    return noms_camps_pk

Retorna la PRIMARY KEY de una tabla o vista de Oracle

Arguments:
  • con_db:
  • nom_tab_or_view:
Returns:

lista nombre columnas clave

def get_tips_geom_tab(con_db, nom_tab_or_view):
432def get_tips_geom_tab(con_db, nom_tab_or_view):
433    """
434    Retorna diccionario con nombre del campo como indice y los tipos de campos geométricos
435    (class_tip_geom) registrados en la global __class_tips_geom_ora como clases 'c_desc_tip_geom_?' siendo ?
436    el LAYER_GTYPE del indice de Oracle
437
438    Args:
439        con_db:
440        nom_tab_or_view:
441
442    Returns:
443        dict indexado por nombre columnas geometria y la clase de geometria (__class_tips_geom_ora)
444    """
445    nom_con = get_nom_conexion(con_db)
446
447    tips_geom_con = __cache_tips_geom_tab.get(nom_con)
448    if not tips_geom_con:
449        tips_geom_con = __cache_tips_geom_tab[nom_con] = {}
450        sql_tip_geom = "select  /*+ result_cache */ " \
451                       "        T_COLS.TABLE_NAME, " \
452                       "        T_COLS.COLUMN_NAME, " \
453                       "        V_IDX_META.SDO_LAYER_GTYPE GTYPE, " \
454                       "        V_G_META.SRID " \
455                       "from    user_tab_columns t_cols," \
456                       "        user_sdo_geom_metadata v_g_meta," \
457                       "        user_sdo_index_info v_idx_info," \
458                       "        user_sdo_index_metadata v_idx_meta " \
459                       "where v_g_meta.table_name = t_cols.table_name and " \
460                       "v_g_meta.column_name = t_cols.column_name and" \
461                       "    v_idx_info.table_name = t_cols.table_name and " \
462                       "v_idx_info.column_name = t_cols.column_name and" \
463                       "    v_idx_meta.sdo_index_name = v_idx_info.index_name"
464
465        for reg_tip_geom in iter_execute_fetch_sql(con_db, sql_tip_geom):
466            nom_cache = reg_tip_geom.TABLE_NAME.upper()
467            cache_tips_geom = tips_geom_con.get(nom_cache)
468            if not cache_tips_geom:
469                cache_tips_geom = {}
470                tips_geom_con[nom_cache] = cache_tips_geom
471
472            tip_geom = class_tip_geom(reg_tip_geom.GTYPE)(*reg_tip_geom.vals().values())
473
474            nom_camp_geom = reg_tip_geom.COLUMN_NAME.upper()
475            cache_tips_geom[nom_camp_geom] = tip_geom
476
477    tips_geom = tips_geom_con.get(nom_tab_or_view.upper())
478
479    return tips_geom if tips_geom else {}

Retorna diccionario con nombre del campo como indice y los tipos de campos geométricos (class_tip_geom) registrados en la global __class_tips_geom_ora como clases 'c_desc_tip_geom_?' siendo ? el LAYER_GTYPE del indice de Oracle

Arguments:
  • con_db:
  • nom_tab_or_view:
Returns:

dict indexado por nombre columnas geometria y la clase de geometria (__class_tips_geom_ora)

def get_row_desc_tab(con_db, nom_tab_or_view):
482def get_row_desc_tab(con_db, nom_tab_or_view):
483    """
484    Retorna el descriptor (cursor con los tipos de campo para cada columna) de una tabla o vista de Oracle
485
486    Args:
487        con_db:
488        nom_tab_or_view:
489
490    Returns:
491        namedtuple ó clase fila con las columnas y sus tipos
492    """
493    nom_dd_cache = get_nom_conexion(con_db) + ":" + nom_tab_or_view.upper()
494    tab_desc = __cache_row_desc_tab.get(nom_dd_cache)
495    if not tab_desc:
496        row_class_tab = get_row_class_tab(con_db, nom_tab_or_view)
497
498        camps_dd = row_class_tab.ora_descriptor
499        dict_camps = camps_dd._asdict()
500        dict_camps_geom = row_class_tab.geoms()
501        if dict_camps_geom:
502            for nom_camp, tip_camp_geom in dict_camps_geom.items():
503                dict_camps[nom_camp] = tip_camp_geom
504
505        tab_desc = row_class_tab(*dict_camps.values())
506        __cache_row_desc_tab[nom_dd_cache] = tab_desc
507
508    return tab_desc

Retorna el descriptor (cursor con los tipos de campo para cada columna) de una tabla o vista de Oracle

Arguments:
  • con_db:
  • nom_tab_or_view:
Returns:

namedtuple ó clase fila con las columnas y sus tipos

def get_tip_camp(con_db, nom_tab_or_view, nom_camp):
511def get_tip_camp(con_db, nom_tab_or_view, nom_camp):
512    """
513    Devuelve el tipo de campo para la tabla_vista y campo especificado. En el caso de campos alfanuméricos
514    devuelve los tipos de cx_Oracle relacionados (cx_Oracle.NUMBER, cx_Oracle.STRING, ...) y para las
515    geométricas del tipo CLASS_TIP_GEOM
516
517    Args:
518        con_db:
519        nom_tab_or_view:
520        nom_camp:
521
522    Returns:
523        object: (cx_Oracle.NUMBER, cx_Oracle.STRING, ...) o si geometria tipo en __class_tips_geom_ora
524    """
525    nom_tab_or_view = nom_tab_or_view.upper()
526    nom_camp = nom_camp.upper()
527
528    dd_tab = get_row_desc_tab(con_db, nom_tab_or_view)
529    if dd_tab:
530        return getattr(dd_tab, nom_camp, None)

Devuelve el tipo de campo para la tabla_vista y campo especificado. En el caso de campos alfanuméricos devuelve los tipos de cx_Oracle relacionados (cx_Oracle.NUMBER, cx_Oracle.STRING, ...) y para las geométricas del tipo CLASS_TIP_GEOM

Arguments:
  • con_db:
  • nom_tab_or_view:
  • nom_camp:
Returns:

object: (cx_Oracle.NUMBER, cx_Oracle.STRING, ...) o si geometria tipo en __class_tips_geom_ora

def sql_tab(nom_tab_or_view, filter_sql=None, columns=None):
533def sql_tab(nom_tab_or_view, filter_sql=None, columns=None):
534    """
535    Devuelve sql para tabla o vista
536
537    Args:
538        nom_tab_or_view:
539        filter_sql:
540        columns (list): Lista nombre de columnas a mostrar. Default '*' (todas)
541
542    Returns:
543        str: con select sql para tabla o vista
544    """
545    cols = "*"
546    if columns:
547        cols = ",".join(columns)
548
549    sql_tab = "select {cols} from {taula} TAB".format(
550        taula=nom_tab_or_view,
551        cols=cols)
552
553    if filter_sql:
554        sql_tab += " where " + filter_sql
555
556    return sql_tab

Devuelve sql para tabla o vista

Arguments:
  • nom_tab_or_view:
  • filter_sql:
  • columns (list): Lista nombre de columnas a mostrar. Default '*' (todas)
Returns:

str: con select sql para tabla o vista

def get_row_class_tab(con_db, nom_tab_or_view):
559def get_row_class_tab(con_db, nom_tab_or_view):
560    """
561    Retorna clase que hereda de namedtuple para instanciar con los valores de un nuevo registro
562
563    Args:
564        con_db:
565        nom_tab_or_view:
566
567    Returns:
568        object: clase que hereda del namedtuple para una fila
569    """
570    nom_tab_or_view = nom_tab_or_view.upper()
571    nom_row_class_tab_cache = get_nom_conexion(con_db) + ":" + nom_tab_or_view
572    row_class_tab = __cache_row_class_tab.get(nom_row_class_tab_cache)
573
574    if not row_class_tab:
575        curs = con_db.cursor()
576        curs.execute(sql_tab(nom_tab_or_view))
577        ora_desc = get_row_descriptor(curs, nom_tab_or_view)
578
579        row_cursor_cls = get_row_class_cursor(ora_desc, con_db)
580
581        class row_table(row_cursor_cls):
582            """
583            Clase para fila devuelta por SQL sobre tabla
584            """
585            nom_tab = nom_tab_or_view
586
587            def __repr__(self):
588                """
589                built_in que actua cuando se representa clase como STRING
590
591                Returns:
592                    str
593                """
594                repr_txt = nom_tab_or_view + "({pk_vals})"
595                pk_txt = []
596                for i, v in self.pk_vals().items():
597                    pk_txt.append(str(i) + "=" + str(v))
598
599                return repr_txt.format(pk_vals=",".join(pk_txt))
600
601            @staticmethod
602            def pk():
603                """
604                Retorna lista con los nombres de campos clave
605
606                Returns:
607                    list con nombres campos clave
608                """
609                return get_pk_tab(con_db, nom_tab_or_view)
610
611            def pk_vals(self):
612                """
613                Retorna OrderedDict con los pares nombres:valor para los campos clave
614
615                Returns:
616                    OrderedDict
617                """
618                pk_vals = OrderedDict.fromkeys(self.pk())
619                for k in self.pk():
620                    pk_vals[k] = self.vals().get(k)
621
622                return pk_vals
623
624            @staticmethod
625            def geoms():
626                """
627                Devuelve diccionario {nom_camp_geom:tip_geom} para todas las columnas de la tabla que son de
628                tipo geométrico.
629                Lo deduce de la definición de la tabla independientemente de si la columna está informada o no
630
631                Returns:
632                    dict {nom_geom:tip_geom} (vease funcion clas_tip_geom())
633                """
634                return get_tips_geom_tab(con_db, nom_tab_or_view)
635
636            def geoms_vals(self):
637                """
638                Valores de los campos geometricos
639
640                Returns:
641                    dict
642                """
643                vals = self.vals()
644                return {nc: vals[nc] for nc in self.geoms()}
645
646            def as_xml(self, excluded_cols=[],
647                       as_etree_elem=False):
648                """
649                Devuelve row como XML
650
651                Args:
652                    excluded_cols: lista de nombre de columnas que se quieren excluir
653                    as_etree_elem {bool} (default=False): Si True devuelve registro como objeto etree.Element. Si False (por defecto) como str en formato XML
654
655                Returns:
656                    str (formato XML) OR etree.Element
657                """
658                return super(row_table, self).as_xml(nom_tab_or_view,
659                                                     self.pk_vals(),
660                                                     excluded_cols,
661                                                     as_etree_elem)
662
663            @staticmethod
664            def get_row_desc():
665                return get_row_desc_tab(con_db, nom_tab_or_view)
666
667            @staticmethod
668            def alfas(include_pk=True):
669                """
670                Devuelve diccionario {nom_camp:tip_camp} para las columnas alfanuméricas (NO son geométricas)
671                Lo deduce de la definición de la tabla independientemente de si la columna está informada o no
672
673                Args:
674                    include_pk: incluir primary key
675
676                Returns:
677                    dict {nom_camp:tip_camp}
678                """
679                return {nc: tc
680                        for nc, tc in get_row_desc_tab(con_db, nom_tab_or_view).vals().items()
681                        if nc not in get_tips_geom_tab(con_db, nom_tab_or_view) and
682                        (include_pk or nc not in get_pk_tab(con_db, nom_tab_or_view))}
683
684            def alfas_vals(self):
685                """
686                Devuelve diccionario con valores campos alfanuméricos
687
688                Returns:
689                    dict {nom_camp:val_camp}
690                """
691                vals = self.vals()
692                return {nc: vals[nc] for nc in self.alfas()}
693
694        row_class_tab = row_table
695        __cache_row_class_tab[nom_row_class_tab_cache] = row_class_tab
696
697    return row_class_tab

Retorna clase que hereda de namedtuple para instanciar con los valores de un nuevo registro

Arguments:
  • con_db:
  • nom_tab_or_view:
Returns:

object: clase que hereda del namedtuple para una fila

def get_row_factory(curs, a_row_class=None):
700def get_row_factory(curs, a_row_class=None):
701    """
702    Retorna funcion para crear instancia clase a partir de los valores de una fila
703
704    Args:
705        curs:
706        a_row_class:
707
708    Returns:
709        function
710    """
711    if not a_row_class:
712        cursor_desc = get_row_descriptor(curs)
713        con_db = curs.connection
714        a_row_class = get_row_class_cursor(cursor_desc, con_db)
715
716    def row_factory_func(*vals_camps):
717        return a_row_class(*vals_camps)
718
719    return row_factory_func

Retorna funcion para crear instancia clase a partir de los valores de una fila

Arguments:
  • curs:
  • a_row_class:
Returns:

function

def get_row_cursor(curs, rowfactory=None):
722def get_row_cursor(curs, rowfactory=None):
723    """
724    Retorna fila como clase que construye el rowfactory. Por defecto si no se informa esta clase heredará
725    de 'namedtuple' con los campos como items más funcionalidad relacionada con la conexion y el descriptor de la fila
726    con los tipos de campo para cada columna
727
728    Args:
729        curs:
730        rowfactory:
731
732    Returns:
733
734    """
735    if not rowfactory and not curs.rowfactory:
736        rowfactory = get_row_factory(curs)
737
738    if rowfactory:
739        curs.rowfactory = rowfactory
740
741    row = curs.fetchone()
742
743    return row

Retorna fila como clase que construye el rowfactory. Por defecto si no se informa esta clase heredará de 'namedtuple' con los campos como items más funcionalidad relacionada con la conexion y el descriptor de la fila con los tipos de campo para cada columna

Arguments:
  • curs:
  • rowfactory:

Returns:

def iter_execute_fetch_sql(con_db, sql_str, *args_sql, **extra_params):
746def iter_execute_fetch_sql(con_db, sql_str, *args_sql, **extra_params):
747    """
748    Itera y devuelve cada fila devuelta para la consulta sql_str
749
750    Args:
751        con_db:
752        sql_str:
753        *args_sql:
754        **extra_params: { "row_class": clase que se utilizará para cada fila. Vease get_row_factory()
755                          "as_format": formato en el que se devuelve cada fila. Las clases base row_cursor y row_table
756                                    (vease get_row_class_cursor() y get_row_class_tab()) responden por defecto a
757                                    "as_xml()" y "as_json()" }
758
759    Returns:
760        row_class o string en formato especificado en **extra_params["as_format"]
761    """
762    curs = None
763
764    try:
765        curs = new_cursor(con_db,
766                          input_handler=extra_params.pop("input_handler",
767                                                         m_sdo_geom.get_sdo_input_handler()),
768                          output_handler=extra_params.pop("output_handler",
769                                                          m_sdo_geom.get_output_handler(
770                                                              con_db,
771                                                              extra_params.pop("geom_format", None))))
772
773        curs.execute(sql_str,
774                     args_sql)
775
776        row_class = extra_params.pop("row_class", None)
777        row_factory = None
778        if row_class:
779            row_factory = get_row_factory(curs, row_class)
780
781        reg = get_row_cursor(curs, rowfactory=row_factory)
782        while reg is not None:
783            if "as_format" in extra_params:
784                f_format = extra_params["as_format"]
785                if f_format:
786                    reg = getattr(reg, f_format)()
787
788            yield reg
789
790            reg = get_row_cursor(curs)
791
792    except:
793        if curs is not None:
794            curs.close()
795        raise
796
797    if curs is not None:
798        curs.close()

Itera y devuelve cada fila devuelta para la consulta sql_str

Arguments:
  • con_db:
  • sql_str:
  • *args_sql:
  • **extra_params: { "row_class": clase que se utilizará para cada fila. Vease get_row_factory() "as_format": formato en el que se devuelve cada fila. Las clases base row_cursor y row_table (vease get_row_class_cursor() y get_row_class_tab()) responden por defecto a "as_xml()" y "as_json()" }
Returns:

row_class o string en formato especificado en **extra_params["as_format"]

def execute_fetch_sql(con_db, sql_str, *args_sql, **extra_params):
801def execute_fetch_sql(con_db, sql_str, *args_sql, **extra_params):
802    """
803    Devuelve la primera iteración sobre la consulta sql_str
804
805    Args:
806        con_db:
807        sql_str:
808        *args_sql:
809        **extra_params: { "row_class": clase que se utilizará para cada fila. Vease get_row_factory()
810                          "as_format": formato en el que se devuelve cada fila. Las clases base row_cursor y row_table
811                                    (vease get_row_class_cursor() y get_row_class_tab()) responden por defecto a
812                                    "as_xml()" y "as_json()" }
813
814    Returns:
815        row_class o string en formato especificado en **extra_params["as_format"]
816    """
817    reg = None
818    for row in iter_execute_fetch_sql(con_db,
819                                      sql_str,
820                                      *args_sql,
821                                      input_handler=extra_params.pop("input_handler", None),
822                                      output_handler=extra_params.pop("output_handler", None),
823                                      row_class=extra_params.pop("row_class", None),
824                                      **extra_params):
825        reg = row
826        break
827
828    return reg

Devuelve la primera iteración sobre la consulta sql_str

Arguments:
  • con_db:
  • sql_str:
  • *args_sql:
  • **extra_params: { "row_class": clase que se utilizará para cada fila. Vease get_row_factory() "as_format": formato en el que se devuelve cada fila. Las clases base row_cursor y row_table (vease get_row_class_cursor() y get_row_class_tab()) responden por defecto a "as_xml()" y "as_json()" }
Returns:

row_class o string en formato especificado en **extra_params["as_format"]

def dict_as_sql_bind_and_params(dict_vals, bool_rel='and', filter_oper='='):
831def dict_as_sql_bind_and_params(dict_vals, bool_rel="and", filter_oper="="):
832    """
833    A partir de un dict de {nom_camps:value_camps} devuelve un string en forma
834    de SQL FILTER bindind (camp_a = :1 and camp_b = :2) y la lista de params que se asignarán via binding
835    El BOOL_REL podrá ser 'AND', 'OR' o ',' para el SET de los updates
836    El FILTER_OPER podrá ser '=', '!=' o ':=' para el SET de los updates
837
838    Args:
839        dict_vals:
840        bool_rel:
841        filter_oper:
842
843    Returns:
844        str, [params*]
845    """
846    query_elems = {}
847    for nom_camp, val_clau in dict_vals.items():
848        sql_str = str(nom_camp) + " " + filter_oper + " :" + str(nom_camp)
849        query_elems[sql_str] = val_clau
850
851    sql = (" " + bool_rel.strip().upper() + " ").join(query_elems.keys())
852
853    return sql, list(query_elems.values())

A partir de un dict de {nom_camps:value_camps} devuelve un string en forma de SQL FILTER bindind (camp_a = :1 and camp_b = :2) y la lista de params que se asignarán via binding El BOOL_REL podrá ser 'AND', 'OR' o ',' para el SET de los updates El FILTER_OPER podrá ser '=', '!=' o ':=' para el SET de los updates

Arguments:
  • dict_vals:
  • bool_rel:
  • filter_oper:
Returns:

str, [params*]

class SerializableGenerator(builtins.list):
856class SerializableGenerator(list):
857    """Generator that is serializable by JSON
858
859    It is useful for serializing huge data by JSON
860    It can be used in a generator of json chunks used e.g. for a stream
861    ('[1', ']')
862    # >>> for chunk in iter_json:
863    # ...     stream.write(chunk)
864    # >>> SerializableGenerator((x for x in range(3)))
865    # [<generator object <genexpr> at 0x7f858b5180f8>]
866    """
867
868    def __init__(self, iterable):
869        super().__init__()
870        tmp_body = iter(iterable)
871        try:
872            self._head = iter([next(tmp_body)])
873            self.append(tmp_body)
874        except StopIteration:
875            self._head = []
876
877    def __iter__(self):
878        return itertools.chain(self._head, *self[:1])

Generator that is serializable by JSON

It is useful for serializing huge data by JSON It can be used in a generator of json chunks used e.g. for a stream ('[1', ']')

>>> for chunk in iter_json:

... stream.write(chunk)

>>> SerializableGenerator((x for x in range(3)))

[ at 0x7f858b5180f8>]

SerializableGenerator(iterable)
868    def __init__(self, iterable):
869        super().__init__()
870        tmp_body = iter(iterable)
871        try:
872            self._head = iter([next(tmp_body)])
873            self.append(tmp_body)
874        except StopIteration:
875            self._head = []
def geojson_from_gen_ora_sql(generator_sql, as_string=False):
881def geojson_from_gen_ora_sql(generator_sql, as_string=False):
882    """
883    Devuelve diccionario geojson para un generator de rows de Oracle
884
885    Args:
886        generator_sql (function generator):
887        as_string (bool): (opcional) indica si se querrá el geojson como un string
888
889    Returns:
890        geojson (dict ó str)
891    """
892    vals = {"type": "FeatureCollection",
893            "features": [getattr(r, "__geo_interface__")
894                         for r in generator_sql
895                         if getattr(r, "__geo_interface__")]}
896
897    ret = vals
898    if as_string:
899        ret = json.dumps(vals,
900                         ensure_ascii=False)
901
902    return ret

Devuelve diccionario geojson para un generator de rows de Oracle

Arguments:
  • generator_sql (function generator):
  • as_string (bool): (opcional) indica si se querrá el geojson como un string
Returns:

geojson (dict ó str)

def vector_file_from_gen_ora_sql( file_path, vector_format, func_gen, zipped=False, indent_json=None, cols_csv=None, tip_cols_csv=None):
905def vector_file_from_gen_ora_sql(file_path, vector_format, func_gen, zipped=False, indent_json=None, cols_csv=None,
906                                 tip_cols_csv=None):
907    """
908    A partir del resultado de una query SQL devuelve un file_object formateado segun formato
909
910    Args:
911        file_path (str): path del fichero a grabar
912        vector_format (str): tipo formato (CSV, JSON, GEOJSON)
913        func_gen (generator function): funcion que devuelva filas sql en forma de row_cursor o row_table
914                                      (vease funciones generator_rows_sql() o generator_rows_table())
915        zipped (bool=False): Devuelve fichero en un fichero comprimido (.zip)
916        indent_json (int):
917        cols_csv (list): Lista de columnes del CSV
918        tip_cols_csv (list): Lista de tipos de columnas para CSV.
919                             Revisar especificacion en https://giswiki.hsr.ch/GeoCSV
920    Returns:
921        str: pathfile del fichero generado
922    """
923    vector_format = vector_format.lower()
924    newline = None if vector_format != "csv" else ""
925    file_path_csvt = None
926    str_csvt = None
927    dir_base = os.path.dirname(file_path)
928    if dir_base:
929        os.makedirs(dir_base, exist_ok=True)
930
931    # Se hace en memoria o recursos locales (SpooledTemporaryFile) para evitar trabajar lo minimo en la red
932    # si se da el caso en el path fichero del fichero indicado. Cuando se quiere grabar en recurso local el tiempo
933    # perdido por utilizar un fichero temporal debería ser despreciable comparado con la complejidad añadida al código
934    # para decidir si usar o no el SpooledTemporaryFile
935    with SpooledTemporaryFile(mode="w+", encoding="utf-8", newline=newline) as temp_file:
936        if vector_format == "geojson":
937            json.dump(geojson_from_gen_ora_sql(func_gen),
938                      temp_file,
939                      ensure_ascii=False,
940                      indent=indent_json,
941                      cls=geojson_encoder)
942        elif vector_format == "json":
943            json.dump(SerializableGenerator((r.vals() for r in func_gen)),
944                      temp_file,
945                      ensure_ascii=False,
946                      indent=indent_json,
947                      cls=geojson_encoder)
948        elif vector_format == "csv":
949            writer = csv.DictWriter(temp_file, fieldnames=cols_csv)
950            writer.writeheader()
951
952            for r in func_gen:
953                writer.writerow(r.vals())
954
955        temp_file.seek(0)
956        if tip_cols_csv:
957            file_path_csvt = ".".join((os.path.splitext(file_path)[0], "csvt"))
958            str_csvt = ",".join(tip_cols_csv)
959
960        if zipped:
961            file_path_res = "{}.zip".format(os.path.splitext(file_path)[0])
962            with SpooledTemporaryFile() as zip_temp_file:
963                with ZipFile(zip_temp_file, "w", compression=ZIP_DEFLATED, allowZip64=True) as my_temp_zip:
964                    my_temp_zip.writestr(zinfo_or_arcname=os.path.basename(file_path), data=temp_file.read())
965                    if str_csvt:
966                        my_temp_zip.writestr(zinfo_or_arcname=os.path.basename(file_path_csvt), data=str_csvt)
967
968                zip_temp_file.seek(0)
969                with open(file_path_res, mode="wb") as file_res:
970                    shutil.copyfileobj(zip_temp_file, file_res)
971        else:
972            file_path_res = file_path
973            with open(file_path_res, mode="w", encoding="utf-8") as file_res:
974                shutil.copyfileobj(temp_file, file_res)
975            if str_csvt:
976                with open(file_path_csvt, mode="w") as csvt_file:
977                    csvt_file.write(str_csvt)
978
979    return file_path_res

A partir del resultado de una query SQL devuelve un file_object formateado segun formato

Arguments:
  • file_path (str): path del fichero a grabar
  • vector_format (str): tipo formato (CSV, JSON, GEOJSON)
  • func_gen (generator function): funcion que devuelva filas sql en forma de row_cursor o row_table (vease funciones generator_rows_sql() o generator_rows_table())
  • zipped (bool=False): Devuelve fichero en un fichero comprimido (.zip)
  • indent_json (int):
  • cols_csv (list): Lista de columnes del CSV
  • tip_cols_csv (list): Lista de tipos de columnas para CSV. Revisar especificacion en https://giswiki.hsr.ch/GeoCSV
Returns:

str: pathfile del fichero generado

class geojson_encoder(json.encoder.JSONEncoder):
 982class geojson_encoder(json.JSONEncoder):
 983    """
 984    Class Encoder to parser SDO_GEOM to GEOJSON
 985    """
 986    __num_decs__ = 9
 987
 988    def default(self, obj_val):
 989        """
 990        Redefine default para tratar las geometrias SDO_GEOM y convertirlas a geojson y las fechas a iso_format
 991        Args:
 992            obj_val: valor
 993
 994        Returns:
 995            object encoded
 996        """
 997        if isinstance(obj_val, m_sdo_geom.sdo_geom):
 998            return obj_val.as_geojson()
 999        elif isinstance(obj_val, (datetime.datetime, datetime.date)):
1000            return obj_val.isoformat()
1001        elif isinstance(obj_val, cx_Oracle.LOB):
1002            return obj_val.read()
1003        else:
1004            return json.JSONEncoder.default(self, obj_val)

Class Encoder to parser SDO_GEOM to GEOJSON

def default(self, obj_val):
 988    def default(self, obj_val):
 989        """
 990        Redefine default para tratar las geometrias SDO_GEOM y convertirlas a geojson y las fechas a iso_format
 991        Args:
 992            obj_val: valor
 993
 994        Returns:
 995            object encoded
 996        """
 997        if isinstance(obj_val, m_sdo_geom.sdo_geom):
 998            return obj_val.as_geojson()
 999        elif isinstance(obj_val, (datetime.datetime, datetime.date)):
1000            return obj_val.isoformat()
1001        elif isinstance(obj_val, cx_Oracle.LOB):
1002            return obj_val.read()
1003        else:
1004            return json.JSONEncoder.default(self, obj_val)

Redefine default para tratar las geometrias SDO_GEOM y convertirlas a geojson y las fechas a iso_format

Arguments:
  • obj_val: valor
Returns:

object encoded

class gestor_oracle:
1045class gestor_oracle(object):
1046    """
1047    Clase que gestionará distintas conexiones a Oracle y facilitará operaciones sobre la BBDD
1048    """
1049    tip_number = cx_Oracle.NUMBER
1050    tip_string = cx_Oracle.STRING
1051    tip_clob = cx_Oracle.CLOB
1052    tip_blob = cx_Oracle.BLOB
1053    tip_date = cx_Oracle.DATETIME
1054    tip_fix_char = cx_Oracle.FIXED_CHAR
1055
1056    __slots__ = 'nom_con_db', '__con_db__', '__user_con_db__', \
1057        '__psw_con_db__', '__dsn_ora__', '__call_timeout__', '__schema_con_db__', 'logger'
1058
1059    def __init__(self, user_ora, psw_ora, dsn_ora, a_logger=None, call_timeout: int = None, schema_ora=None):
1060        """
1061        Inicializa gestor de Oracle para una conexion cx_Oracle a Oracle
1062        Se puede pasar por parametro un logger o inicializar por defecto
1063
1064        Args:
1065            user_ora {str}: Usuario/schema Oracle
1066            psw_ora {str}: Password usuario
1067            dsn_ora {str}: DSN Oracle (Nombre instancia/datasource de Oracle
1068                    según TSN o string tal cual devuelve cx_Oracle.makedsn())
1069            call_timeout (int=None): miliseconds espera per transaccio
1070            a_logger:
1071            schema_ora(str=None): indicate scheme when it is different from the user, default schema = user
1072        """
1073        self.__con_db__ = None
1074        self.__call_timeout__ = call_timeout
1075        self.logger = a_logger
1076        self.__set_logger()
1077        self.__set_conexion(user_ora, psw_ora, dsn_ora, schema_ora=schema_ora)
1078
1079    def __del__(self):
1080        """
1081        Cierra la conexion al matar la instancia
1082        """
1083        try:
1084            if hasattr(self, "__con_db__"):
1085                self.__con_db__.close()
1086        except:
1087            pass
1088
1089    def __repr__(self):
1090        """
1091        built_in que actua cuando se representa clase como STRING
1092
1093        Returns:
1094            str
1095        """
1096        repr_txt = "{}".format(self.nom_con_db)
1097
1098        return repr_txt
1099
1100    @staticmethod
1101    def log_dir():
1102        """
1103        Devuelve el directorio donde irán los logs indicado en la funcion apb_logging.logs_dir()
1104
1105        Returns:
1106            {str} - path del directorio de logs
1107        """
1108        return utils_logging.logs_dir(True)
1109
1110    def log_name(self):
1111        """
1112        Devuelve el nombre del fichero de log por defecto
1113
1114        Returns:
1115            {str} - Nombre fichero log por defecto
1116        """
1117        return self.__class__.__name__
1118
1119    def log_file_name(self):
1120        return "{}.(LOG_LEVEL).log".format(os.path.join(self.log_dir(), self.log_name()))
1121
1122    def __set_logger(self):
1123        """
1124        Asigna el LOGGER po defecto si este no se ha informado al inicializar el gestor
1125
1126        Returns:
1127        """
1128        if self.logger is None:
1129            self.logger = utils_logging.get_file_logger(self.log_name(), dir_log=self.log_dir())
1130
1131    def path_logs(self, if_exist=True):
1132        """
1133        Devuelve lista paths base de los logs vinculados al gestor
1134
1135        Args:
1136            if_exist (bool): Devuelve los paths si el fichero existe
1137
1138        Returns:
1139            list:
1140        """
1141        return logger_path_logs(self.logger)
1142
1143    def print_log(self, msg):
1144        """
1145        Sobre el logger escribe mensaje de info
1146
1147        Args:
1148            msg {str}: String con el mensaje
1149        """
1150        self.logger.info(msg)
1151
1152    def print_log_error(self, msg):
1153        """
1154        Sobre el logger escribe mensaje de error
1155
1156        Args:
1157            msg {str}: String con el mensaje
1158        """
1159        self.logger.error(msg)
1160
1161    def print_log_exception(self, msg):
1162        """
1163        Sobre el logger escribe excepcion
1164
1165        Args:
1166            msg {str}: String con el mensaje
1167        """
1168        self.logger.exception(msg)
1169
1170    @print_to_log_exception(lanzar_exc=True)
1171    def __set_conexion(self, user_ora, psw_ora, dsn_ora, schema_ora=None):
1172        """
1173        Añade conexion Oracle al gestor a partir de nombre de usuario/schema (user_ora), contraseña (psw_ora) y
1174        nombre datasource de la bbdd según tns_names (ds_ora).
1175
1176        La conexión quedará registrada como 'user_ora@ds_ora'
1177
1178        Args:
1179            user_ora {str}: Usuario/schema Oracle
1180            psw_ora {str}: Password usuario
1181            dsn_ora {str}: DSN Oracle (Nombre instancia/datasource de Oracle según TSN o string tal cual devuelve cx_Oracle.makedsn())
1182            schema_ora(str=None): indicate scheme when it is different from the user, default schema = user
1183
1184        """
1185        nom_con = "@".join((user_ora.upper(), dsn_ora.upper()))
1186        self.nom_con_db = nom_con
1187        self.__user_con_db__ = user_ora
1188        self.__psw_con_db__ = psw_ora
1189        self.__schema_con_db__ = schema_ora
1190        self.__dsn_ora__ = dsn_ora
1191        self.__con_db__ = get_oracle_connection(user_ora, psw_ora, dsn_ora, self.__call_timeout__, schema_ora=schema_ora)
1192
1193    @property
1194    @print_to_log_exception(cx_Oracle.Error, lanzar_exc=True)
1195    def con_db(self):
1196        """
1197        Return a cx_Oracle Conection live
1198        
1199        Returns:
1200            cx_Oracle.Connection
1201        """
1202        reconnect = False
1203        if (con_ora := self.__con_db__) is not None:
1204            try:
1205                con_ora.ping()
1206            except cx_Oracle.Error as exc:
1207                # Borramos las entradas de cache asociadas a la conexión que no responde
1208                del_cache_rel_con_db(get_nom_conexion(con_ora))
1209                try:
1210                    con_ora.close()
1211                except cx_Oracle.Error:
1212                    pass
1213                self.__con_db__ = con_ora = None
1214
1215        if con_ora is None:
1216            self.__set_conexion(
1217                self.__user_con_db__,
1218                self.__psw_con_db__,
1219                self.__dsn_ora__, schema_ora=self.__schema_con_db__)
1220
1221            con_ora = self.__con_db__
1222
1223        return con_ora
1224
1225    @print_to_log_exception(cx_Oracle.DatabaseError)
1226    def exec_trans_db(self, sql_str, *args_sql, **types_sql_args):
1227        """
1228        Ejecuta transaccion SQL
1229
1230        Args:
1231            sql_str (str): sql transaction (update, insert, delete}
1232            *args_sql: Lista argumentos a pasar
1233            **types_sql_args (OPCIONAL): Lista tipos cx_Oracle para cada argumento
1234
1235        Returns:
1236            ok {bool}: Si ha ido bien True si no False
1237        """
1238        curs_db = None
1239        try:
1240            curs_db = new_cursor(self.con_db,
1241                                 input_handler=m_sdo_geom.get_sdo_input_handler())
1242
1243            curs_db.setinputsizes(*types_sql_args.values())
1244
1245            curs_db.execute(sql_str,
1246                            args_sql)
1247        finally:
1248            if curs_db:
1249                curs_db.close()
1250
1251        return True
1252
1253    @print_to_log_exception(cx_Oracle.DatabaseError)
1254    def exec_script_plsql(self, sql_str):
1255        """
1256        Ejecuta script SQL
1257
1258        Args:
1259            sql_str {str}: sql script
1260
1261        Returns:
1262            ok {bool}: Si ha ido bien True si no False
1263        """
1264        curs_db = None
1265        try:
1266            curs_db = new_cursor(self.con_db)
1267            curs_db.execute(sql_str)
1268        finally:
1269            if curs_db:
1270                curs_db.close()
1271
1272        return True
1273
1274    @print_to_log_exception(cx_Oracle.DatabaseError)
1275    def callfunc_sql(self, nom_func, ret_cx_ora_tipo, *args_func):
1276        """
1277        Ejecuta funcion PL/SQL y retorna el valor
1278
1279        Args:
1280            nom_func (str): Nombre de la funcion PL/SQL
1281            ret_cx_ora_tipo (cx_Oracle TIPO): El retorno de la función en cx_Oracle (cx_Oracle.NUMBER,
1282                                            cx_Oracle.STRING,...)
1283            *args_func: Argumentos de la funcion PL/SQL
1284
1285        Returns:
1286            Valor retornado por la función PL/SQL
1287        """
1288        curs = None
1289        try:
1290            curs = new_cursor(self.con_db)
1291            ret = curs.callfunc(nom_func,
1292                                ret_cx_ora_tipo,
1293                                args_func)
1294        finally:
1295            if curs:
1296                curs.close()
1297
1298        return ret
1299
1300    @print_to_log_exception(cx_Oracle.DatabaseError)
1301    def callproc_sql(self, nom_proc, *args_proc):
1302        """
1303        Ejecuta procedimiento PL/SQL
1304
1305        Args:
1306            nom_proc (str): Nombre del procedimiento PL/SQL
1307            *args_proc: Argumentos del procedimiento
1308
1309        Returns:
1310            ok {bool}: Si ha ido bien True si no False
1311        """
1312        curs = None
1313        try:
1314            curs = new_cursor(self.con_db)
1315            curs.callproc(nom_proc,
1316                          args_proc)
1317        finally:
1318            if curs:
1319                curs.close()
1320
1321        return True
1322
1323    @print_to_log_exception(cx_Oracle.DatabaseError)
1324    def row_sql(self, sql_str, *args_sql, **extra_params):
1325        """
1326        Retorna la fila resultante de la query sql SQL_STR con los parámetros *ARGS_SQL.
1327        Se puede informar **EXTRA_PARAMS con algunos de los siguientes parámetros:
1328            INPUT_HANDLER funcion que tratará los bindings de manera específica
1329            OUTPUT_HANLER funcion que tratará los valores de las columnas de modo específico
1330            ROW_CLASS define que con que clase se devolverá cada fila. Por defecto la que devuleve la funcion
1331                      'apb_cx_oracle_spatial.get_row_class_cursor()'
1332            AS_FORMAT (as_xml, as_json, as_geojson) devuelve fila en el formato especificado. La row_class deberá
1333                        responder a esas funciones
1334
1335        Args:
1336            sql_str:
1337            *args_sql:
1338            **extra_params:
1339
1340        Returns:
1341            object (instancia que debería ser o heredar de las clases row_cursor o row_table)
1342        """
1343        return execute_fetch_sql(self.con_db,
1344                                 sql_str,
1345                                 *args_sql,
1346                                 input_handler=extra_params.pop("input_handler", None),
1347                                 output_handler=extra_params.pop(
1348                                     "output_handler",
1349                                     m_sdo_geom.get_output_handler(self.con_db,
1350                                                                   extra_params.pop("geom_format", None))),
1351                                 row_class=extra_params.pop("row_class", None),
1352                                 **extra_params)
1353
1354    @print_to_log_exception(cx_Oracle.DatabaseError)
1355    def generator_rows_sql(self, sql_str, *args_sql, **extra_params):
1356        """
1357        Ejecuta consulta SQL de forma iterativa retornando cada fila como un objeto row_class (por defecto row_cursor)
1358
1359        Se puede informar **EXTRA_PARAMS con algunos de los siguientes parámetros:
1360            INPUT_HANDLER funcion que tratará los bindings de manera específica
1361            OUTPUT_HANLER funcion que tratará los valores de las columnas de modo específico
1362            ROW_CLASS define que con que clase se devolverá cada fila. Por defecto la que devuleve la funcion
1363                      'apb_cx_oracle_spatial.get_row_class_cursor()'
1364            AS_FORMAT (as_xml, as_json, as_geojson) devuelve fila en el formato especificado. La row_class deberá
1365                        responder a esas funciones
1366
1367        Args:
1368            sql_str:
1369            *args_sql:
1370            **extra_params:
1371
1372        Returns:
1373            object (instancia que debería ser o heredar de las clases row_cursor o row_table)
1374        """
1375        for reg in iter_execute_fetch_sql(self.con_db,
1376                                          sql_str,
1377                                          *args_sql,
1378                                          input_handler=extra_params.pop("input_handler", None),
1379                                          output_handler=extra_params.pop(
1380                                              "output_handler",
1381                                              m_sdo_geom.get_output_handler(self.con_db,
1382                                                                            extra_params.pop("geom_format", None))),
1383                                          row_class=extra_params.pop("row_class", None),
1384                                          **extra_params):
1385            yield reg
1386
1387    def rows_sql(self, sql_str, *args_sql):
1388        """
1389        Vease funcion 'generator_rows_sql()'
1390        Args:
1391            sql_str:
1392            *args_sql:
1393
1394        Returns:
1395            list
1396        """
1397        return list(self.generator_rows_sql(sql_str, *args_sql))
1398
1399    def get_primary_key_table(self, nom_tab_or_view):
1400        """
1401        Retorna lista con las columnas que conforman la primary key de una tabla/vista
1402        Args:
1403            nom_tab_or_view:
1404
1405        Returns:
1406            list con campos clave
1407        """
1408        return get_pk_tab(self.con_db, nom_tab_or_view)
1409
1410    @print_to_log_exception(lanzar_exc=True)
1411    def get_dd_table(self, nom_tab_or_view):
1412        """
1413        Retorna instancia row_table con los tipos para cada columna
1414        Args:
1415            nom_tab_or_view:
1416
1417        Returns:
1418            object de clase row_table con los tipos de cada columna como valores
1419        """
1420        return get_row_desc_tab(self.con_db, nom_tab_or_view)
1421
1422    def generator_rows_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql, **extra_params):
1423        """
1424        Retorna los registros que cumplan con el FILTER_SQL sobre la tabla o vista indicada.
1425
1426        La tabla se puede referenciar en el filtro con el alias 'TAB'
1427
1428        Si el FILTER_SQL utiliza valores por binding (:1, :2,...) estos se indicaran por orden en ARGS_FILTER_SQL
1429
1430        Args:
1431            nom_tab_or_view: Nombre de la tabla o vista sobre la que se hará la consulta
1432            filter_sql: Filtre sobre la taula
1433            args_filter_sql: Valors en ordre de binding per passar
1434            extra_params: Vease generator_rows_sql()
1435
1436        Yields:
1437             row_table: regs. clase row_table (mirar get_row_class_tab())
1438        """
1439        for reg in self.generator_rows_sql(sql_tab(nom_tab_or_view,
1440                                                   filter_sql),
1441                                           *args_filter_sql,
1442                                           row_class=extra_params.pop(
1443                                               "row_class",
1444                                               get_row_class_tab(self.con_db, nom_tab_or_view)),
1445                                           **extra_params):
1446            yield reg
1447
1448    def generator_rows_interact_geom(self, nom_tab, a_sdo_geom, cols_geom=None, geom_format=None):
1449        """
1450        Retorna las filas de una tabla que interactuan con una geometria
1451
1452        Args:
1453            nom_tab: nombre de la tabla
1454            a_sdo_geom: geometria clase sdo_geom
1455            cols_geom (default=None): Lista con nombre de columnas geométricas sobre las que se quiere aplicar filtro
1456            geom_format:
1457        Yields:
1458            row_table: regs. clase row_table (mirar get_row_class_tab())
1459        """
1460        # Uso de " <>'FALSE'" por fallo de Oracle usando "= 'TRUE'"
1461        filter_interact_base = "SDO_ANYINTERACT({camp_geom}, :1) <> 'FALSE'"
1462
1463        if not cols_geom:
1464            cols_geom = get_tips_geom_tab(self.con_db, nom_tab).keys()
1465
1466        if cols_geom:
1467            filter_sql = " OR ".join([filter_interact_base.format(camp_geom=ng) for ng in cols_geom])
1468            for reg in self.generator_rows_table(nom_tab, filter_sql, a_sdo_geom.as_ora_sdo_geometry(),
1469                                                 geom_format=geom_format):
1470                yield reg
1471
1472    def rows_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql):
1473        """
1474        Vease funcion 'generator_rows_table()'
1475
1476        Args:
1477            nom_tab_or_view:
1478            filter_sql:
1479            *args_filter_sql:
1480
1481        Returns:
1482            dict
1483        """
1484        gen_tab = self.generator_rows_table(nom_tab_or_view,
1485                                            filter_sql,
1486                                            *args_filter_sql)
1487        pk_tab = self.get_primary_key_table(nom_tab_or_view)
1488        l_pk = len(pk_tab)
1489        if l_pk == 0:
1490            return [r for r in gen_tab]
1491        else:
1492            def f_key(r):
1493                return getattr(r, pk_tab[0])
1494
1495            if l_pk > 1:
1496                def f_key(r): return tuple(map(lambda nf: getattr(r, nf), pk_tab))
1497            return {f_key(r): r for r in gen_tab}
1498
1499    def row_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql, **extra_params):
1500        """
1501        Retorna primer registro para el FILTER_SQL sobre la tabla o vista indicada
1502        Si el FILTER_SQL utiliza valores por binding (:1, :2,...) estos se indicaran por orden en ARGS_FILTER_SQL
1503
1504        Args:
1505            nom_tab_or_view:
1506            filter_sql:
1507            *args_filter_sql:
1508            **extra_params:
1509
1510        Returns:
1511            object de la clase row_table o especificada en **extra_params['row_class']
1512        """
1513        gen = self.generator_rows_table(nom_tab_or_view,
1514                                        filter_sql,
1515                                        *args_filter_sql,
1516                                        **extra_params)
1517        return next(gen, None)
1518
1519    def row_table_at(self, nom_tab_or_view, *vals_key):
1520        """
1521        Devuelve row_tabla_class para el registro que de la tabla_vista que cumpla con la clave
1522
1523        Args:
1524            nom_tab_or_view:
1525            *vals_key:
1526
1527        Returns:
1528            object de la clase row_table o especificada en **extra_params['row_class']
1529        """
1530        return self.exist_row_tab(nom_tab_or_view,
1531                                  {nc: val for nc, val in zip(self.get_primary_key_table(nom_tab_or_view),
1532                                                              vals_key)})
1533
1534    def test_row_table(self, row_tab, a_sql, *args_sql):
1535        """
1536        Testea un registro de tabla (clase rwo_table) cumpla con sql indicado
1537
1538        Args:
1539            row_tab: registro de tabla en forma de clase row_table
1540            a_sql: string con sql a testear
1541
1542        Returns:
1543            bool: True o False según cumpla con el SQL indicado
1544        """
1545        sql_pk = dict_as_sql_bind_and_params(row_tab.pk_vals())
1546        query_sql = "{} AND ({})".format(sql_pk[0], a_sql)
1547
1548        ret = False
1549        if self.row_table(row_tab.nom_tab, query_sql, *(tuple(sql_pk[1]) + tuple(args_sql))):
1550            ret = True
1551
1552        return ret
1553
1554    def insert_row_tab(self, nom_tab, dict_vals_param=None, dict_vals_str=None, pasar_nulls=False):
1555        """
1556        Inserta registro en la tabla indicada. Los valores para cada columna se pasarán a través de dict_vals_param
1557        como bindings o a través de dict_vals_str directemente en el string del sql ejecutado
1558        Args:
1559            nom_tab: nombre de la tabla
1560            dict_vals_param: diccionario indexado por columnas-valores. Los valores se pasarán como bindings
1561            dict_vals_str: diccionario indexado por columnas-valores a asignar como strings. El valor se pasa
1562                        directamente como asignacion en la senetencia sql
1563            pasar_nulls: (opcional) por defecto False. Indica si los valores NULL se asignarán o no
1564
1565        Returns:
1566            row_table (si genera el registro) o False si va mal la operación
1567        """
1568        if not dict_vals_param:
1569            dict_vals_param = {}
1570        if not dict_vals_str:
1571            dict_vals_str = {}
1572
1573        ora_dict_vals_param = self.get_vals_tab_for_transdb(nom_tab, dict_vals_param, pasar_nulls=pasar_nulls)
1574
1575        if pasar_nulls:
1576            # Se revisa que en un campo geometrico se asigne None y por lo tanto se asigne valor via STR
1577            geoms_null = [ng for ng, val in ora_dict_vals_param.items()
1578                          if not val and self.get_tip_camp_geom(nom_tab, ng)]
1579            if geoms_null:
1580                keys_str = [nc.upper() for nc in dict_vals_str.keys()]
1581                for gn in geoms_null:
1582                    ora_dict_vals_param.pop(gn)
1583                    if gn.upper() not in keys_str:
1584                        dict_vals_str[gn.upper()] = "NULL"
1585
1586        params = []
1587        nom_camps = []
1588        vals_camps = []
1589        for nom_camp, val_camp in ora_dict_vals_param.items():
1590            if val_camp is None:
1591                continue
1592            nom_camps.append(nom_camp)
1593            vals_camps.append(":" + nom_camp)
1594            params.append(val_camp)
1595
1596        for nom_camp, val_camp_str in dict_vals_str.items():
1597            if val_camp_str is None:
1598                continue
1599            nom_camps.append(nom_camp)
1600            vals_camps.append(str(val_camp_str))
1601
1602        row_desc_tab = get_row_desc_tab(self.con_db, nom_tab)
1603        pk_binds = {k: new_cursor(self.con_db).var(ora_tip_camp) for k, ora_tip_camp in row_desc_tab.pk_vals().items()}
1604        if not pk_binds:
1605            pk_binds = {'ROWID': new_cursor(self.con_db).var(cx_Oracle.ROWID)}
1606        str_pk_camps = ",".join(pk_binds.keys())
1607        str_pk_binds = ",".join(list(map(lambda x: ":ret_" + str(x), pk_binds.keys())))
1608        params += list(pk_binds.values())
1609
1610        a_sql_res = f"insert into {nom_tab}({','.join(nom_camps)}) values({','.join(vals_camps)}) " \
1611                    f"returning {str_pk_camps} into {str_pk_binds}"
1612
1613        ok = self.exec_trans_db(a_sql_res, *params)
1614        if ok:
1615            pk_vals = {k: curs_var.getvalue(0)[0] for k, curs_var in pk_binds.items()}
1616            return self.exist_row_tab(nom_tab, pk_vals)
1617
1618        return ok
1619
1620    def update_row_tab(self, nom_tab, dict_clau_reg, dict_vals_param=None, dict_vals_str=None, pasar_nulls=None):
1621        """
1622        Actualiza registro en la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}
1623        Los valores para cada columna se pasarán a través de dict_vals_param como bindings o a través de
1624        dict_vals_str directemente en el string del sql ejecutado
1625
1626        Args:
1627            nom_tab: nombre de la tabla
1628            dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
1629            dict_vals_param: diccionario indexado por columnas-valores. Los valores se pasarán como bindings
1630            dict_vals_str: diccionario indexado por columnas-valores a asignar como strings. El valor se pasa
1631                        directamente como asignacion en la senetencia sql
1632            pasar_nulls: (opcional) por defecto False. Indica si los valores NULL se asignarán o no
1633
1634        Returns:
1635            row_table (si genera el registro) o False si va mal la operación
1636        """
1637        if not dict_vals_param:
1638            dict_vals_param = {}
1639        if not dict_vals_str:
1640            dict_vals_str = {}
1641
1642        ora_dict_clau_reg = self.get_vals_tab_for_transdb(nom_tab, dict_clau_reg)
1643        ora_dict_vals_param = self.get_vals_tab_for_transdb(nom_tab, dict_vals_param, pasar_nulls=pasar_nulls)
1644
1645        if pasar_nulls:
1646            # Se revisa que en un campo geometrico se asigne None y por lo tanto se asigne valor via STR
1647            geoms_null = [ng for ng, val in ora_dict_vals_param.items()
1648                          if not val and self.get_tip_camp_geom(nom_tab, ng)]
1649            if geoms_null:
1650                keys_str = [nc.upper() for nc in dict_vals_str.keys()]
1651                for gn in geoms_null:
1652                    ora_dict_vals_param.pop(gn)
1653                    if gn.upper() not in keys_str:
1654                        dict_vals_str[gn.upper()] = "NULL"
1655
1656        (sql_set_camps, params_set_camps) = dict_as_sql_bind_and_params(ora_dict_vals_param,
1657                                                                        ",", "=")
1658
1659        (query_clau, params_filter) = dict_as_sql_bind_and_params(ora_dict_clau_reg)
1660
1661        params = params_set_camps + params_filter
1662
1663        for nom_camp, val_camp_str in dict_vals_str.items():
1664            if sql_set_camps:
1665                sql_set_camps += " , "
1666            sql_set_camps += nom_camp + "=" + val_camp_str
1667
1668        ok = None
1669        if sql_set_camps:
1670            a_sql_res = f"update {nom_tab} set {sql_set_camps} where {query_clau}"
1671
1672            ok = self.exec_trans_db(a_sql_res, *params)
1673
1674            if ok:
1675                return self.exist_row_tab(nom_tab, dict_clau_reg)
1676
1677        return ok
1678
1679    def remove_row_tab(self, nom_tab, dict_clau_reg):
1680        """
1681        Borra registro en la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}
1682
1683        Args:
1684            nom_tab: nombre de la tabla
1685            dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
1686
1687        Returns:
1688            bool según vaya la operación
1689        """
1690        ora_dict_clau_reg = self.get_vals_tab_for_transdb(nom_tab, dict_clau_reg)
1691
1692        a_sql_tmpl = "delete {nom_tab} where {query_clau}"
1693
1694        (sql_filter, params) = dict_as_sql_bind_and_params(ora_dict_clau_reg)
1695
1696        a_sql_res = a_sql_tmpl.format(nom_tab=nom_tab,
1697                                      query_clau=sql_filter)
1698
1699        return self.exec_trans_db(a_sql_res, *params)
1700
1701    def exist_row_tab(self, nom_tab, dict_clau_reg, **extra_params):
1702        """
1703        Devuelve registro de la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}
1704
1705        Args:
1706            nom_tab: nombre de la tabla
1707            dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
1708            **extra_params:
1709
1710        Returns:
1711            object de la clase row_table o especificada en **extra_params['row_class']
1712        """
1713        ora_dict_clau_reg = self.get_vals_tab_for_transdb(nom_tab, dict_clau_reg)
1714
1715        (filter_sql, params) = dict_as_sql_bind_and_params(ora_dict_clau_reg, "and", "=")
1716
1717        return self.row_table(nom_tab, filter_sql, *params, **extra_params)
1718
1719    def get_vals_tab_for_transdb(self, nom_tab, dict_camps_vals, pasar_nulls=True):
1720        """
1721        Para un tabla y diccionario columnas-valores devuelve diccionario indexado por las columnas con los valores
1722        convertidos a formato cx_Oracle según tipo de cada columna en Oracle
1723        Args:
1724            nom_tab: nombre de la tabla
1725            dict_camps_vals: diccionario indexado por columnas-valor a convertir a formato cx_Oracle
1726            pasar_nulls: (opcional) por defecto convertirá los None a NULL de Oracle
1727
1728        Returns:
1729            dict indexado por columnas con los valores convertidos a tipo cx_Oracle
1730        """
1731        dd_tab = get_row_desc_tab(self.con_db, nom_tab)
1732
1733        # Retorna dict con los campos a pasar por parametro
1734        d_params = {}
1735
1736        # Los nombres de campo siempre se buscarán en mayúsculas
1737        dict_camps_vals = {k.upper(): v for k, v in dict_camps_vals.items()}
1738
1739        for camp, tip_camp in dd_tab.vals().items():
1740            if camp not in dict_camps_vals:
1741                continue
1742
1743            val_camp = dict_camps_vals.get(camp)
1744            if not pasar_nulls and val_camp is None:
1745                continue
1746
1747            if isinstance(val_camp, m_sdo_geom.sdo_geom):
1748                var = val_camp.as_ora_sdo_geometry()
1749            else:
1750                try:
1751                    var = new_cursor(self.con_db).var(tip_camp)
1752                    var.setvalue(0, val_camp)
1753                except:
1754                    var = val_camp
1755
1756            d_params[camp] = var
1757
1758        return d_params
1759
1760    @print_to_log_exception()
1761    def run_sql_script(self, filename):
1762        """
1763        Ejecuta slq script (filename) sobre SQLPLUS
1764
1765        Args:
1766            filename: path del sql script
1767
1768        Returns:
1769
1770        """
1771        user_ora = self.con_db.username
1772        ds_ora = self.con_db.dsn
1773        nom_con = self.nom_con_db
1774        psw_ora = self.__psw_con_db__
1775        if psw_ora is None:
1776            print("ERROR - Conexión '" + nom_con + "' no está añadida al gestor!!")
1777            return
1778
1779        with open(filename, 'rb') as a_file:
1780            a_sql_command = a_file.read()
1781
1782        con_db_str = user_ora + "/" + psw_ora + "@" + ds_ora
1783        sqlplus = Popen(['sqlplus', '-S', con_db_str], stdin=PIPE, stdout=PIPE, stderr=PIPE)
1784        # sqlplus.stdin.write(a_sql_command)
1785
1786        (stdout, stderr) = sqlplus.communicate(a_sql_command)
1787
1788        self.print_log("Resultado lanzar script '{}': \n"
1789                       "{}".format(filename,
1790                                   stdout.decode("utf-8")))
1791
1792        if sqlplus is not None:
1793            sqlplus.terminate()
1794
1795    @staticmethod
1796    def get_nom_obj_sql(nom_base, prefix="", sufix=""):
1797        """
1798        Retorna nombre propuesto con prefijo/sufijos formateado para que cumpla longitud máxima de 32 caracteres en
1799        objetos sql Oracle
1800
1801        Args:
1802            nom_base: nombre propuesto
1803            prefix: (opc) prefijo
1804            sufix: (opc) sufijo
1805
1806        Returns:
1807            str formateado
1808        """
1809        return x_sql_parser.get_nom_obj_sql(nom_base, prefix, sufix)
1810
1811    def iter_sdo_gtypes_vals_camp_tab(self, nom_taula, nom_camp):
1812        """
1813        Retorna los distintos tipos de Geometria (codigo entero que define el tipo SDO_GTYPE)
1814        que se encuentran dentro de la columna sdo_geometry de una tabla
1815
1816        Args:
1817            nom_taula: nombre de la tabla
1818            nom_camp: nombre campo geometrico
1819
1820        Returns:
1821            int definiendo tipo de geometría
1822        """
1823        sql_tip_geoms = f"select distinct(tab.{nom_camp}.Get_GType()) as tip_geom from {nom_taula} tab " \
1824                        f"where {nom_camp} is not null"
1825
1826        for reg in self.generator_rows_sql(sql_tip_geoms):
1827            yield reg.TIP_GEOM
1828
1829    def iter_distinct_vals_camp_tab(self, nom_taula, nom_camp, filter_sql=None):
1830        """
1831        Retorna los distintos valores de la columna de una tabla
1832
1833        Args:
1834            nom_taula (str): Nombre de tabla
1835            nom_camp (str): Nombre de campo
1836            filter_sql(str): Filtro SQL sobre la tabla indicada
1837
1838        Returns:
1839            {str}: Itera los distintos valores encontrados en el campo indicado
1840        """
1841        sql_distinct_vals = f"select distinct(tab.{nom_camp}) as VAL from {nom_taula} tab"
1842        if filter_sql:
1843            sql_distinct_vals += " where " + filter_sql
1844
1845        for reg in self.generator_rows_sql(sql_distinct_vals):
1846            yield reg.VAL
1847
1848    def get_tip_camp_geom(self, nom_tab_or_view, nom_camp_geom):
1849        """
1850        Retorna el tipo de campo geométrico (class_tip_geom) registrados en la global __class_tips_geom_ora
1851        para el campo indicado
1852
1853        Args:
1854            nom_tab_or_view: nombre tabla/vista
1855            nom_camp_geom: nombre campo geom
1856
1857        Returns:
1858            namedtuple: con atributos ['TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID'])
1859        """
1860        tips_geom_tab = get_tips_geom_tab(self.con_db, nom_tab_or_view)
1861        if tips_geom_tab:
1862            return tips_geom_tab.get(nom_camp_geom.upper())
1863
1864    def get_epsg_for_srid(self, srid):
1865        """
1866        Rertorna WKT con la definicion del SRID dado
1867        """
1868        return self.callfunc_sql('SDO_CS.MAP_ORACLE_SRID_TO_EPSG', cx_Oracle.NUMBER, srid)
1869
1870    def get_gtype_camp_geom(self, nom_tab_or_view, nom_camp_geom):
1871        """
1872        Retorna el tipo GTYPE (int) de la geometria
1873
1874        Args:
1875            nom_tab_or_view: nombre tabla/vista
1876            nom_camp_geom: nombre campo geom
1877
1878        Returns:
1879            int
1880        """
1881        gtype = 0
1882        g_tip_ora = self.get_tip_camp_geom(nom_tab_or_view, nom_camp_geom)
1883        if g_tip_ora:
1884            gtype = GTYPES_ORA.index(g_tip_ora.GTYPE)
1885
1886        return gtype
1887
1888    @staticmethod
1889    def verificar_path_vector_file(nom_tab_or_view, dir, file_name, ext, zipped):
1890        """
1891        Compone el/los path para el/los vector_file de una tabla y determina si exists
1892        Args:
1893            nom_tab_or_view:
1894            dir:
1895            file_name:
1896            ext:
1897            zipped:
1898
1899        Returns:
1900            file_path (str), file_path_zip (str), exists (bool)
1901        """
1902        if file_name and not file_name.endswith(ext):
1903            file_name = ".".join((file_name, ext))
1904        elif not file_name:
1905            file_name = ".".join((nom_tab_or_view, ext)).lower()
1906
1907        file_path = os.path.join(dir, file_name)
1908        file_path_zip = None
1909        if zipped:
1910            file_path_zip = "{}.zip".format(os.path.splitext(file_path)[0])
1911
1912        exists = (os.path.exists(file_path) and not file_path_zip) or (file_path_zip and os.path.exists(file_path_zip))
1913
1914        return file_path, file_path_zip, exists
1915
1916    @print_to_log_exception()
1917    def create_json_tab_or_view(self, nom_tab_or_view, dir='.', file_name=None, overwrite=True, cols=None, zipped=True,
1918                                filter_sql=None, *args_sql):
1919        """
1920
1921        Args:
1922            nom_tab_or_view (str): Nombre tabla o vista
1923            dir (str):
1924            file_name (str):
1925            overwrite (bool):
1926            cols (list): columnas
1927            zipped (bool):
1928            filter_sql (str):
1929            *args_sql: lista de argumentos a pasar al filtro sql
1930        Returns:
1931            file_path (str)
1932        """
1933        file_path, file_path_zip, exists = self.verificar_path_vector_file(
1934            nom_tab_or_view, dir, file_name, "json", zipped)
1935
1936        if overwrite or not exists:
1937            # Se calculan las columnas para hacer get de la fila con el orden en las columnas de la tabla
1938            if not cols:
1939                dd_tab = self.get_dd_table(nom_tab_or_view)
1940                cols = dd_tab.cols
1941            sql = sql_tab(nom_tab_or_view,
1942                          filter_sql=filter_sql,
1943                          columns=cols)
1944
1945            file_path_res = vector_file_from_gen_ora_sql(file_path, "json", self.generator_rows_sql(sql, *args_sql),
1946                                                         zipped=zipped)
1947        else:
1948            file_path_res = file_path if not zipped else file_path_zip
1949
1950        return file_path_res
1951
1952    @print_to_log_exception()
1953    def create_csv_tab_or_view(self, nom_tab_or_view, dir='.', file_name=None, overwrite=True, cols=None, zipped=True,
1954                               filter_sql=None, *args_sql):
1955        """
1956
1957        Args:
1958            nom_tab_or_view (str): Nombre tabla o vista
1959            dir (str):
1960            file_name (str):
1961            overwrite (bool):
1962            cols (list): columnas
1963            zipped (bool):
1964            filter_sql (str):
1965            *args_sql: lista de argumentos a pasar al filtro sql
1966
1967        Returns:
1968            file_path_res (str)
1969        """
1970        file_path, file_path_zip, exists = self.verificar_path_vector_file(
1971            nom_tab_or_view, dir, file_name, "csv", zipped)
1972
1973        if overwrite or not exists:
1974            if not cols:
1975                dd_tab = self.get_dd_table(nom_tab_or_view)
1976                cols = dd_tab.cols
1977            sql = sql_tab(nom_tab_or_view,
1978                          filter_sql=filter_sql,
1979                          columns=cols)
1980
1981            # Para el formato geocsv que acepta GDAL se añade fichero con los tipos de columna
1982            tip_cols_csv = []
1983            for col in cols:
1984                r_tip_col = self.row_table("user_tab_columns",
1985                                           "table_name = :1 and column_name = :2",
1986                                           nom_tab_or_view.upper(), col.upper())
1987                dtype = r_tip_col.DATA_TYPE
1988                dlength = r_tip_col.DATA_LENGTH
1989                dprecision = r_tip_col.DATA_PRECISION
1990                dscale = r_tip_col.DATA_SCALE
1991
1992                if dtype == "DATE":
1993                    tip_cols_csv.append('"DateTime"')
1994                elif dtype == "FLOAT":
1995                    tip_cols_csv.append('"Real({}.{})"'.format(dlength, dprecision))
1996                elif dtype == "NUMBER":
1997                    if dscale and dscale != 0:
1998                        tip_cols_csv.append('"Real({}.{})"'.format(dprecision, dscale))
1999                    elif dprecision:
2000                        tip_cols_csv.append('"Integer({})"'.format(dprecision))
2001                    else:
2002                        tip_cols_csv.append('"Real(10.8)"')
2003                elif dtype == "SDO_GEOMETRY":
2004                    tip_cols_csv.append('"WKT"')
2005                else:
2006                    tip_cols_csv.append('"String({})"'.format(round(dlength * 1.25)))
2007
2008            file_path_res = vector_file_from_gen_ora_sql(file_path, "csv",
2009                                                         self.generator_rows_sql(sql, *args_sql, geom_format="as_wkt"),
2010                                                         zipped=zipped, cols_csv=cols, tip_cols_csv=tip_cols_csv)
2011        else:
2012            file_path_res = file_path if not zipped else file_path_zip
2013
2014        return file_path_res
2015
2016    @print_to_log_exception()
2017    def create_geojsons_tab_or_view(self, nom_tab_or_view, dir='.', file_name_prefix=None, by_geom=False,
2018                                    dir_topojson=None, overwrite=True, cols=None,
2019                                    filter_sql=None, *args_sql):
2020        """
2021
2022        Args:
2023            nom_tab_or_view (str): Nombre tabla (vigente o versionada) para entidad GIS
2024            dir (str="."):
2025            file_name_prefix (str=None): (opcional) prefijo del fichero
2026            by_geom (bool=False): (Opcional) si se querrán los geojsons por geometria. Si no se saca un unico geojson con
2027                            la columna geometry como una GeometryCollection si la tabla es multigeom
2028            dir_topojson (str=None): path donde irán las conversiones
2029            overwrite (bool=True):
2030            cols (list=None):
2031            filter_sql (str=None):
2032            *args_sql: lista de argumentos a pasar al filtro sql
2033
2034        Returns:
2035            ok (bool)
2036        """
2037        ext = "geo.json"
2038        sqls = {}
2039        dd_tab = self.get_dd_table(nom_tab_or_view)
2040        if not cols:
2041            cols = dd_tab.cols
2042
2043        if by_geom:
2044            c_alfas = [cn for cn in dd_tab.alfas() if cn in cols]
2045            c_geoms = [cn for cn in dd_tab.geoms() if cn in cols]
2046            for c_geom in c_geoms:
2047                sqls[c_geom] = sql_tab(nom_tab_or_view, filter_sql, c_alfas + [c_geom])
2048        else:
2049            sqls[None] = sql_tab(nom_tab_or_view, filter_sql, cols)
2050
2051        if not file_name_prefix:
2052            file_name_prefix = nom_tab_or_view
2053
2054        for ng, sql in sqls.items():
2055            file_name = file_name_prefix
2056            if ng:
2057                file_name = "-".join((file_name, ng))
2058
2059            file_name = ".".join((file_name, ext)).lower()
2060            file_path = os.path.join(dir, file_name)
2061
2062            if overwrite or not os.path.exists(file_path):
2063                file_path = vector_file_from_gen_ora_sql(file_path, "geojson",
2064                                                         self.generator_rows_sql(sql, *args_sql))
2065
2066            if by_geom and dir_topojson and file_path:
2067                tip_geom = getattr(dd_tab, ng).GTYPE
2068                simplify = True
2069                if tip_geom.endswith("POINT"):
2070                    simplify = False
2071
2072                topojson_utils.geojson_to_topojson(file_path, dir_topojson,
2073                                                   simplify=simplify,
2074                                                   overwrite=overwrite)
2075
2076        return True

Clase que gestionará distintas conexiones a Oracle y facilitará operaciones sobre la BBDD

gestor_oracle( user_ora, psw_ora, dsn_ora, a_logger=None, call_timeout: int = None, schema_ora=None)
1059    def __init__(self, user_ora, psw_ora, dsn_ora, a_logger=None, call_timeout: int = None, schema_ora=None):
1060        """
1061        Inicializa gestor de Oracle para una conexion cx_Oracle a Oracle
1062        Se puede pasar por parametro un logger o inicializar por defecto
1063
1064        Args:
1065            user_ora {str}: Usuario/schema Oracle
1066            psw_ora {str}: Password usuario
1067            dsn_ora {str}: DSN Oracle (Nombre instancia/datasource de Oracle
1068                    según TSN o string tal cual devuelve cx_Oracle.makedsn())
1069            call_timeout (int=None): miliseconds espera per transaccio
1070            a_logger:
1071            schema_ora(str=None): indicate scheme when it is different from the user, default schema = user
1072        """
1073        self.__con_db__ = None
1074        self.__call_timeout__ = call_timeout
1075        self.logger = a_logger
1076        self.__set_logger()
1077        self.__set_conexion(user_ora, psw_ora, dsn_ora, schema_ora=schema_ora)

Inicializa gestor de Oracle para una conexion cx_Oracle a Oracle Se puede pasar por parametro un logger o inicializar por defecto

Arguments:
  • user_ora {str}: Usuario/schema Oracle
  • psw_ora {str}: Password usuario
  • dsn_ora {str}: DSN Oracle (Nombre instancia/datasource de Oracle según TSN o string tal cual devuelve cx_Oracle.makedsn())
  • call_timeout (int=None): miliseconds espera per transaccio
  • a_logger:
  • schema_ora(str=None): indicate scheme when it is different from the user, default schema = user
tip_number = <cx_Oracle.ApiType NUMBER>
tip_string = <cx_Oracle.ApiType STRING>
tip_clob = <cx_Oracle.DbType DB_TYPE_CLOB>
tip_blob = <cx_Oracle.DbType DB_TYPE_BLOB>
tip_date = <cx_Oracle.ApiType DATETIME>
tip_fix_char = <cx_Oracle.DbType DB_TYPE_CHAR>
logger
@staticmethod
def log_dir():
1100    @staticmethod
1101    def log_dir():
1102        """
1103        Devuelve el directorio donde irán los logs indicado en la funcion apb_logging.logs_dir()
1104
1105        Returns:
1106            {str} - path del directorio de logs
1107        """
1108        return utils_logging.logs_dir(True)

Devuelve el directorio donde irán los logs indicado en la funcion apb_logging.logs_dir()

Returns:

{str} - path del directorio de logs

def log_name(self):
1110    def log_name(self):
1111        """
1112        Devuelve el nombre del fichero de log por defecto
1113
1114        Returns:
1115            {str} - Nombre fichero log por defecto
1116        """
1117        return self.__class__.__name__

Devuelve el nombre del fichero de log por defecto

Returns:

{str} - Nombre fichero log por defecto

def log_file_name(self):
1119    def log_file_name(self):
1120        return "{}.(LOG_LEVEL).log".format(os.path.join(self.log_dir(), self.log_name()))
def path_logs(self, if_exist=True):
1131    def path_logs(self, if_exist=True):
1132        """
1133        Devuelve lista paths base de los logs vinculados al gestor
1134
1135        Args:
1136            if_exist (bool): Devuelve los paths si el fichero existe
1137
1138        Returns:
1139            list:
1140        """
1141        return logger_path_logs(self.logger)

Devuelve lista paths base de los logs vinculados al gestor

Arguments:
  • if_exist (bool): Devuelve los paths si el fichero existe
Returns:

list:

def print_log(self, msg):
1143    def print_log(self, msg):
1144        """
1145        Sobre el logger escribe mensaje de info
1146
1147        Args:
1148            msg {str}: String con el mensaje
1149        """
1150        self.logger.info(msg)

Sobre el logger escribe mensaje de info

Arguments:
  • msg {str}: String con el mensaje
def print_log_error(self, msg):
1152    def print_log_error(self, msg):
1153        """
1154        Sobre el logger escribe mensaje de error
1155
1156        Args:
1157            msg {str}: String con el mensaje
1158        """
1159        self.logger.error(msg)

Sobre el logger escribe mensaje de error

Arguments:
  • msg {str}: String con el mensaje
def print_log_exception(self, msg):
1161    def print_log_exception(self, msg):
1162        """
1163        Sobre el logger escribe excepcion
1164
1165        Args:
1166            msg {str}: String con el mensaje
1167        """
1168        self.logger.exception(msg)

Sobre el logger escribe excepcion

Arguments:
  • msg {str}: String con el mensaje
con_db
1193    @property
1194    @print_to_log_exception(cx_Oracle.Error, lanzar_exc=True)
1195    def con_db(self):
1196        """
1197        Return a cx_Oracle Conection live
1198        
1199        Returns:
1200            cx_Oracle.Connection
1201        """
1202        reconnect = False
1203        if (con_ora := self.__con_db__) is not None:
1204            try:
1205                con_ora.ping()
1206            except cx_Oracle.Error as exc:
1207                # Borramos las entradas de cache asociadas a la conexión que no responde
1208                del_cache_rel_con_db(get_nom_conexion(con_ora))
1209                try:
1210                    con_ora.close()
1211                except cx_Oracle.Error:
1212                    pass
1213                self.__con_db__ = con_ora = None
1214
1215        if con_ora is None:
1216            self.__set_conexion(
1217                self.__user_con_db__,
1218                self.__psw_con_db__,
1219                self.__dsn_ora__, schema_ora=self.__schema_con_db__)
1220
1221            con_ora = self.__con_db__
1222
1223        return con_ora

Return a cx_Oracle Conection live

Returns:

cx_Oracle.Connection

@print_to_log_exception(cx_Oracle.DatabaseError)
def exec_trans_db(self, sql_str, *args_sql, **types_sql_args):
1225    @print_to_log_exception(cx_Oracle.DatabaseError)
1226    def exec_trans_db(self, sql_str, *args_sql, **types_sql_args):
1227        """
1228        Ejecuta transaccion SQL
1229
1230        Args:
1231            sql_str (str): sql transaction (update, insert, delete}
1232            *args_sql: Lista argumentos a pasar
1233            **types_sql_args (OPCIONAL): Lista tipos cx_Oracle para cada argumento
1234
1235        Returns:
1236            ok {bool}: Si ha ido bien True si no False
1237        """
1238        curs_db = None
1239        try:
1240            curs_db = new_cursor(self.con_db,
1241                                 input_handler=m_sdo_geom.get_sdo_input_handler())
1242
1243            curs_db.setinputsizes(*types_sql_args.values())
1244
1245            curs_db.execute(sql_str,
1246                            args_sql)
1247        finally:
1248            if curs_db:
1249                curs_db.close()
1250
1251        return True

Ejecuta transaccion SQL

Arguments:
  • sql_str (str): sql transaction (update, insert, delete}
  • *args_sql: Lista argumentos a pasar
  • **types_sql_args (OPCIONAL): Lista tipos cx_Oracle para cada argumento
Returns:

ok {bool}: Si ha ido bien True si no False

@print_to_log_exception(cx_Oracle.DatabaseError)
def exec_script_plsql(self, sql_str):
1253    @print_to_log_exception(cx_Oracle.DatabaseError)
1254    def exec_script_plsql(self, sql_str):
1255        """
1256        Ejecuta script SQL
1257
1258        Args:
1259            sql_str {str}: sql script
1260
1261        Returns:
1262            ok {bool}: Si ha ido bien True si no False
1263        """
1264        curs_db = None
1265        try:
1266            curs_db = new_cursor(self.con_db)
1267            curs_db.execute(sql_str)
1268        finally:
1269            if curs_db:
1270                curs_db.close()
1271
1272        return True

Ejecuta script SQL

Arguments:
  • sql_str {str}: sql script
Returns:

ok {bool}: Si ha ido bien True si no False

@print_to_log_exception(cx_Oracle.DatabaseError)
def callfunc_sql(self, nom_func, ret_cx_ora_tipo, *args_func):
1274    @print_to_log_exception(cx_Oracle.DatabaseError)
1275    def callfunc_sql(self, nom_func, ret_cx_ora_tipo, *args_func):
1276        """
1277        Ejecuta funcion PL/SQL y retorna el valor
1278
1279        Args:
1280            nom_func (str): Nombre de la funcion PL/SQL
1281            ret_cx_ora_tipo (cx_Oracle TIPO): El retorno de la función en cx_Oracle (cx_Oracle.NUMBER,
1282                                            cx_Oracle.STRING,...)
1283            *args_func: Argumentos de la funcion PL/SQL
1284
1285        Returns:
1286            Valor retornado por la función PL/SQL
1287        """
1288        curs = None
1289        try:
1290            curs = new_cursor(self.con_db)
1291            ret = curs.callfunc(nom_func,
1292                                ret_cx_ora_tipo,
1293                                args_func)
1294        finally:
1295            if curs:
1296                curs.close()
1297
1298        return ret

Ejecuta funcion PL/SQL y retorna el valor

Arguments:
  • nom_func (str): Nombre de la funcion PL/SQL
  • ret_cx_ora_tipo (cx_Oracle TIPO): El retorno de la función en cx_Oracle (cx_Oracle.NUMBER, cx_Oracle.STRING,...)
  • *args_func: Argumentos de la funcion PL/SQL
Returns:

Valor retornado por la función PL/SQL

@print_to_log_exception(cx_Oracle.DatabaseError)
def callproc_sql(self, nom_proc, *args_proc):
1300    @print_to_log_exception(cx_Oracle.DatabaseError)
1301    def callproc_sql(self, nom_proc, *args_proc):
1302        """
1303        Ejecuta procedimiento PL/SQL
1304
1305        Args:
1306            nom_proc (str): Nombre del procedimiento PL/SQL
1307            *args_proc: Argumentos del procedimiento
1308
1309        Returns:
1310            ok {bool}: Si ha ido bien True si no False
1311        """
1312        curs = None
1313        try:
1314            curs = new_cursor(self.con_db)
1315            curs.callproc(nom_proc,
1316                          args_proc)
1317        finally:
1318            if curs:
1319                curs.close()
1320
1321        return True

Ejecuta procedimiento PL/SQL

Arguments:
  • nom_proc (str): Nombre del procedimiento PL/SQL
  • *args_proc: Argumentos del procedimiento
Returns:

ok {bool}: Si ha ido bien True si no False

@print_to_log_exception(cx_Oracle.DatabaseError)
def row_sql(self, sql_str, *args_sql, **extra_params):
1323    @print_to_log_exception(cx_Oracle.DatabaseError)
1324    def row_sql(self, sql_str, *args_sql, **extra_params):
1325        """
1326        Retorna la fila resultante de la query sql SQL_STR con los parámetros *ARGS_SQL.
1327        Se puede informar **EXTRA_PARAMS con algunos de los siguientes parámetros:
1328            INPUT_HANDLER funcion que tratará los bindings de manera específica
1329            OUTPUT_HANLER funcion que tratará los valores de las columnas de modo específico
1330            ROW_CLASS define que con que clase se devolverá cada fila. Por defecto la que devuleve la funcion
1331                      'apb_cx_oracle_spatial.get_row_class_cursor()'
1332            AS_FORMAT (as_xml, as_json, as_geojson) devuelve fila en el formato especificado. La row_class deberá
1333                        responder a esas funciones
1334
1335        Args:
1336            sql_str:
1337            *args_sql:
1338            **extra_params:
1339
1340        Returns:
1341            object (instancia que debería ser o heredar de las clases row_cursor o row_table)
1342        """
1343        return execute_fetch_sql(self.con_db,
1344                                 sql_str,
1345                                 *args_sql,
1346                                 input_handler=extra_params.pop("input_handler", None),
1347                                 output_handler=extra_params.pop(
1348                                     "output_handler",
1349                                     m_sdo_geom.get_output_handler(self.con_db,
1350                                                                   extra_params.pop("geom_format", None))),
1351                                 row_class=extra_params.pop("row_class", None),
1352                                 **extra_params)

Retorna la fila resultante de la query sql SQL_STR con los parámetros *ARGS_SQL. Se puede informar **EXTRA_PARAMS con algunos de los siguientes parámetros: INPUT_HANDLER funcion que tratará los bindings de manera específica OUTPUT_HANLER funcion que tratará los valores de las columnas de modo específico ROW_CLASS define que con que clase se devolverá cada fila. Por defecto la que devuleve la funcion 'apb_cx_oracle_spatial.get_row_class_cursor()' AS_FORMAT (as_xml, as_json, as_geojson) devuelve fila en el formato especificado. La row_class deberá responder a esas funciones

Arguments:
  • sql_str:
  • *args_sql:
  • **extra_params:
Returns:

object (instancia que debería ser o heredar de las clases row_cursor o row_table)

@print_to_log_exception(cx_Oracle.DatabaseError)
def generator_rows_sql(self, sql_str, *args_sql, **extra_params):
1354    @print_to_log_exception(cx_Oracle.DatabaseError)
1355    def generator_rows_sql(self, sql_str, *args_sql, **extra_params):
1356        """
1357        Ejecuta consulta SQL de forma iterativa retornando cada fila como un objeto row_class (por defecto row_cursor)
1358
1359        Se puede informar **EXTRA_PARAMS con algunos de los siguientes parámetros:
1360            INPUT_HANDLER funcion que tratará los bindings de manera específica
1361            OUTPUT_HANLER funcion que tratará los valores de las columnas de modo específico
1362            ROW_CLASS define que con que clase se devolverá cada fila. Por defecto la que devuleve la funcion
1363                      'apb_cx_oracle_spatial.get_row_class_cursor()'
1364            AS_FORMAT (as_xml, as_json, as_geojson) devuelve fila en el formato especificado. La row_class deberá
1365                        responder a esas funciones
1366
1367        Args:
1368            sql_str:
1369            *args_sql:
1370            **extra_params:
1371
1372        Returns:
1373            object (instancia que debería ser o heredar de las clases row_cursor o row_table)
1374        """
1375        for reg in iter_execute_fetch_sql(self.con_db,
1376                                          sql_str,
1377                                          *args_sql,
1378                                          input_handler=extra_params.pop("input_handler", None),
1379                                          output_handler=extra_params.pop(
1380                                              "output_handler",
1381                                              m_sdo_geom.get_output_handler(self.con_db,
1382                                                                            extra_params.pop("geom_format", None))),
1383                                          row_class=extra_params.pop("row_class", None),
1384                                          **extra_params):
1385            yield reg

Ejecuta consulta SQL de forma iterativa retornando cada fila como un objeto row_class (por defecto row_cursor)

Se puede informar **EXTRA_PARAMS con algunos de los siguientes parámetros: INPUT_HANDLER funcion que tratará los bindings de manera específica OUTPUT_HANLER funcion que tratará los valores de las columnas de modo específico ROW_CLASS define que con que clase se devolverá cada fila. Por defecto la que devuleve la funcion 'apb_cx_oracle_spatial.get_row_class_cursor()' AS_FORMAT (as_xml, as_json, as_geojson) devuelve fila en el formato especificado. La row_class deberá responder a esas funciones

Arguments:
  • sql_str:
  • *args_sql:
  • **extra_params:
Returns:

object (instancia que debería ser o heredar de las clases row_cursor o row_table)

def rows_sql(self, sql_str, *args_sql):
1387    def rows_sql(self, sql_str, *args_sql):
1388        """
1389        Vease funcion 'generator_rows_sql()'
1390        Args:
1391            sql_str:
1392            *args_sql:
1393
1394        Returns:
1395            list
1396        """
1397        return list(self.generator_rows_sql(sql_str, *args_sql))

Vease funcion 'generator_rows_sql()'

Arguments:
  • sql_str:
  • *args_sql:
Returns:

list

def get_primary_key_table(self, nom_tab_or_view):
1399    def get_primary_key_table(self, nom_tab_or_view):
1400        """
1401        Retorna lista con las columnas que conforman la primary key de una tabla/vista
1402        Args:
1403            nom_tab_or_view:
1404
1405        Returns:
1406            list con campos clave
1407        """
1408        return get_pk_tab(self.con_db, nom_tab_or_view)

Retorna lista con las columnas que conforman la primary key de una tabla/vista

Arguments:
  • nom_tab_or_view:
Returns:

list con campos clave

@print_to_log_exception(lanzar_exc=True)
def get_dd_table(self, nom_tab_or_view):
1410    @print_to_log_exception(lanzar_exc=True)
1411    def get_dd_table(self, nom_tab_or_view):
1412        """
1413        Retorna instancia row_table con los tipos para cada columna
1414        Args:
1415            nom_tab_or_view:
1416
1417        Returns:
1418            object de clase row_table con los tipos de cada columna como valores
1419        """
1420        return get_row_desc_tab(self.con_db, nom_tab_or_view)

Retorna instancia row_table con los tipos para cada columna

Arguments:
  • nom_tab_or_view:
Returns:

object de clase row_table con los tipos de cada columna como valores

def generator_rows_table( self, nom_tab_or_view, filter_sql=None, *args_filter_sql, **extra_params):
1422    def generator_rows_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql, **extra_params):
1423        """
1424        Retorna los registros que cumplan con el FILTER_SQL sobre la tabla o vista indicada.
1425
1426        La tabla se puede referenciar en el filtro con el alias 'TAB'
1427
1428        Si el FILTER_SQL utiliza valores por binding (:1, :2,...) estos se indicaran por orden en ARGS_FILTER_SQL
1429
1430        Args:
1431            nom_tab_or_view: Nombre de la tabla o vista sobre la que se hará la consulta
1432            filter_sql: Filtre sobre la taula
1433            args_filter_sql: Valors en ordre de binding per passar
1434            extra_params: Vease generator_rows_sql()
1435
1436        Yields:
1437             row_table: regs. clase row_table (mirar get_row_class_tab())
1438        """
1439        for reg in self.generator_rows_sql(sql_tab(nom_tab_or_view,
1440                                                   filter_sql),
1441                                           *args_filter_sql,
1442                                           row_class=extra_params.pop(
1443                                               "row_class",
1444                                               get_row_class_tab(self.con_db, nom_tab_or_view)),
1445                                           **extra_params):
1446            yield reg

Retorna los registros que cumplan con el FILTER_SQL sobre la tabla o vista indicada.

La tabla se puede referenciar en el filtro con el alias 'TAB'

Si el FILTER_SQL utiliza valores por binding (:1, :2,...) estos se indicaran por orden en ARGS_FILTER_SQL

Arguments:
  • nom_tab_or_view: Nombre de la tabla o vista sobre la que se hará la consulta
  • filter_sql: Filtre sobre la taula
  • args_filter_sql: Valors en ordre de binding per passar
  • extra_params: Vease generator_rows_sql()
Yields:

row_table: regs. clase row_table (mirar get_row_class_tab())

def generator_rows_interact_geom(self, nom_tab, a_sdo_geom, cols_geom=None, geom_format=None):
1448    def generator_rows_interact_geom(self, nom_tab, a_sdo_geom, cols_geom=None, geom_format=None):
1449        """
1450        Retorna las filas de una tabla que interactuan con una geometria
1451
1452        Args:
1453            nom_tab: nombre de la tabla
1454            a_sdo_geom: geometria clase sdo_geom
1455            cols_geom (default=None): Lista con nombre de columnas geométricas sobre las que se quiere aplicar filtro
1456            geom_format:
1457        Yields:
1458            row_table: regs. clase row_table (mirar get_row_class_tab())
1459        """
1460        # Uso de " <>'FALSE'" por fallo de Oracle usando "= 'TRUE'"
1461        filter_interact_base = "SDO_ANYINTERACT({camp_geom}, :1) <> 'FALSE'"
1462
1463        if not cols_geom:
1464            cols_geom = get_tips_geom_tab(self.con_db, nom_tab).keys()
1465
1466        if cols_geom:
1467            filter_sql = " OR ".join([filter_interact_base.format(camp_geom=ng) for ng in cols_geom])
1468            for reg in self.generator_rows_table(nom_tab, filter_sql, a_sdo_geom.as_ora_sdo_geometry(),
1469                                                 geom_format=geom_format):
1470                yield reg

Retorna las filas de una tabla que interactuan con una geometria

Arguments:
  • nom_tab: nombre de la tabla
  • a_sdo_geom: geometria clase sdo_geom
  • cols_geom (default=None): Lista con nombre de columnas geométricas sobre las que se quiere aplicar filtro
  • geom_format:
Yields:

row_table: regs. clase row_table (mirar get_row_class_tab())

def rows_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql):
1472    def rows_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql):
1473        """
1474        Vease funcion 'generator_rows_table()'
1475
1476        Args:
1477            nom_tab_or_view:
1478            filter_sql:
1479            *args_filter_sql:
1480
1481        Returns:
1482            dict
1483        """
1484        gen_tab = self.generator_rows_table(nom_tab_or_view,
1485                                            filter_sql,
1486                                            *args_filter_sql)
1487        pk_tab = self.get_primary_key_table(nom_tab_or_view)
1488        l_pk = len(pk_tab)
1489        if l_pk == 0:
1490            return [r for r in gen_tab]
1491        else:
1492            def f_key(r):
1493                return getattr(r, pk_tab[0])
1494
1495            if l_pk > 1:
1496                def f_key(r): return tuple(map(lambda nf: getattr(r, nf), pk_tab))
1497            return {f_key(r): r for r in gen_tab}

Vease funcion 'generator_rows_table()'

Arguments:
  • nom_tab_or_view:
  • filter_sql:
  • *args_filter_sql:
Returns:

dict

def row_table( self, nom_tab_or_view, filter_sql=None, *args_filter_sql, **extra_params):
1499    def row_table(self, nom_tab_or_view, filter_sql=None, *args_filter_sql, **extra_params):
1500        """
1501        Retorna primer registro para el FILTER_SQL sobre la tabla o vista indicada
1502        Si el FILTER_SQL utiliza valores por binding (:1, :2,...) estos se indicaran por orden en ARGS_FILTER_SQL
1503
1504        Args:
1505            nom_tab_or_view:
1506            filter_sql:
1507            *args_filter_sql:
1508            **extra_params:
1509
1510        Returns:
1511            object de la clase row_table o especificada en **extra_params['row_class']
1512        """
1513        gen = self.generator_rows_table(nom_tab_or_view,
1514                                        filter_sql,
1515                                        *args_filter_sql,
1516                                        **extra_params)
1517        return next(gen, None)

Retorna primer registro para el FILTER_SQL sobre la tabla o vista indicada Si el FILTER_SQL utiliza valores por binding (:1, :2,...) estos se indicaran por orden en ARGS_FILTER_SQL

Arguments:
  • nom_tab_or_view:
  • filter_sql:
  • *args_filter_sql:
  • **extra_params:
Returns:

object de la clase row_table o especificada en **extra_params['row_class']

def row_table_at(self, nom_tab_or_view, *vals_key):
1519    def row_table_at(self, nom_tab_or_view, *vals_key):
1520        """
1521        Devuelve row_tabla_class para el registro que de la tabla_vista que cumpla con la clave
1522
1523        Args:
1524            nom_tab_or_view:
1525            *vals_key:
1526
1527        Returns:
1528            object de la clase row_table o especificada en **extra_params['row_class']
1529        """
1530        return self.exist_row_tab(nom_tab_or_view,
1531                                  {nc: val for nc, val in zip(self.get_primary_key_table(nom_tab_or_view),
1532                                                              vals_key)})

Devuelve row_tabla_class para el registro que de la tabla_vista que cumpla con la clave

Arguments:
  • nom_tab_or_view:
  • *vals_key:
Returns:

object de la clase row_table o especificada en **extra_params['row_class']

def test_row_table(self, row_tab, a_sql, *args_sql):
1534    def test_row_table(self, row_tab, a_sql, *args_sql):
1535        """
1536        Testea un registro de tabla (clase rwo_table) cumpla con sql indicado
1537
1538        Args:
1539            row_tab: registro de tabla en forma de clase row_table
1540            a_sql: string con sql a testear
1541
1542        Returns:
1543            bool: True o False según cumpla con el SQL indicado
1544        """
1545        sql_pk = dict_as_sql_bind_and_params(row_tab.pk_vals())
1546        query_sql = "{} AND ({})".format(sql_pk[0], a_sql)
1547
1548        ret = False
1549        if self.row_table(row_tab.nom_tab, query_sql, *(tuple(sql_pk[1]) + tuple(args_sql))):
1550            ret = True
1551
1552        return ret

Testea un registro de tabla (clase rwo_table) cumpla con sql indicado

Arguments:
  • row_tab: registro de tabla en forma de clase row_table
  • a_sql: string con sql a testear
Returns:

bool: True o False según cumpla con el SQL indicado

def insert_row_tab( self, nom_tab, dict_vals_param=None, dict_vals_str=None, pasar_nulls=False):
1554    def insert_row_tab(self, nom_tab, dict_vals_param=None, dict_vals_str=None, pasar_nulls=False):
1555        """
1556        Inserta registro en la tabla indicada. Los valores para cada columna se pasarán a través de dict_vals_param
1557        como bindings o a través de dict_vals_str directemente en el string del sql ejecutado
1558        Args:
1559            nom_tab: nombre de la tabla
1560            dict_vals_param: diccionario indexado por columnas-valores. Los valores se pasarán como bindings
1561            dict_vals_str: diccionario indexado por columnas-valores a asignar como strings. El valor se pasa
1562                        directamente como asignacion en la senetencia sql
1563            pasar_nulls: (opcional) por defecto False. Indica si los valores NULL se asignarán o no
1564
1565        Returns:
1566            row_table (si genera el registro) o False si va mal la operación
1567        """
1568        if not dict_vals_param:
1569            dict_vals_param = {}
1570        if not dict_vals_str:
1571            dict_vals_str = {}
1572
1573        ora_dict_vals_param = self.get_vals_tab_for_transdb(nom_tab, dict_vals_param, pasar_nulls=pasar_nulls)
1574
1575        if pasar_nulls:
1576            # Se revisa que en un campo geometrico se asigne None y por lo tanto se asigne valor via STR
1577            geoms_null = [ng for ng, val in ora_dict_vals_param.items()
1578                          if not val and self.get_tip_camp_geom(nom_tab, ng)]
1579            if geoms_null:
1580                keys_str = [nc.upper() for nc in dict_vals_str.keys()]
1581                for gn in geoms_null:
1582                    ora_dict_vals_param.pop(gn)
1583                    if gn.upper() not in keys_str:
1584                        dict_vals_str[gn.upper()] = "NULL"
1585
1586        params = []
1587        nom_camps = []
1588        vals_camps = []
1589        for nom_camp, val_camp in ora_dict_vals_param.items():
1590            if val_camp is None:
1591                continue
1592            nom_camps.append(nom_camp)
1593            vals_camps.append(":" + nom_camp)
1594            params.append(val_camp)
1595
1596        for nom_camp, val_camp_str in dict_vals_str.items():
1597            if val_camp_str is None:
1598                continue
1599            nom_camps.append(nom_camp)
1600            vals_camps.append(str(val_camp_str))
1601
1602        row_desc_tab = get_row_desc_tab(self.con_db, nom_tab)
1603        pk_binds = {k: new_cursor(self.con_db).var(ora_tip_camp) for k, ora_tip_camp in row_desc_tab.pk_vals().items()}
1604        if not pk_binds:
1605            pk_binds = {'ROWID': new_cursor(self.con_db).var(cx_Oracle.ROWID)}
1606        str_pk_camps = ",".join(pk_binds.keys())
1607        str_pk_binds = ",".join(list(map(lambda x: ":ret_" + str(x), pk_binds.keys())))
1608        params += list(pk_binds.values())
1609
1610        a_sql_res = f"insert into {nom_tab}({','.join(nom_camps)}) values({','.join(vals_camps)}) " \
1611                    f"returning {str_pk_camps} into {str_pk_binds}"
1612
1613        ok = self.exec_trans_db(a_sql_res, *params)
1614        if ok:
1615            pk_vals = {k: curs_var.getvalue(0)[0] for k, curs_var in pk_binds.items()}
1616            return self.exist_row_tab(nom_tab, pk_vals)
1617
1618        return ok

Inserta registro en la tabla indicada. Los valores para cada columna se pasarán a través de dict_vals_param como bindings o a través de dict_vals_str directemente en el string del sql ejecutado

Arguments:
  • nom_tab: nombre de la tabla
  • dict_vals_param: diccionario indexado por columnas-valores. Los valores se pasarán como bindings
  • dict_vals_str: diccionario indexado por columnas-valores a asignar como strings. El valor se pasa directamente como asignacion en la senetencia sql
  • pasar_nulls: (opcional) por defecto False. Indica si los valores NULL se asignarán o no
Returns:

row_table (si genera el registro) o False si va mal la operación

def update_row_tab( self, nom_tab, dict_clau_reg, dict_vals_param=None, dict_vals_str=None, pasar_nulls=None):
1620    def update_row_tab(self, nom_tab, dict_clau_reg, dict_vals_param=None, dict_vals_str=None, pasar_nulls=None):
1621        """
1622        Actualiza registro en la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}
1623        Los valores para cada columna se pasarán a través de dict_vals_param como bindings o a través de
1624        dict_vals_str directemente en el string del sql ejecutado
1625
1626        Args:
1627            nom_tab: nombre de la tabla
1628            dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
1629            dict_vals_param: diccionario indexado por columnas-valores. Los valores se pasarán como bindings
1630            dict_vals_str: diccionario indexado por columnas-valores a asignar como strings. El valor se pasa
1631                        directamente como asignacion en la senetencia sql
1632            pasar_nulls: (opcional) por defecto False. Indica si los valores NULL se asignarán o no
1633
1634        Returns:
1635            row_table (si genera el registro) o False si va mal la operación
1636        """
1637        if not dict_vals_param:
1638            dict_vals_param = {}
1639        if not dict_vals_str:
1640            dict_vals_str = {}
1641
1642        ora_dict_clau_reg = self.get_vals_tab_for_transdb(nom_tab, dict_clau_reg)
1643        ora_dict_vals_param = self.get_vals_tab_for_transdb(nom_tab, dict_vals_param, pasar_nulls=pasar_nulls)
1644
1645        if pasar_nulls:
1646            # Se revisa que en un campo geometrico se asigne None y por lo tanto se asigne valor via STR
1647            geoms_null = [ng for ng, val in ora_dict_vals_param.items()
1648                          if not val and self.get_tip_camp_geom(nom_tab, ng)]
1649            if geoms_null:
1650                keys_str = [nc.upper() for nc in dict_vals_str.keys()]
1651                for gn in geoms_null:
1652                    ora_dict_vals_param.pop(gn)
1653                    if gn.upper() not in keys_str:
1654                        dict_vals_str[gn.upper()] = "NULL"
1655
1656        (sql_set_camps, params_set_camps) = dict_as_sql_bind_and_params(ora_dict_vals_param,
1657                                                                        ",", "=")
1658
1659        (query_clau, params_filter) = dict_as_sql_bind_and_params(ora_dict_clau_reg)
1660
1661        params = params_set_camps + params_filter
1662
1663        for nom_camp, val_camp_str in dict_vals_str.items():
1664            if sql_set_camps:
1665                sql_set_camps += " , "
1666            sql_set_camps += nom_camp + "=" + val_camp_str
1667
1668        ok = None
1669        if sql_set_camps:
1670            a_sql_res = f"update {nom_tab} set {sql_set_camps} where {query_clau}"
1671
1672            ok = self.exec_trans_db(a_sql_res, *params)
1673
1674            if ok:
1675                return self.exist_row_tab(nom_tab, dict_clau_reg)
1676
1677        return ok

Actualiza registro en la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor} Los valores para cada columna se pasarán a través de dict_vals_param como bindings o a través de dict_vals_str directemente en el string del sql ejecutado

Arguments:
  • nom_tab: nombre de la tabla
  • dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
  • dict_vals_param: diccionario indexado por columnas-valores. Los valores se pasarán como bindings
  • dict_vals_str: diccionario indexado por columnas-valores a asignar como strings. El valor se pasa directamente como asignacion en la senetencia sql
  • pasar_nulls: (opcional) por defecto False. Indica si los valores NULL se asignarán o no
Returns:

row_table (si genera el registro) o False si va mal la operación

def remove_row_tab(self, nom_tab, dict_clau_reg):
1679    def remove_row_tab(self, nom_tab, dict_clau_reg):
1680        """
1681        Borra registro en la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}
1682
1683        Args:
1684            nom_tab: nombre de la tabla
1685            dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
1686
1687        Returns:
1688            bool según vaya la operación
1689        """
1690        ora_dict_clau_reg = self.get_vals_tab_for_transdb(nom_tab, dict_clau_reg)
1691
1692        a_sql_tmpl = "delete {nom_tab} where {query_clau}"
1693
1694        (sql_filter, params) = dict_as_sql_bind_and_params(ora_dict_clau_reg)
1695
1696        a_sql_res = a_sql_tmpl.format(nom_tab=nom_tab,
1697                                      query_clau=sql_filter)
1698
1699        return self.exec_trans_db(a_sql_res, *params)

Borra registro en la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}

Arguments:
  • nom_tab: nombre de la tabla
  • dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
Returns:

bool según vaya la operación

def exist_row_tab(self, nom_tab, dict_clau_reg, **extra_params):
1701    def exist_row_tab(self, nom_tab, dict_clau_reg, **extra_params):
1702        """
1703        Devuelve registro de la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}
1704
1705        Args:
1706            nom_tab: nombre de la tabla
1707            dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
1708            **extra_params:
1709
1710        Returns:
1711            object de la clase row_table o especificada en **extra_params['row_class']
1712        """
1713        ora_dict_clau_reg = self.get_vals_tab_for_transdb(nom_tab, dict_clau_reg)
1714
1715        (filter_sql, params) = dict_as_sql_bind_and_params(ora_dict_clau_reg, "and", "=")
1716
1717        return self.row_table(nom_tab, filter_sql, *params, **extra_params)

Devuelve registro de la tabla indicada que cunpla con la clave pasada por dict_clau_reg {clave:valor}

Arguments:
  • nom_tab: nombre de la tabla
  • dict_clau_reg: diccionario indexado por clave-valor del registro a actualizar
  • **extra_params:
Returns:

object de la clase row_table o especificada en **extra_params['row_class']

def get_vals_tab_for_transdb(self, nom_tab, dict_camps_vals, pasar_nulls=True):
1719    def get_vals_tab_for_transdb(self, nom_tab, dict_camps_vals, pasar_nulls=True):
1720        """
1721        Para un tabla y diccionario columnas-valores devuelve diccionario indexado por las columnas con los valores
1722        convertidos a formato cx_Oracle según tipo de cada columna en Oracle
1723        Args:
1724            nom_tab: nombre de la tabla
1725            dict_camps_vals: diccionario indexado por columnas-valor a convertir a formato cx_Oracle
1726            pasar_nulls: (opcional) por defecto convertirá los None a NULL de Oracle
1727
1728        Returns:
1729            dict indexado por columnas con los valores convertidos a tipo cx_Oracle
1730        """
1731        dd_tab = get_row_desc_tab(self.con_db, nom_tab)
1732
1733        # Retorna dict con los campos a pasar por parametro
1734        d_params = {}
1735
1736        # Los nombres de campo siempre se buscarán en mayúsculas
1737        dict_camps_vals = {k.upper(): v for k, v in dict_camps_vals.items()}
1738
1739        for camp, tip_camp in dd_tab.vals().items():
1740            if camp not in dict_camps_vals:
1741                continue
1742
1743            val_camp = dict_camps_vals.get(camp)
1744            if not pasar_nulls and val_camp is None:
1745                continue
1746
1747            if isinstance(val_camp, m_sdo_geom.sdo_geom):
1748                var = val_camp.as_ora_sdo_geometry()
1749            else:
1750                try:
1751                    var = new_cursor(self.con_db).var(tip_camp)
1752                    var.setvalue(0, val_camp)
1753                except:
1754                    var = val_camp
1755
1756            d_params[camp] = var
1757
1758        return d_params

Para un tabla y diccionario columnas-valores devuelve diccionario indexado por las columnas con los valores convertidos a formato cx_Oracle según tipo de cada columna en Oracle

Arguments:
  • nom_tab: nombre de la tabla
  • dict_camps_vals: diccionario indexado por columnas-valor a convertir a formato cx_Oracle
  • pasar_nulls: (opcional) por defecto convertirá los None a NULL de Oracle
Returns:

dict indexado por columnas con los valores convertidos a tipo cx_Oracle

@print_to_log_exception()
def run_sql_script(self, filename):
1760    @print_to_log_exception()
1761    def run_sql_script(self, filename):
1762        """
1763        Ejecuta slq script (filename) sobre SQLPLUS
1764
1765        Args:
1766            filename: path del sql script
1767
1768        Returns:
1769
1770        """
1771        user_ora = self.con_db.username
1772        ds_ora = self.con_db.dsn
1773        nom_con = self.nom_con_db
1774        psw_ora = self.__psw_con_db__
1775        if psw_ora is None:
1776            print("ERROR - Conexión '" + nom_con + "' no está añadida al gestor!!")
1777            return
1778
1779        with open(filename, 'rb') as a_file:
1780            a_sql_command = a_file.read()
1781
1782        con_db_str = user_ora + "/" + psw_ora + "@" + ds_ora
1783        sqlplus = Popen(['sqlplus', '-S', con_db_str], stdin=PIPE, stdout=PIPE, stderr=PIPE)
1784        # sqlplus.stdin.write(a_sql_command)
1785
1786        (stdout, stderr) = sqlplus.communicate(a_sql_command)
1787
1788        self.print_log("Resultado lanzar script '{}': \n"
1789                       "{}".format(filename,
1790                                   stdout.decode("utf-8")))
1791
1792        if sqlplus is not None:
1793            sqlplus.terminate()

Ejecuta slq script (filename) sobre SQLPLUS

Arguments:
  • filename: path del sql script

Returns:

@staticmethod
def get_nom_obj_sql(nom_base, prefix='', sufix=''):
1795    @staticmethod
1796    def get_nom_obj_sql(nom_base, prefix="", sufix=""):
1797        """
1798        Retorna nombre propuesto con prefijo/sufijos formateado para que cumpla longitud máxima de 32 caracteres en
1799        objetos sql Oracle
1800
1801        Args:
1802            nom_base: nombre propuesto
1803            prefix: (opc) prefijo
1804            sufix: (opc) sufijo
1805
1806        Returns:
1807            str formateado
1808        """
1809        return x_sql_parser.get_nom_obj_sql(nom_base, prefix, sufix)

Retorna nombre propuesto con prefijo/sufijos formateado para que cumpla longitud máxima de 32 caracteres en objetos sql Oracle

Arguments:
  • nom_base: nombre propuesto
  • prefix: (opc) prefijo
  • sufix: (opc) sufijo
Returns:

str formateado

def iter_sdo_gtypes_vals_camp_tab(self, nom_taula, nom_camp):
1811    def iter_sdo_gtypes_vals_camp_tab(self, nom_taula, nom_camp):
1812        """
1813        Retorna los distintos tipos de Geometria (codigo entero que define el tipo SDO_GTYPE)
1814        que se encuentran dentro de la columna sdo_geometry de una tabla
1815
1816        Args:
1817            nom_taula: nombre de la tabla
1818            nom_camp: nombre campo geometrico
1819
1820        Returns:
1821            int definiendo tipo de geometría
1822        """
1823        sql_tip_geoms = f"select distinct(tab.{nom_camp}.Get_GType()) as tip_geom from {nom_taula} tab " \
1824                        f"where {nom_camp} is not null"
1825
1826        for reg in self.generator_rows_sql(sql_tip_geoms):
1827            yield reg.TIP_GEOM

Retorna los distintos tipos de Geometria (codigo entero que define el tipo SDO_GTYPE) que se encuentran dentro de la columna sdo_geometry de una tabla

Arguments:
  • nom_taula: nombre de la tabla
  • nom_camp: nombre campo geometrico
Returns:

int definiendo tipo de geometría

def iter_distinct_vals_camp_tab(self, nom_taula, nom_camp, filter_sql=None):
1829    def iter_distinct_vals_camp_tab(self, nom_taula, nom_camp, filter_sql=None):
1830        """
1831        Retorna los distintos valores de la columna de una tabla
1832
1833        Args:
1834            nom_taula (str): Nombre de tabla
1835            nom_camp (str): Nombre de campo
1836            filter_sql(str): Filtro SQL sobre la tabla indicada
1837
1838        Returns:
1839            {str}: Itera los distintos valores encontrados en el campo indicado
1840        """
1841        sql_distinct_vals = f"select distinct(tab.{nom_camp}) as VAL from {nom_taula} tab"
1842        if filter_sql:
1843            sql_distinct_vals += " where " + filter_sql
1844
1845        for reg in self.generator_rows_sql(sql_distinct_vals):
1846            yield reg.VAL

Retorna los distintos valores de la columna de una tabla

Arguments:
  • nom_taula (str): Nombre de tabla
  • nom_camp (str): Nombre de campo
  • filter_sql(str): Filtro SQL sobre la tabla indicada
Returns:

{str}: Itera los distintos valores encontrados en el campo indicado

def get_tip_camp_geom(self, nom_tab_or_view, nom_camp_geom):
1848    def get_tip_camp_geom(self, nom_tab_or_view, nom_camp_geom):
1849        """
1850        Retorna el tipo de campo geométrico (class_tip_geom) registrados en la global __class_tips_geom_ora
1851        para el campo indicado
1852
1853        Args:
1854            nom_tab_or_view: nombre tabla/vista
1855            nom_camp_geom: nombre campo geom
1856
1857        Returns:
1858            namedtuple: con atributos ['TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID'])
1859        """
1860        tips_geom_tab = get_tips_geom_tab(self.con_db, nom_tab_or_view)
1861        if tips_geom_tab:
1862            return tips_geom_tab.get(nom_camp_geom.upper())

Retorna el tipo de campo geométrico (class_tip_geom) registrados en la global __class_tips_geom_ora para el campo indicado

Arguments:
  • nom_tab_or_view: nombre tabla/vista
  • nom_camp_geom: nombre campo geom
Returns:

namedtuple: con atributos ['TABLE_NAME', 'COLUMN_NAME', 'GTYPE', 'SRID'])

def get_epsg_for_srid(self, srid):
1864    def get_epsg_for_srid(self, srid):
1865        """
1866        Rertorna WKT con la definicion del SRID dado
1867        """
1868        return self.callfunc_sql('SDO_CS.MAP_ORACLE_SRID_TO_EPSG', cx_Oracle.NUMBER, srid)

Rertorna WKT con la definicion del SRID dado

def get_gtype_camp_geom(self, nom_tab_or_view, nom_camp_geom):
1870    def get_gtype_camp_geom(self, nom_tab_or_view, nom_camp_geom):
1871        """
1872        Retorna el tipo GTYPE (int) de la geometria
1873
1874        Args:
1875            nom_tab_or_view: nombre tabla/vista
1876            nom_camp_geom: nombre campo geom
1877
1878        Returns:
1879            int
1880        """
1881        gtype = 0
1882        g_tip_ora = self.get_tip_camp_geom(nom_tab_or_view, nom_camp_geom)
1883        if g_tip_ora:
1884            gtype = GTYPES_ORA.index(g_tip_ora.GTYPE)
1885
1886        return gtype

Retorna el tipo GTYPE (int) de la geometria

Arguments:
  • nom_tab_or_view: nombre tabla/vista
  • nom_camp_geom: nombre campo geom
Returns:

int

@staticmethod
def verificar_path_vector_file(nom_tab_or_view, dir, file_name, ext, zipped):
1888    @staticmethod
1889    def verificar_path_vector_file(nom_tab_or_view, dir, file_name, ext, zipped):
1890        """
1891        Compone el/los path para el/los vector_file de una tabla y determina si exists
1892        Args:
1893            nom_tab_or_view:
1894            dir:
1895            file_name:
1896            ext:
1897            zipped:
1898
1899        Returns:
1900            file_path (str), file_path_zip (str), exists (bool)
1901        """
1902        if file_name and not file_name.endswith(ext):
1903            file_name = ".".join((file_name, ext))
1904        elif not file_name:
1905            file_name = ".".join((nom_tab_or_view, ext)).lower()
1906
1907        file_path = os.path.join(dir, file_name)
1908        file_path_zip = None
1909        if zipped:
1910            file_path_zip = "{}.zip".format(os.path.splitext(file_path)[0])
1911
1912        exists = (os.path.exists(file_path) and not file_path_zip) or (file_path_zip and os.path.exists(file_path_zip))
1913
1914        return file_path, file_path_zip, exists

Compone el/los path para el/los vector_file de una tabla y determina si exists

Arguments:
  • nom_tab_or_view:
  • dir:
  • file_name:
  • ext:
  • zipped:
Returns:

file_path (str), file_path_zip (str), exists (bool)

@print_to_log_exception()
def create_json_tab_or_view( self, nom_tab_or_view, dir='.', file_name=None, overwrite=True, cols=None, zipped=True, filter_sql=None, *args_sql):
1916    @print_to_log_exception()
1917    def create_json_tab_or_view(self, nom_tab_or_view, dir='.', file_name=None, overwrite=True, cols=None, zipped=True,
1918                                filter_sql=None, *args_sql):
1919        """
1920
1921        Args:
1922            nom_tab_or_view (str): Nombre tabla o vista
1923            dir (str):
1924            file_name (str):
1925            overwrite (bool):
1926            cols (list): columnas
1927            zipped (bool):
1928            filter_sql (str):
1929            *args_sql: lista de argumentos a pasar al filtro sql
1930        Returns:
1931            file_path (str)
1932        """
1933        file_path, file_path_zip, exists = self.verificar_path_vector_file(
1934            nom_tab_or_view, dir, file_name, "json", zipped)
1935
1936        if overwrite or not exists:
1937            # Se calculan las columnas para hacer get de la fila con el orden en las columnas de la tabla
1938            if not cols:
1939                dd_tab = self.get_dd_table(nom_tab_or_view)
1940                cols = dd_tab.cols
1941            sql = sql_tab(nom_tab_or_view,
1942                          filter_sql=filter_sql,
1943                          columns=cols)
1944
1945            file_path_res = vector_file_from_gen_ora_sql(file_path, "json", self.generator_rows_sql(sql, *args_sql),
1946                                                         zipped=zipped)
1947        else:
1948            file_path_res = file_path if not zipped else file_path_zip
1949
1950        return file_path_res
Arguments:
  • nom_tab_or_view (str): Nombre tabla o vista
  • dir (str):
  • file_name (str):
  • overwrite (bool):
  • cols (list): columnas
  • zipped (bool):
  • filter_sql (str):
  • *args_sql: lista de argumentos a pasar al filtro sql
Returns:

file_path (str)

@print_to_log_exception()
def create_csv_tab_or_view( self, nom_tab_or_view, dir='.', file_name=None, overwrite=True, cols=None, zipped=True, filter_sql=None, *args_sql):
1952    @print_to_log_exception()
1953    def create_csv_tab_or_view(self, nom_tab_or_view, dir='.', file_name=None, overwrite=True, cols=None, zipped=True,
1954                               filter_sql=None, *args_sql):
1955        """
1956
1957        Args:
1958            nom_tab_or_view (str): Nombre tabla o vista
1959            dir (str):
1960            file_name (str):
1961            overwrite (bool):
1962            cols (list): columnas
1963            zipped (bool):
1964            filter_sql (str):
1965            *args_sql: lista de argumentos a pasar al filtro sql
1966
1967        Returns:
1968            file_path_res (str)
1969        """
1970        file_path, file_path_zip, exists = self.verificar_path_vector_file(
1971            nom_tab_or_view, dir, file_name, "csv", zipped)
1972
1973        if overwrite or not exists:
1974            if not cols:
1975                dd_tab = self.get_dd_table(nom_tab_or_view)
1976                cols = dd_tab.cols
1977            sql = sql_tab(nom_tab_or_view,
1978                          filter_sql=filter_sql,
1979                          columns=cols)
1980
1981            # Para el formato geocsv que acepta GDAL se añade fichero con los tipos de columna
1982            tip_cols_csv = []
1983            for col in cols:
1984                r_tip_col = self.row_table("user_tab_columns",
1985                                           "table_name = :1 and column_name = :2",
1986                                           nom_tab_or_view.upper(), col.upper())
1987                dtype = r_tip_col.DATA_TYPE
1988                dlength = r_tip_col.DATA_LENGTH
1989                dprecision = r_tip_col.DATA_PRECISION
1990                dscale = r_tip_col.DATA_SCALE
1991
1992                if dtype == "DATE":
1993                    tip_cols_csv.append('"DateTime"')
1994                elif dtype == "FLOAT":
1995                    tip_cols_csv.append('"Real({}.{})"'.format(dlength, dprecision))
1996                elif dtype == "NUMBER":
1997                    if dscale and dscale != 0:
1998                        tip_cols_csv.append('"Real({}.{})"'.format(dprecision, dscale))
1999                    elif dprecision:
2000                        tip_cols_csv.append('"Integer({})"'.format(dprecision))
2001                    else:
2002                        tip_cols_csv.append('"Real(10.8)"')
2003                elif dtype == "SDO_GEOMETRY":
2004                    tip_cols_csv.append('"WKT"')
2005                else:
2006                    tip_cols_csv.append('"String({})"'.format(round(dlength * 1.25)))
2007
2008            file_path_res = vector_file_from_gen_ora_sql(file_path, "csv",
2009                                                         self.generator_rows_sql(sql, *args_sql, geom_format="as_wkt"),
2010                                                         zipped=zipped, cols_csv=cols, tip_cols_csv=tip_cols_csv)
2011        else:
2012            file_path_res = file_path if not zipped else file_path_zip
2013
2014        return file_path_res
Arguments:
  • nom_tab_or_view (str): Nombre tabla o vista
  • dir (str):
  • file_name (str):
  • overwrite (bool):
  • cols (list): columnas
  • zipped (bool):
  • filter_sql (str):
  • *args_sql: lista de argumentos a pasar al filtro sql
Returns:

file_path_res (str)

@print_to_log_exception()
def create_geojsons_tab_or_view( self, nom_tab_or_view, dir='.', file_name_prefix=None, by_geom=False, dir_topojson=None, overwrite=True, cols=None, filter_sql=None, *args_sql):
2016    @print_to_log_exception()
2017    def create_geojsons_tab_or_view(self, nom_tab_or_view, dir='.', file_name_prefix=None, by_geom=False,
2018                                    dir_topojson=None, overwrite=True, cols=None,
2019                                    filter_sql=None, *args_sql):
2020        """
2021
2022        Args:
2023            nom_tab_or_view (str): Nombre tabla (vigente o versionada) para entidad GIS
2024            dir (str="."):
2025            file_name_prefix (str=None): (opcional) prefijo del fichero
2026            by_geom (bool=False): (Opcional) si se querrán los geojsons por geometria. Si no se saca un unico geojson con
2027                            la columna geometry como una GeometryCollection si la tabla es multigeom
2028            dir_topojson (str=None): path donde irán las conversiones
2029            overwrite (bool=True):
2030            cols (list=None):
2031            filter_sql (str=None):
2032            *args_sql: lista de argumentos a pasar al filtro sql
2033
2034        Returns:
2035            ok (bool)
2036        """
2037        ext = "geo.json"
2038        sqls = {}
2039        dd_tab = self.get_dd_table(nom_tab_or_view)
2040        if not cols:
2041            cols = dd_tab.cols
2042
2043        if by_geom:
2044            c_alfas = [cn for cn in dd_tab.alfas() if cn in cols]
2045            c_geoms = [cn for cn in dd_tab.geoms() if cn in cols]
2046            for c_geom in c_geoms:
2047                sqls[c_geom] = sql_tab(nom_tab_or_view, filter_sql, c_alfas + [c_geom])
2048        else:
2049            sqls[None] = sql_tab(nom_tab_or_view, filter_sql, cols)
2050
2051        if not file_name_prefix:
2052            file_name_prefix = nom_tab_or_view
2053
2054        for ng, sql in sqls.items():
2055            file_name = file_name_prefix
2056            if ng:
2057                file_name = "-".join((file_name, ng))
2058
2059            file_name = ".".join((file_name, ext)).lower()
2060            file_path = os.path.join(dir, file_name)
2061
2062            if overwrite or not os.path.exists(file_path):
2063                file_path = vector_file_from_gen_ora_sql(file_path, "geojson",
2064                                                         self.generator_rows_sql(sql, *args_sql))
2065
2066            if by_geom and dir_topojson and file_path:
2067                tip_geom = getattr(dd_tab, ng).GTYPE
2068                simplify = True
2069                if tip_geom.endswith("POINT"):
2070                    simplify = False
2071
2072                topojson_utils.geojson_to_topojson(file_path, dir_topojson,
2073                                                   simplify=simplify,
2074                                                   overwrite=overwrite)
2075
2076        return True
Arguments:
  • nom_tab_or_view (str): Nombre tabla (vigente o versionada) para entidad GIS
  • dir (str="."):
  • file_name_prefix (str=None): (opcional) prefijo del fichero
  • by_geom (bool=False): (Opcional) si se querrán los geojsons por geometria. Si no se saca un unico geojson con la columna geometry como una GeometryCollection si la tabla es multigeom
  • dir_topojson (str=None): path donde irán las conversiones
  • overwrite (bool=True):
  • cols (list=None):
  • filter_sql (str=None):
  • *args_sql: lista de argumentos a pasar al filtro sql
Returns:

ok (bool)

nom_con_db