Using Oracle Database from Python and Zato Services

Overview

Oracle Database remains a cornerstone of enterprise IT, powering mission-critical applications around the world. Integrating Oracle with Python unlocks automation, reporting, and API-based workflows. In this article, you'll learn how to:

  • Connect to Oracle Database from Python
  • Use Oracle Database in Zato services
  • Execute SQL queries and call stored procedures
  • Understand the underlying SQL objects

All examples are based on real-world use cases and follow best practices for security and maintainability.

Why Use Oracle Database from Python?

Python is a popular language for automation, integration, and data processing. By connecting Python to Oracle Database, you can:

  • Automate business processes
  • Build APIs that interact with enterprise data
  • Run analytics and reporting jobs
  • Integrate with other systems using Zato

Using Oracle Database in Zato Services

SQL connections are configured in the Dashboard, and you can use them directly in your service code.

In all the service below, the logic is split into several dedicated services, each responsible for a specific operation. This separation improves clarity, reusability, and maintainability.

Setting Up: Oracle Database Objects

First, let's start with the basic SQL objects used in our examples:

-- Table definition
CREATE TABLE users (
    user_id NUMBER,
    username VARCHAR2(50)
);
-- Sample data
INSERT INTO users (user_id, username) VALUES (1, 'john_doe');
INSERT INTO users (user_id, username) VALUES (2, 'jane_smith');
INSERT INTO users (user_id, username) VALUES (3, 'bob_jones');
-- Stored procedure: process_data
CREATE OR REPLACE PROCEDURE process_data (
    input_num   IN  NUMBER,
    input_str   IN  VARCHAR2,
    output_num  OUT NUMBER,
    output_str  OUT VARCHAR2
)
AS
BEGIN
    output_num := input_num * 2;
    output_str := 'Input was: ' || input_str;
END process_data;
/
-- Stored procedure: get_users
CREATE OR REPLACE PROCEDURE get_users (
    recordset OUT SYS_REFCURSOR
)
AS
BEGIN
    OPEN recordset FOR
        SELECT user_id, username
        FROM users
        ORDER BY user_id;
END get_users;
/

1. Querying All Users

This service retrieves all users from the users table.

# -*- coding: utf-8 -*-
from zato.server.service import Service

class GetAllUsers(Service):
    """ Service to retrieve all users from the database.
    """
    def handle(self):

        # Obtain a reference to the configured Oracle Database connection
        conn = self.out.sql['My Oracle DB']

        # Define the SQL query to select all rows from the users table
        query = 'select * from users'

        # Execute the query; returns a list of dictionaries, one per row
        response = conn.execute(query)

        # Set the service response to the query result
        self.response.payload = response
[
  {"user_id":1, "username":"john.doe"},
  {"user_id":2, "username":"jane.smith"},
  {"user_id":3,"username":"bob.jones"}
]

Explanation:

  • The service connects to Oracle using the configured connection.
  • It executes a simple SQL query to fetch all user records.
  • The result is returned as the service response payload.

2. Querying a Specific User by ID

This service fetches a user by their user_id using a parameterized query. There are multiple ways to retrieve results depending on whether you expect one or many rows.

# -*- coding: utf-8 -*-
from zato.server.service import Service

class GetUserById(Service):
    """ Service to fetch a user by their user_id.
    """

    def handle(self):

        # Get the Oracle Database connection from the pool
        conn = self.out.sql['My Oracle DB']

        # Parameterized SQL to prevent injection
        query = 'select * from users where user_id = :user_id'

        # In a real service, this would be read from incoming JSON
        params = {'user_id': 1}

        # Execute the query with parameters; returns a list
        response = conn.execute(query, params)

        # Set the result as the service's response
        self.response.payload = response
[{"user_id":1,"username":"john.doe"}]

Explanation:

  • The service expects user_id in the request payload.
  • It uses a parameterized query to prevent SQL injection.
  • The result is always a list, even if only one row matches.

3. Calling a Stored Procedure with Input and Output Parameters

This service demonstrates how to call an Oracle stored procedure that takes input values and returns output values.

# -*- coding: utf-8 -*-

# Zato
from zato.common.oracledb import NumberIn, NumberOut, StringIn, StringOut
from zato.server.service import Service

class CallProcessData(Service):
    """ Service to call a stored procedure with input/output params.
    """
    def handle(self):

        # Obtain Oracle Database connection
        conn = self.out.sql['My Oracle DB']

        # Prepare input parameter for NUMBER
        in_num = NumberIn(333)

        # Prepare input parameter for VARCHAR2
        in_str = StringIn('Hello')

        # Prepare output parameter for NUMBER (will be written to by the procedure)
        out_num = NumberOut()

        # Prepare output parameter for VARCHAR2, specify max buffer size (optionally)
        out_str = StringOut(size=200)

        # Build the parameter list in the order expected by the procedure
        params = [in_num, in_str, out_num, out_str]

        # Call the stored procedure with the parameters
        response = conn.callproc('process_data', params)

        # Return the output values as a dictionary in the response
        self.response.payload = {
            'output_num': out_num.get(),
            'output_str': out_str.get()
        }
{"output_num":666, "output_str":"Input was: Hello"}

