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