cy_Oracle
=========
cy_Oracle is a simple Python module that improves the handling of
Oracle object types compared to cx_Oracle.
cy_Oracle supports a central declaration of a mapping between
Python classes and Oracle object types. This mapping can be
used in DML (SELECT, UPDATE, INSERT) as well as with PL/SQL
functions and procedures. Details are described in the online help
for the function "addTypemap".
>>> import cy_Oracle
>>> help(cy_Oracle.OracleDB.addTypemap)
The entry point for the client is the class cy_Oracle.OracleDB.
Here you define the connection parameter to the Oracle database
(User, Password, URL).
>>> import cy_Oracle
>>> help(cy_Oracle.OracleDB) for details.
The class cy_Oracle.Connection derives from cx_Oracle.Connection.
It does not add any new methods, but override the method "cursor()".
Here we return an object of the class cy_Oracle.Cursor.
>>> import cy_Oracle
>>> help(cy_Oracle.Connection.cursor) for details.
The class cy_Oracle.Cursor derives from cx_Oracle.Cursor.
Here we setup the input and output handlers to map between
Oracle object types and Python classes.
>>> import cy_Oracle
>>> help(cy_Oracle.Cursor) for details.
Example 1
---------
This 'Hello World' example for the module cy_Oracle will
list the names and the creation date of objects in the
database schema "book"::
1 from cy_Oracle import OracleDB
2 # setup the DB connection
3 myDB = OracleDB(user='book', password='book', databaseURL='bookdb')
4 # connect to DB
5 con = myDB.connect()
6 # select some data from the view 'USER_OBJECTS'
7 cur = con.cursor()
8 cur.execute('SELECT object_name, created from user_objects')
9 # loop the result
10 for row in cur:
11 print ('%s --> %s' % (row.object_name, row.created))
12 # close down
13 cur.close()
14 con.close()
In this example, cy_Oracle works just as cx_Oracle - the only difference
is line 11. Here, cy_Oracle gives you an object with names of the selected
columns as attributes whereas in cx_Oracle you will just get a collection.
The example needs a TNS-entry (here: bookdb) to locate the host of the
database server. As an alternative, it is possible to include the host,
the port an the service name into the connection::
3 myDB = OracleDB(databaseURL='user/password@dbhost:dbport/servicename')
Example 2
---------
Oracle object types are mostly used in complex database application
that are build from a mixture of SQL and PL/SQL. So, this "simple"
example using object types is one that is not very useful. It will
list all columns of all tables an views in the database schema "book"::
1 from cy_Oracle import OracleDB
2 from cy_Oracle import Attribute
3
4 # class to receive the result - one object of type T_COL_INFO.
5 class ColInfo(object):
6 def __init__(self):
7 self.name=None
8 self.oracleDataType=None
9 self.oracleDataLenght=None
10
11 def __str__(self):
12 return 'name=%s type=%s(%d)' % \
13 (self.name,self.oracleDataType,self.oracleDataLenght)
14
15 myDB = OracleDB(user='book', password='book', databaseURL='orcl')
16 con = myDB.connect()
17 cur = con.cursor()
18 # Create a new object type in the database
19 cur.execute(""" create or replace type T_COL_INFO as object(
20 NAME varchar2(85 char)
21 ,DATA_TYPE varchar2(106 char)
22 ,DATA_LENGTH number
23 ,COMMENTS varchar2(4000 char))
24 """)
25 # create a view containing objects of the type T_COL_INFO
26 cur.execute(""" create or replace view V_COL_INFO as
27 select
28 c.TABLE_NAME as TABLE_NAME
29 ,T_COL_INFO(c.COLUMN_NAME,c.DATA_TYPE,c.DATA_LENGTH,m.COMMENTS)
30 as COL_INFO
31 from USER_TAB_COLUMNS c ,USER_COL_COMMENTS m
32 where c.COLUMN_NAME = m.COLUMN_NAME and c.TABLE_NAME = m.TABLE_NAME
33 """)
34 # Create a type-mapping between T_COL_INFO and the Python object ColInfo
35 myDB.addTypemap(oracleTypename='T_COL_INFO',pythonClass=ColInfo,attrDict={
36 'name' : Attribute('NAME'),
37 'oracleDataType' : Attribute('DATA_TYPE'),
38 'oracleDataLenght' : Attribute('DATA_LENGTH'),
39 })
40 # select some objects from the database
41 for row in cur.execute(""" select TABLE_NAME,COL_INFO from V_COL_INFO order by 1 """):
42 print 'Table::%s "%s"::%s' % \
43 (row.table_name,row.col_info.__class__.__name__,row.col_info)
43 cur.close()
44 con.close()
First of all, we need a Python class to receive the data from the
Oracle object type. In line 5, we create the class "ColInfo". To
use the class with cy_Oracle, it needs a constructor without
parameters an "object" somewhere in its base classes.
Now, we need an Oracle object type. We are using the DDL-statement in
line 19 to create the type T_COL_INFO. It stores information about one colunm
of a table or a view in the database.
Next, we want to be able to receive objects of the type T_COL_INFO from the database.
In this example, we are creating the database view V_COL_INFO that contains this object
in one of its columns - see line 26.
The interesting part of this example the code in line 35. In cx_Oracle, a SELECT
from the view V_COL_INFO would return an object of the Python class cx_Oracle.Object.
This is a generic class, used for all Oracle object types with the following
advantages and disadvantages:
* it is generic and contains all attributes of the Oracle object
* all instances become inaccessable when closing the database connection
* you can not derive your own Python class from cx_Oracle.Object
With the method "addTypemap" we tell cy_Oracle that:
* we want an object of the Python class "ColInfo" each time a Oracle object
of the object type "T_COL_INFO" shows up
* we want an Oracle object of the object type "T_COL_INFO" each time a Python
object of the class "ColInfo" shows up
* we tell cy_Oracle how the attributes of the two types are mapped.
The output from the example depends on the objects in your database schema, but
it should be something like this::
Table::BOOK "ColInfo"::name=BK_ISBN type=NUMBER(22)
Table::BOOK "ColInfo"::name=BK_BOOK type=T_BOOK(1)
It shows, that:
* we got an object of the Python class "ColInfo" for each entry in view
"V_COL_INFO".
* We can add or override methods in "ColInfo" as usual
Example 3
---------
Oracle object types can be build as a composition using other object types.
The following example shows how cy_Oracle handles compositions and collections
of objects.
We are extending the example 2 to get one Python object for each table in our
schema containing:
* the name of the table
* the remaining free space in the table
* a collection of "ColInfo" (see Example 2) objects describing each of the columns.
The exmple also shows that cy_Oracle is able to deal with composit Object
object types::
1 """ Example using Oracle Object Types
2
3 This example shows how to map composit
4 Oracle object types as well collections
5 to Python objects using cy_Oracle.
6 """
7 from cy_Oracle import OracleDB
8 from cy_Oracle import Attribute
9 from cy_Oracle import Collection
10
11 # class to receive the result - one object of type T_COL_INFO.
12 class ColInfo(object):
13 def __init__(self):
14 self.name=None
15 self.oracleDataType=None
16 self.oracleDataLenght=None
17 def __str__(self):
18 return 'name=%s type=%s(%d)' % \
19 (self.name,self.oracleDataType,self.oracleDataLenght)
20
21 # Class the receive the result - one object of type T_TAB_INFO
22 class TabInfo(object):
23 def __init__(self):
24 self.name=None
25 self.pctFree=None
26 self.colInfo=None
27 def __str__(self):
28 result = 'name=%s free=%d(%%)' % (self.name,self.pctFree)
29 for col in self.colInfo:
30 result = '%s\n %s' % (result,col)
31 return result
32
33 myDB = OracleDB(user='book', password='book', databaseURL='bookdb')
34 con = myDB.connect()
35 cur = con.cursor()
36
37 # to be able to run this program more then once we must drop the existing types
38 try:
39 cur.execute(""" drop type T_TAB_INFO """)
40 except:
41 pass
42 try:
43 cur.execute(""" drop type T_COL_INFO_NT """)
44 except:
45 pass
46
47 # Create a object type in the database
48 cur.execute(""" create or replace type T_COL_INFO as object(
49 NAME varchar2(85 char)
50 ,DATA_TYPE varchar2(106 char)
51 ,DATA_LENGTH number
52 ,COMMENTS varchar2(4000 char))
53 """)
54 # Create a collection type in the database
55 cur.execute(""" create or replace type T_COL_INFO_NT as table of T_COL_INFO """)
56 # Create the type that describes one table
57 cur.execute(""" create or replace type T_TAB_INFO as object(
58 NAME varchar2(30 char),
59 PCT_FREE number,
60 COL_INFO T_COL_INFO_NT)
61 """)
62 # create a view containing objects of the type T_COL_INFO
63 cur.execute(""" create or replace view V_TAB_INFO as
64 select T_TAB_INFO(
65 t.TABLE_NAME
66 ,t.PCT_FREE
67 ,cast(MULTISET(
68 SELECT T_COL_INFO(c.COLUMN_NAME,c.DATA_TYPE,c.DATA_LENGTH
69 ,m.COMMENTS) from
70 USER_TAB_COLUMNS c,USER_COL_COMMENTS m
71 where c.TABLE_NAME=t.TABLE_NAME and c.TABLE_NAME=m.TABLE_NAME)
72 as T_COL_INFO_NT )
73 ) as TAB_INFO
74 from user_tables t
75 """)
76
77 # Create a type-mapping between T_COL_INFO and the Python object ColInfo
78 myDB.addTypemap(oracleTypename='T_COL_INFO',pythonClass=ColInfo,attrDict={
79 'name' : Attribute('NAME'),
80 'oracleDataType' : Attribute('DATA_TYPE'),
81 'oracleDataLenght' : Attribute('DATA_LENGTH'),
82 })
83 # Create a type-mapping between T_TAB_INFO and the Python object ColInfo
84 myDB.addTypemap(oracleTypename='T_TAB_INFO',pythonClass=TabInfo,attrDict={
85 'name' : Attribute('NAME'),
86 'pctFree' : Attribute('PCT_FREE'),
87 'colInfo' : Collection('COL_INFO','T_COL_INFO_NT')
88 })
89
90 # select some objects from the database
91 for row in cur.execute(""" select TAB_INFO from V_TAB_INFO """):
92 print '\n%s' % (row.tab_info)
93 cur.close()
94 con.close()
The output from the example depends on the objects in your database schema, but
it should be something like this::
name=BOOK free=10(%)
name=BK_BOOK type=T_BOOK(1)
name=BK_ISBN type=NUMBER(22)
name=BK_BOOK type=T_BOOK(1)
name=BK_ISBN type=NUMBER(22)
...
...