Explanation:

  • The service prepares input and output parameters using helper classes.
  • It calls the process_data procedure with both input and output arguments.
  • The result includes both output values, returned as a dictionary.
  • Note that you always need to provide the parameters for the procedure in the same order as they were declared in the procedure itself.

4. Calling a Procedure Returning Multiple Rows

This service calls a procedure that returns a set of rows (a cursor) and collects the results.

# -*- coding: utf-8 -*-
from zato.common.oracledb import RowsOut
from zato.server.service import Service

class CallGetUsers(Service):
    """ Service to call a procedure returning a set of rows.
    """
    def handle(self):

        # Get Oracle Database connection
        conn = self.out.sql['My Oracle DB']

        # Prepare a RowsOut object to receive the result set
        rows_out = RowsOut()

        # Build parameter list for the procedure
        params = [rows_out]

        # Call the procedure, populating rows
        conn.callproc('get_users', params)

        # Convert the cursor results to a list of rows
        rows = list(rows_out.get())

        # Return the list as the service response
        self.response.payload = rows
[[1,"john.doe"], [2,"jane.smith"], [3,"bob.jones"]]

Explanation:

  • The service prepares a RowsOut object to receive the rows into. That is, the procedure will write rows into this object.
  • It calls the get_users procedure, which populates the rows.
  • You call rows_out.get to get the actual rows from the database.
  • The rows are converted to a list and returned as the payload.

4. Returning a Single Object

When you know your query will return a single row, you can use conn.one or conn.one_or_none for more predictable results:

# -*- coding: utf-8 -*-
from zato.server.service import Service

class GetSingleUserById(Service):
    """ # Service to fetch exactly one user or raise if not found/ambiguous.
    """
    def handle(self):

        # Get the Oracle Database connection
        conn = self.out.sql['My Oracle DB']

        # Parameterized SQL query
        query = 'select * from users where user_id = :user_id'

        # In a real service, this would be read from incoming JSON
        params = {'user_id': 1}

        # conn.one returns a dict if exactly one row, else raises (zero or multiple rows)
        result = conn.one(query, params)

        # Return the single user as the response
        self.response.payload = result

class GetSingleUserOrNoneById(Service):
    """ Service to fetch one user, None if not found, or raise an Exception if ambiguous.
    """
    def handle(self):

        # Get Oracle Database connection
        conn = self.out.sql['My Oracle DB']

        # SQL with named parameter
        query = 'select * from users where user_id = :user_id'

        # Extract user_id from payload
        params = {'user_id': 1}

        # conn.one_or_none returns a dict if one row, None if zero, raises if multiple rows
        result = conn.one_or_none(query, params)

        # Return dict or None
        self.response.payload = result

Explanation:

  • conn.one(query, params) will return a single row as a dictionary if exactly one row is found. If no rows or more than one row are returned, it raises an exception.
  • conn.one_or_none(query, params) will return a single row as a dictionary if one row is found, None if no rows are found, but still raises an exception if more than one row is returned.
  • Use these methods when you expect either exactly one or zero/one results, and want to handle them cleanly.

Key Concepts Explained

  • Connection Management: Zato handles connection pooling and configuration for you. Use self.out.sql['My Oracle DB'] to get a ready-to-use connection.
  • Parameterized Queries: Always use parameters (e.g., :user_id) to avoid SQL injection and improve code clarity.
  • Calling Procedures: Use helper classes (NumberIn, StringIn, NumberOut, StringOut, RowsOut) for input/output arguments and recordsets.
  • Service Separation: Each service is focused on a single responsibility, making code easier to test and reuse.

Security and Best Practices

  • Always use parameterized queries for user input.
  • Manage credentials and connection strings securely (never hardcode them in source code).
  • Handle exceptions and database errors gracefully in production code.
  • Use connection pooling (Zato does this for you) for efficiency.

Summary

Integrating Oracle Database with Python and Zato services gives you powerful tools for building APIs, automating workflows, and connecting enterprise data sources.

Whether you need to run queries, call stored procedures, or expose Oracle data through REST APIs, Zato provides a robust and Pythonic way to do it.

More resources

➤ Python API integration tutorials
What is an integration platform?
Python Integration platform as a Service (iPaaS)
What is an Enterprise Service Bus (ESB)? What is SOA?
Open-source iPaaS in Python