config updation

This commit is contained in:
2026-03-18 15:24:19 +05:30
parent b405426165
commit afd3f57c01
1769 changed files with 313583 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,659 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# aq.py
#
# Contains the classes used for handling Advanced Queuing (AQ): Queue,
# DeqOptions, EnqOptions and MessageProperties.
# -----------------------------------------------------------------------------
import datetime
from . import connection as connection_module
from typing import Any, Union
from . import errors
from .base import BaseMetaClass
from .dbobject import DbObject, DbObjectType
class BaseQueue(metaclass=BaseMetaClass):
@classmethod
def _from_impl(cls, connection, impl):
queue = cls.__new__(cls)
queue._connection = connection
queue._deq_options = DeqOptions._from_impl(impl.deq_options_impl)
queue._enq_options = EnqOptions._from_impl(impl.enq_options_impl)
queue._payload_type = None
queue._impl = impl
return queue
def _verify_message(self, message: "MessageProperties") -> None:
"""
Internal method used for verifying a message.
"""
if not isinstance(message, MessageProperties):
raise TypeError("expecting MessageProperties object")
if message.payload is None:
errors._raise_err(errors.ERR_MESSAGE_HAS_NO_PAYLOAD)
if isinstance(self.payload_type, DbObjectType):
if (
not isinstance(message.payload, DbObject)
or message.payload.type != self.payload_type
):
errors._raise_err(errors.ERR_PAYLOAD_CANNOT_BE_ENQUEUED)
elif self.payload_type == "JSON":
if not isinstance(message.payload, (dict, list)):
errors._raise_err(errors.ERR_PAYLOAD_CANNOT_BE_ENQUEUED)
else:
if not isinstance(message.payload, (str, bytes)):
errors._raise_err(errors.ERR_PAYLOAD_CANNOT_BE_ENQUEUED)
@property
def connection(self) -> "connection_module.Connection":
"""
This read-only attribute returns a reference to the connection object
on which the queue was created.
"""
return self._connection
@property
def deqoptions(self) -> "DeqOptions":
"""
This read-only attribute returns a reference to the options that will
be used when dequeuing messages from the queue.
"""
return self._deq_options
@property
def deqOptions(self) -> "DeqOptions":
"""
Deprecated: use deqoptions instead.
"""
return self.deqoptions
@property
def enqoptions(self) -> "EnqOptions":
"""
This read-only attribute returns a reference to the options that will
be used when enqueuing messages into the queue.
"""
return self._enq_options
@property
def enqOptions(self) -> "EnqOptions":
"""
Deprecated: use enqoptions() instead.
"""
return self.enqoptions
@property
def name(self) -> str:
"""
This read-only attribute returns the name of the queue.
"""
return self._impl.name
@property
def payload_type(self) -> Union[DbObjectType, None]:
"""
This read-only attribute returns the object type for payloads that can
be enqueued and dequeued. If using a JSON queue, this returns the value
"JSON". If using a raw queue, this returns the value *None*.
"""
if self._payload_type is None:
if self._impl.is_json:
self._payload_type = "JSON"
elif self._impl.payload_type is not None:
self._payload_type = DbObjectType._from_impl(
self._impl.payload_type
)
return self._payload_type
@property
def payloadType(self) -> Union[DbObjectType, None]:
"""
Deprecated: use payload_type instead.
"""
return self.payload_type
class Queue(BaseQueue):
def deqmany(self, max_num_messages: int) -> list["MessageProperties"]:
"""
Dequeues up to the specified number of messages from the queue and
returns a list of these messages.
"""
if self._impl._supports_deq_many(self._connection._impl):
message_impls = self._impl.deq_many(max_num_messages)
else:
message_impls = []
while len(message_impls) < max_num_messages:
message_impl = self._impl.deq_one()
if message_impl is None:
break
message_impls.append(message_impl)
return [MessageProperties._from_impl(impl) for impl in message_impls]
def deqMany(self, max_num_messages: int) -> list["MessageProperties"]:
"""
Deprecated: use deqmany() instead.
"""
return self.deqmany(max_num_messages)
def deqone(self) -> Union["MessageProperties", None]:
"""
Dequeues at most one message from the queue and returns it. If no
message is dequeued, None is returned.
"""
message_impl = self._impl.deq_one()
if message_impl is not None:
return MessageProperties._from_impl(message_impl)
def deqOne(self) -> Union["MessageProperties", None]:
"""
Deprecated: use deqone() instead.
"""
return self.deqone()
def enqmany(self, messages: list["MessageProperties"]) -> None:
"""
Enqueues multiple messages into the queue. The messages parameter must
be a sequence containing message property objects which have all had
their payload attribute set to a value that the queue supports.
Warning: In python-oracledb Thick mode using Oracle Client libraries
prior to 21c, calling :meth:`Queue.enqmany()` in parallel on different
connections acquired from the same connection pool may fail due to
Oracle bug 29928074. To avoid this, do one of: upgrade the client
libraries, ensure that :meth:`Queue.enqmany()` is not run in parallel,
use standalone connections or connections from different pools, or make
multiple calls to :meth:`Queue.enqone()`. The function
:meth:`Queue.deqmany()` call is not affected.
"""
for message in messages:
self._verify_message(message)
message_impls = [m._impl for m in messages]
self._impl.enq_many(message_impls)
def enqMany(self, messages: list["MessageProperties"]) -> None:
"""
Deprecated: use enqmany() instead.
"""
return self.enqmany(messages)
def enqone(self, message: "MessageProperties") -> None:
"""
Enqueues a single message into the queue. The message must be a message
property object which has had its payload attribute set to a value that
the queue supports.
"""
self._verify_message(message)
self._impl.enq_one(message._impl)
def enqOne(self, message: "MessageProperties") -> None:
"""
Deprecated: use enqone() instead.
"""
return self.enqone(message)
class AsyncQueue(BaseQueue):
async def deqmany(
self, max_num_messages: int
) -> list["MessageProperties"]:
"""
Dequeues up to the specified number of messages from the queue and
returns a list of these messages.
"""
message_impls = await self._impl.deq_many(max_num_messages)
return [MessageProperties._from_impl(impl) for impl in message_impls]
async def deqone(self) -> Union["MessageProperties", None]:
"""
Dequeues at most one message from the queue and returns it. If no
message is dequeued, None is returned.
"""
message_impl = await self._impl.deq_one()
if message_impl is not None:
return MessageProperties._from_impl(message_impl)
async def enqmany(self, messages: list["MessageProperties"]) -> None:
"""
Enqueues multiple messages into the queue. The messages parameter must
be a sequence containing message property objects which have all had
their payload attribute set to a value that the queue supports.
Warning: calling this function in parallel on different connections
acquired from the same pool may fail due to Oracle bug 29928074. Ensure
that this function is not run in parallel, use standalone connections
or connections from different pools, or make multiple calls to
enqone() instead. The function Queue.deqmany() call is not affected.
"""
for message in messages:
self._verify_message(message)
message_impls = [m._impl for m in messages]
await self._impl.enq_many(message_impls)
async def enqone(self, message: "MessageProperties") -> None:
"""
Enqueues a single message into the queue. The message must be a message
property object which has had its payload attribute set to a value that
the queue supports.
"""
self._verify_message(message)
await self._impl.enq_one(message._impl)
class DeqOptions(metaclass=BaseMetaClass):
@classmethod
def _from_impl(cls, impl):
options = cls.__new__(cls)
options._impl = impl
return options
@property
def condition(self) -> str:
"""
This read-write attribute specifies a boolean expression similar to the
where clause of a SQL query. The boolean expression can include
conditions on message properties, user data properties, and PL/SQL or
SQL functions. The default is to have no condition specified.
"""
return self._impl.get_condition()
@condition.setter
def condition(self, value: str) -> None:
self._impl.set_condition(value)
@property
def consumername(self) -> str:
"""
This read-write attribute specifies the name of the consumer. Only
messages matching the consumer name will be accessed. If the queue is
not set up for multiple consumers this attribute should not be set. The
default is to have no consumer name specified.
"""
return self._impl.get_consumer_name()
@consumername.setter
def consumername(self, value: str) -> None:
self._impl.set_consumer_name(value)
@property
def correlation(self) -> str:
"""
This read-write attribute specifies the correlation identifier of the
message to be dequeued. Special pattern-matching characters, such as
the percent sign (%) and the underscore (_), can be used. If multiple
messages satisfy the pattern, the order of dequeuing is indeterminate.
The default is to have no correlation specified.
"""
return self._impl.get_correlation()
@correlation.setter
def correlation(self, value: str) -> None:
self._impl.set_correlation(value)
@property
def deliverymode(self) -> int:
"""
This write-only attribute specifies what types of messages should be
dequeued. It should be one of the values
:data:`~oracledb.MSG_PERSISTENT` (default),
:data:`~oracledb.MSG_BUFFERED`, or
:data:`~oracledb.MSG_PERSISTENT_OR_BUFFERED`.
Note that :data:`~oracledb.MSG_BUFFERED` is not supported for JSON
payloads.
"""
raise AttributeError("deliverymode can only be written")
@deliverymode.setter
def deliverymode(self, value: int) -> None:
self._impl.set_delivery_mode(value)
@property
def mode(self) -> int:
"""
This read-write attribute specifies the locking behaviour associated
with the dequeue operation. It should be one of the values
:data:`~oracledb.DEQ_BROWSE`, :data:`~oracledb.DEQ_LOCKED`,
:data:`~oracledb.DEQ_REMOVE` (default), or
:data:`~oracledb.DEQ_REMOVE_NODATA`.
"""
return self._impl.get_mode()
@mode.setter
def mode(self, value: int) -> None:
self._impl.set_mode(value)
@property
def msgid(self) -> bytes:
"""
This read-write attribute specifies the identifier of the message to
be dequeued. The default is to have no message identifier specified.
"""
return self._impl.get_message_id()
@msgid.setter
def msgid(self, value: bytes) -> None:
self._impl.set_message_id(value)
@property
def navigation(self) -> int:
"""
This read-write attribute specifies the position of the message that is
retrieved. It should be one of the values
:data:`~oracledb.DEQ_FIRST_MSG`, :data:`~oracledb.DEQ_NEXT_MSG`
(default), or :data:`~oracledb.DEQ_NEXT_TRANSACTION`.
"""
return self._impl.get_navigation()
@navigation.setter
def navigation(self, value: int) -> None:
self._impl.set_navigation(value)
@property
def transformation(self) -> str:
"""
This read-write attribute specifies the name of the transformation that
must be applied after the message is dequeued from the database but
before it is returned to the calling application. The transformation
must be created using dbms_transform. The default is to have no
transformation specified.
"""
return self._impl.get_transformation()
@transformation.setter
def transformation(self, value: str) -> None:
self._impl.set_transformation(value)
@property
def visibility(self) -> int:
"""
This read-write attribute specifies the transactional behavior of the
dequeue request. It should be one of the values
:data:`~oracledb.DEQ_ON_COMMIT` (default) or
:data:`~oracledb.DEQ_IMMEDIATE`. This attribute is ignored when using
the :data:`~oracledb.DEQ_BROWSE` mode. Note the value of
:attr:`~Connection.autocommit` is always ignored.
"""
return self._impl.get_visibility()
@visibility.setter
def visibility(self, value: int) -> None:
self._impl.set_visibility(value)
@property
def wait(self) -> int:
"""
This read-write attribute specifies the time to wait, in seconds, for a
message matching the search criteria to become available for dequeuing.
One of the values :data:`~oracledb.DEQ_NO_WAIT` or
:data:`~oracledb.DEQ_WAIT_FOREVER` can also be used. The default is
:data:`~oracledb.DEQ_WAIT_FOREVER`.
"""
return self._impl.get_wait()
@wait.setter
def wait(self, value: int) -> None:
self._impl.set_wait(value)
class EnqOptions(metaclass=BaseMetaClass):
@classmethod
def _from_impl(cls, impl):
options = cls.__new__(cls)
options._impl = impl
return options
@property
def deliverymode(self) -> int:
"""
This write-only attribute specifies what type of messages should be
enqueued. It should be one of the values
:data:`~oracledb.MSG_PERSISTENT` (default) or
:data:`~oracledb.MSG_BUFFERED`.
Note that :data:`~oracledb.MSG_BUFFERED` is not supported for JSON
payloads.
"""
raise AttributeError("deliverymode can only be written")
@deliverymode.setter
def deliverymode(self, value: int) -> None:
self._impl.set_delivery_mode(value)
@property
def transformation(self) -> str:
"""
This read-write attribute specifies the name of the transformation that
must be applied before the message is enqueued into the database. The
transformation must be created using dbms_transform. The default is to
have no transformation specified.
"""
return self._impl.get_transformation()
@transformation.setter
def transformation(self, value: str) -> None:
self._impl.set_transformation(value)
@property
def visibility(self) -> int:
"""
This read-write attribute specifies the transactional behavior of the
enqueue request. It should be one of the values
:data:`~oracledb.ENQ_ON_COMMIT` (default) or
:data:`~oracledb.ENQ_IMMEDIATE`. Note the value of
:attr:`~Connection.autocommit` is ignored.
"""
return self._impl.get_visibility()
@visibility.setter
def visibility(self, value: int) -> None:
self._impl.set_visibility(value)
class MessageProperties(metaclass=BaseMetaClass):
_recipients = []
@classmethod
def _from_impl(cls, impl):
props = cls.__new__(cls)
props._impl = impl
return props
@property
def attempts(self) -> int:
"""
This read-only attribute specifies the number of attempts that have
been made to dequeue the message.
"""
return self._impl.get_num_attempts()
@property
def correlation(self) -> str:
"""
This read-write attribute specifies the correlation used when the
message was enqueued.
"""
return self._impl.get_correlation()
@correlation.setter
def correlation(self, value: str) -> None:
self._impl.set_correlation(value)
@property
def delay(self) -> int:
"""
This read-write attribute specifies the number of seconds to delay an
enqueued message. Any integer is acceptable but the constant
:data:`~oracledb.MSG_NO_DELAY` can also be used indicating that the
message is available for immediate dequeuing.
"""
return self._impl.get_delay()
@delay.setter
def delay(self, value: int) -> None:
self._impl.set_delay(value)
@property
def deliverymode(self) -> int:
"""
This read-only attribute specifies the type of message that was
dequeued. It will be one of the values
:data:`~oracledb.MSG_PERSISTENT` or
:data:`~oracledb.MSG_BUFFERED`.
"""
return self._impl.get_delivery_mode()
@property
def enqtime(self) -> datetime.datetime:
"""
This read-only attribute specifies the time that the message was
enqueued.
"""
return self._impl.get_enq_time()
@property
def exceptionq(self) -> str:
"""
This read-write attribute specifies the name of the queue to which the
message is moved if it cannot be processed successfully. Messages are
moved if the number of unsuccessful dequeue attempts has exceeded the
maximum number of retries or if the message has expired. All messages
in the exception queue are in the :data:`~oracledb.MSG_EXPIRED` state.
The default value is the name of the exception queue associated with
the queue table.
"""
return self._impl.get_exception_queue()
@exceptionq.setter
def exceptionq(self, value: str) -> None:
self._impl.set_exception_queue(value)
@property
def expiration(self) -> int:
"""
This read-write attribute specifies, in seconds, how long the message
is available for dequeuing. This attribute is an offset from the delay
attribute. Expiration processing requires the queue monitor to be
running. Any integer is accepted but the constant
:data:`~oracledb.MSG_NO_EXPIRATION` can also be used indicating that
the message never expires.
"""
return self._impl.get_expiration()
@expiration.setter
def expiration(self, value: int) -> None:
self._impl.set_expiration(value)
@property
def msgid(self) -> bytes:
"""
This read-only attribute specifies the id of the message in the last
queue that enqueued or dequeued this message. If the message has never
been dequeued or enqueued, the value will be `None`.
"""
return self._impl.get_message_id()
@property
def payload(self) -> Union[bytes, DbObject]:
"""
This read-write attribute specifies the payload that will be enqueued
or the payload that was dequeued when using a queue. When enqueuing,
the value is checked to ensure that it conforms to the type expected
by that queue. For RAW queues, the value can be a bytes object or a
string. If the value is a string it will be converted to bytes in the
encoding UTF-8.
"""
return self._impl.payload
@payload.setter
def payload(self, value: Any) -> None:
if isinstance(value, DbObject):
self._impl.set_payload_object(value._impl)
elif not isinstance(value, (str, bytes)):
self._impl.set_payload_json(value)
else:
if isinstance(value, str):
value_bytes = value.encode()
elif isinstance(value, bytes):
value_bytes = value
self._impl.set_payload_bytes(value_bytes)
self._impl.payload = value
@property
def priority(self) -> int:
"""
This read-write attribute specifies the priority of the message. A
smaller number indicates a higher priority. The priority can be any
integer, including negative numbers. The default value is 0.
"""
return self._impl.get_priority()
@priority.setter
def priority(self, value: int) -> None:
self._impl.set_priority(value)
@property
def recipients(self) -> list[str]:
"""
This read-write attribute specifies a list of recipient names that can
be associated with a message at the time a message is enqueued. This
allows a limited set of recipients to dequeue each message. The
recipient list associated with the message overrides the queue
subscriber list, if there is one. The recipient names need not be in
the subscriber list but can be, if desired.
To dequeue a message, the consumername attribute can be set to one of
the recipient names. The original message recipient list is not
available on dequeued messages. All recipients have to dequeue a
message before it gets removed from the queue.
Subscribing to a queue is like subscribing to a magazine: each
subscriber can dequeue all the messages placed into a specific queue,
just as each magazine subscriber has access to all its articles. Being
a recipient, however, is like getting a letter: each recipient is a
designated target of a particular message.
"""
return self._recipients
@recipients.setter
def recipients(self, value: list) -> None:
self._impl.set_recipients(value)
self._recipients = value
@property
def state(self) -> int:
"""
This read-only attribute specifies the state of the message at the time
of the dequeue. It will be one of the values
:data:`~oracledb.MSG_WAITING`, :data:`~oracledb.MSG_READY`,
:data:`~oracledb.MSG_PROCESSED`, or :data:`~oracledb.MSG_EXPIRED`.
"""
return self._impl.get_state()

View File

@@ -0,0 +1,112 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# arrow_array.py
#
# Implement an ArrowArray that is used for efficiently transferring Arrow
# array data to other data frame libraries.
# -----------------------------------------------------------------------------
from .arrow_impl import ArrowArrayImpl
from .base import BaseMetaClass
from . import errors
class ArrowArray(metaclass=BaseMetaClass):
_impl = None
def __init__(self):
errors._raise_err(errors.ERR_INTERNAL_CREATION_REQUIRED)
def __len__(self):
return self.num_rows
def __repr__(self):
return (
f"ArrowArray(name={self.name}, "
f"len={self.num_rows}, "
f"type={self.dtype})"
)
def __str__(self):
return self.__repr__()
@classmethod
def _from_arrow(cls, obj):
array = cls.__new__(cls)
array._impl = ArrowArrayImpl.from_arrow_array(obj)
return array
@classmethod
def _from_impl(cls, impl):
array = cls.__new__(cls)
array._impl = impl
return array
def __arrow_c_array__(self, requested_schema=None):
"""
Returns a tuple containing an ArrowSchema and ArrowArray PyCapsules.
"""
if requested_schema is not None:
raise NotImplementedError("requested_schema")
return (
self._impl.get_schema_capsule(),
self._impl.get_array_capsule(),
)
def __arrow_c_schema__(self):
"""
Returns an ArrowSchema PyCapsule.
"""
return self._impl.get_schema_capsule()
@property
def dtype(self) -> str:
"""
Returns the data type associated with the array.
"""
return self._impl.get_data_type()
@property
def name(self) -> str:
"""
Returns the name associated with the array.
"""
return self._impl.get_name()
@property
def null_count(self) -> int:
"""
Returns the number of rows that contain null values.
"""
return self._impl.get_null_count()
@property
def num_rows(self) -> int:
"""
Returns the number of rows in the array.
"""
return self._impl.get_num_rows()

View File

@@ -0,0 +1,48 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# base.py
#
# Contains base classes and methods that have no internal dependencies.
# -----------------------------------------------------------------------------
from . import __name__ as MODULE_NAME
# metaclass used by all oracledb classes; currently this only ensures that when
# the class is displayed it only shows the overall module name instead of any
# subpackage names
class BaseMetaClass(type):
def __new__(cls, name, bases, attrs):
module_name = attrs["__module__"]
qual_name = attrs["__qualname__"]
if module_name.startswith(MODULE_NAME):
module_name = MODULE_NAME
attrs["_public_name"] = f"{module_name}.{qual_name}"
return super().__new__(cls, name, bases, attrs)
def __repr__(cls):
return f"<class '{cls._public_name}'>"

View File

@@ -0,0 +1,102 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# config_provider.py
#
# Contains the built-in config providers.
# -----------------------------------------------------------------------------
import base64
import json
import os
import urllib.parse
import warnings
from . import errors
from .utils import register_password_type, register_protocol
def config_provider_file_hook(protocol, protocol_arg, connect_params):
"""
Hook for "config-file://". The protocol_arg is expected to be the name of a
file containing one or more configurations. An optional "key" parameter is
allowed which will choose a configuration from a set of configurations
stored in the file.
"""
pos = protocol_arg.find("?")
if pos < 0:
file_name = protocol_arg
key = None
else:
file_name = protocol_arg[:pos]
args = urllib.parse.parse_qs(protocol_arg[pos + 1 :])
key = args.get("key")
if key is not None:
key = key[0]
if not os.path.isabs(file_name):
if connect_params.config_dir is None:
errors._raise_err(errors.ERR_NO_CONFIG_DIR)
file_name = os.path.join(connect_params.config_dir, file_name)
config = json.load(open(file_name))
if key is not None:
config = config[key]
connect_params.set_from_config(config)
register_protocol("config-file", config_provider_file_hook)
def ldap_hook(protocol, arg, params):
"""
Default hook for LDAP which simply points the user to the documentation
which explains how they can write their own hook for LDAP.
This hook is needed for python-oracledb Thin mode,or when
defaults.thick_mode_dsn_passthrough is False in Thick mode.
"""
doc_url = (
"https://python-oracledb.readthedocs.io/en/latest"
"/user_guide/connection_handling.html#ldap-directory-naming"
)
message = (
f"To use an LDAP URL in python-oracledb, "
f"register an LDAP resolution function as shown in {doc_url}"
)
raise Exception(message)
register_protocol("ldap", ldap_hook)
register_protocol("ldaps", ldap_hook)
def password_type_base64_hook(args):
"""
Hook for password type "base64". The key "value" in the supplied args is
expected to be a base64-encoded string.
"""
warnings.warn("base64 encoded passwords are insecure")
return base64.b64decode(args["value"].encode()).decode()
register_password_type("base64", password_type_base64_hook)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# constants.py
#
# Contains the constants defined by the package.
# -----------------------------------------------------------------------------
# AQ delivery modes
MSG_BUFFERED = 2
MSG_PERSISTENT = 1
MSG_PERSISTENT_OR_BUFFERED = 3
# AQ dequeue modes
DEQ_BROWSE = 1
DEQ_LOCKED = 2
DEQ_REMOVE = 3
DEQ_REMOVE_NODATA = 4
# AQ dequeue navigation modes
DEQ_FIRST_MSG = 1
DEQ_NEXT_MSG = 3
DEQ_NEXT_TRANSACTION = 2
# AQ dequeue visibility modes
DEQ_IMMEDIATE = 1
DEQ_ON_COMMIT = 2
# AQ dequeue wait modes
DEQ_NO_WAIT = 0
DEQ_WAIT_FOREVER = 2**32 - 1
# AQ enqueue visibility modes
ENQ_IMMEDIATE = 1
ENQ_ON_COMMIT = 2
# AQ message states
MSG_EXPIRED = 3
MSG_PROCESSED = 2
MSG_READY = 0
MSG_WAITING = 1
# AQ other constants
MSG_NO_DELAY = 0
MSG_NO_EXPIRATION = -1
# shutdown modes
DBSHUTDOWN_ABORT = 4
DBSHUTDOWN_FINAL = 5
DBSHUTDOWN_IMMEDIATE = 3
DBSHUTDOWN_TRANSACTIONAL = 1
DBSHUTDOWN_TRANSACTIONAL_LOCAL = 2
# subscription grouping classes
SUBSCR_GROUPING_CLASS_NONE = 0
SUBSCR_GROUPING_CLASS_TIME = 1
# subscription grouping types
SUBSCR_GROUPING_TYPE_SUMMARY = 1
SUBSCR_GROUPING_TYPE_LAST = 2
# subscription namespaces
SUBSCR_NAMESPACE_AQ = 1
SUBSCR_NAMESPACE_DBCHANGE = 2
# subscription protocols
SUBSCR_PROTO_HTTP = 3
SUBSCR_PROTO_MAIL = 1
SUBSCR_PROTO_CALLBACK = 0
SUBSCR_PROTO_SERVER = 2
# subscription quality of service
SUBSCR_QOS_BEST_EFFORT = 0x10
SUBSCR_QOS_DEFAULT = 0
SUBSCR_QOS_DEREG_NFY = 0x02
SUBSCR_QOS_QUERY = 0x08
SUBSCR_QOS_RELIABLE = 0x01
SUBSCR_QOS_ROWIDS = 0x04
# event types
EVENT_AQ = 100
EVENT_DEREG = 5
EVENT_NONE = 0
EVENT_OBJCHANGE = 6
EVENT_QUERYCHANGE = 7
EVENT_SHUTDOWN = 2
EVENT_SHUTDOWN_ANY = 3
EVENT_STARTUP = 1
# operation codes
OPCODE_ALLOPS = 0
OPCODE_ALLROWS = 0x01
OPCODE_ALTER = 0x10
OPCODE_DELETE = 0x08
OPCODE_DROP = 0x20
OPCODE_INSERT = 0x02
OPCODE_UPDATE = 0x04
# flags for tpc_end()
TPC_END_NORMAL = 0
TPC_END_SUSPEND = 0x00100000
# vector metadata flags
VECTOR_META_FLAG_FLEXIBLE_DIM = 0x01
VECTOR_META_FLAG_SPARSE_VECTOR = 0x02

View File

@@ -0,0 +1,100 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# constructors.py
#
# Contains the constructors mandated by the Python Database API.
# -----------------------------------------------------------------------------
import datetime
from typing import Any
from . import errors
def Binary(value: Any) -> bytes:
"""
Constructs an object holding a binary (long) string value.
"""
return bytes(value)
def Date(year: int, month: int, day: int) -> datetime.date:
"""
Constructs an object holding a date value.
"""
return datetime.date(year, month, day)
def DateFromTicks(ticks: float) -> datetime.date:
"""
Constructor mandated by the database API for creating a date value given
the number of seconds since the epoch (January 1, 1970). This is equivalent
to using datetime.date.fromtimestamp() and that should be used instead.
"""
return datetime.date.fromtimestamp(ticks)
def Time(hour: int, minute: int, second: int) -> None:
"""
Constructor mandated by the database API for creating a time value. Since
Oracle doesn't support time only values, an exception is raised when this
method is called.
"""
errors._raise_err(errors.ERR_TIME_NOT_SUPPORTED)
def TimeFromTicks(ticks: float) -> None:
"""
Constructor mandated by the database API for creating a time value given
the number of seconds since the epoch (January 1, 1970). Since Oracle
doesn't support time only values, an exception is raised when this method
is called.
"""
errors._raise_err(errors.ERR_TIME_NOT_SUPPORTED)
def Timestamp(
year: int,
month: int,
day: int,
hour: int = 0,
minute: int = 0,
second: int = 0,
) -> datetime.datetime:
"""
Constructs an object holding a time stamp value.
"""
return datetime.datetime(year, month, day, hour, minute, second)
def TimestampFromTicks(ticks: float) -> datetime.datetime:
"""
Constructor mandated by the database API for creating a timestamp value
given the number of seconds since the epoch (January 1, 1970). This is
equivalent to using datetime.datetime.fromtimestamp() and that should be
used instead.
"""
return datetime.datetime.fromtimestamp(ticks)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# dataframe.py
#
# Implement a data frame that can be used for efficiently transferring Arrow
# array data to other data frame libraries.
# -----------------------------------------------------------------------------
from .arrow_array import ArrowArray
from .arrow_impl import DataFrameImpl
from .base import BaseMetaClass
from . import errors
class DataFrame(metaclass=BaseMetaClass):
_impl = None
def __init__(self):
errors._raise_err(errors.ERR_INTERNAL_CREATION_REQUIRED)
@classmethod
def _from_arrow(cls, obj):
df = cls.__new__(cls)
df._initialize(DataFrameImpl.from_arrow_stream(obj))
return df
@classmethod
def _from_impl(cls, impl):
df = cls.__new__(cls)
df._initialize(impl)
return df
def _initialize(self, impl):
"""
Initializes the object given the implementation.
"""
self._impl = impl
self._arrays = [ArrowArray._from_impl(a) for a in impl.get_arrays()]
self._arrays_by_name = {}
for array in self._arrays:
self._arrays_by_name[array.name] = array
def __arrow_c_stream__(self, requested_schema=None):
"""
Returns the ArrowArrayStream PyCapsule which allows direct conversion
to foreign data frames that support this interface.
"""
if requested_schema is not None:
raise NotImplementedError("requested_schema")
return self._impl.get_stream_capsule()
def column_arrays(self) -> list[ArrowArray]:
"""
Returns a list of ArrowArray objects, each containing a select list
column.
"""
return self._arrays
def column_names(self) -> list[str]:
"""
Returns a list of the column names in the data frame.
"""
return [a.name for a in self._arrays]
def get_column(self, i: int) -> ArrowArray:
"""
Returns an :ref:`ArrowArray <oraclearrowarrayobj>` object for the
column at the given index ``i``. If the index is out of range, an
IndexError exception is raised.
"""
if i < 0 or i >= self.num_columns():
raise IndexError(
f"Column index {i} is out of bounds for "
f"DataFrame with {self.num_columns()} columns"
)
return self._arrays[i]
def get_column_by_name(self, name: str) -> ArrowArray:
"""
Returns an :ref:`ArrowArray <oraclearrowarrayobj>` object for the
column with the given name ``name``. If the column name is not found,
a KeyError exception is raised.
"""
try:
return self._arrays_by_name[name]
except KeyError:
raise KeyError(f"Column {name} not found in DataFrame")
def num_columns(self) -> int:
"""
Returns the number of columns in the data frame.
"""
return len(self._arrays)
def num_rows(self) -> int:
"""
Returns the number of rows in the data frame.
"""
return len(self._arrays[0])

View File

@@ -0,0 +1,392 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# dbobject.py
#
# Contains the classes used for managing database objects and the database
# object type metadata: DbObject, DbObjectType and DbObjectAttr.
# -----------------------------------------------------------------------------
from typing import Any, Sequence, Union
from . import errors
from .base import BaseMetaClass
from .base_impl import DbType
class DbObject(metaclass=BaseMetaClass):
def __getattr__(self, name):
try:
attr_impl = self._impl.type.attrs_by_name[name]
except KeyError:
return super().__getattribute__(name)
return self._impl.get_attr_value(attr_impl)
def __iter__(self):
self._ensure_is_collection()
ix = self._impl.get_first_index()
while ix is not None:
yield self._impl.get_element_by_index(ix)
ix = self._impl.get_next_index(ix)
def __repr__(self):
cls_name = self.__class__._public_name
return f"<{cls_name} {self.type._get_full_name()} at {hex(id(self))}>"
def __setattr__(self, name, value):
if name == "_impl" or name == "_type":
super().__setattr__(name, value)
else:
attr_impl = self._impl.type.attrs_by_name[name]
self._impl.set_attr_value(attr_impl, value)
def _ensure_is_collection(self):
"""
Ensures that the object refers to a collection. If not, an exception is
raised.
"""
if not self.type.iscollection:
errors._raise_err(
errors.ERR_OBJECT_IS_NOT_A_COLLECTION,
name=self.type._get_full_name(),
)
@classmethod
def _from_impl(cls, impl):
obj = cls.__new__(cls)
obj._impl = impl
obj._type = None
return obj
def append(self, element: Any) -> None:
"""
Appends an element to the collection object. If no elements exist in
the collection, this creates an element at index 0; otherwise, it
creates an element immediately following the highest index available in
the collection.
"""
self._ensure_is_collection()
self._impl.append(element)
def asdict(self) -> dict:
"""
Returns a dictionary where the collections indexes are the keys and
the elements are its values.
"""
self._ensure_is_collection()
result = {}
ix = self._impl.get_first_index()
while ix is not None:
result[ix] = self._impl.get_element_by_index(ix)
ix = self._impl.get_next_index(ix)
return result
def aslist(self) -> list:
"""
Returns a list of each of the collections elements in index order.
"""
return list(self)
def copy(self) -> "DbObject":
"""
Creates a copy of the object and returns it.
"""
copied_impl = self._impl.copy()
return DbObject._from_impl(copied_impl)
def delete(self, index: int) -> None:
"""
Delete the element at the specified index of the collection. If the
element does not exist or is otherwise invalid, an error is raised.
Note that the indices of the remaining elements in the collection are
not changed. In other words, the delete operation creates holes in the
collection.
"""
self._ensure_is_collection()
self._impl.delete_by_index(index)
def exists(self, index: int) -> bool:
"""
Return True or False indicating if an element exists in the collection
at the specified index.
"""
self._ensure_is_collection()
return self._impl.exists_by_index(index)
def extend(self, seq: list) -> None:
"""
Appends all of the elements in the sequence to the collection. This is
the equivalent of performing append() for each element found in the
sequence.
"""
self._ensure_is_collection()
for value in seq:
self.append(value)
def first(self) -> int:
"""
Returns the index of the first element in the collection. If the
collection is empty, None is returned.
"""
self._ensure_is_collection()
return self._impl.get_first_index()
def getelement(self, index: int) -> Any:
"""
Returns the element at the specified index of the collection. If no
element exists at that index, an exception is raised.
"""
self._ensure_is_collection()
return self._impl.get_element_by_index(index)
def last(self) -> int:
"""
Returns the index of the last element in the collection. If the
collection is empty, None is returned.
"""
self._ensure_is_collection()
return self._impl.get_last_index()
def next(self, index: int) -> int:
"""
Returns the index of the next element in the collection following the
specified index. If there are no elements in the collection following
the specified index, None is returned.
"""
self._ensure_is_collection()
return self._impl.get_next_index(index)
def prev(self, index: int) -> int:
"""
Returns the index of the element in the collection preceding the
specified index. If there are no elements in the collection preceding
the specified index, None is returned.
"""
self._ensure_is_collection()
return self._impl.get_prev_index(index)
def setelement(self, index: int, value: Any) -> None:
"""
Sets the value in the collection at the specified index to the given
value.
"""
self._ensure_is_collection()
self._impl.set_element_by_index(index, value)
def size(self) -> int:
"""
Returns the number of elements in the collection.
"""
self._ensure_is_collection()
return self._impl.get_size()
def trim(self, num: int) -> None:
"""
Removes the specified number of elements from the end of the
collection.
"""
self._ensure_is_collection()
self._impl.trim(num)
@property
def type(self) -> "DbObjectType":
"""
This read-only attribute arturns an ObjectType corresponding to the
type of the object.
"""
if self._type is None:
self._type = DbObjectType._from_impl(self._impl.type)
return self._type
class DbObjectAttr(metaclass=BaseMetaClass):
def __repr__(self):
return f"<oracledb.DbObjectAttr {self.name}>"
@classmethod
def _from_impl(cls, impl):
attr = cls.__new__(cls)
attr._impl = impl
attr._type = None
return attr
@property
def max_size(self) -> Union[int, None]:
"""
This read-only attribute returns the maximum size (in bytes) of the
attribute when the attribute's type is one of
DB_TYPE_RAW, DB_TYPE_CHAR, DB_TYPE_NCHAR, DB_TYPE_VARCHAR and
DB_TYPE_NVARCHAR. For all other types, the value returned is None.
"""
if self._impl.max_size:
return self._impl.max_size
@property
def name(self) -> str:
"""
This read-only attribute returns the name of the attribute.
"""
return self._impl.name
@property
def precision(self) -> Union[int, None]:
"""
This read-only attribute returns the precision of the attribute when
the attribute's type is DB_TYPE_NUMBER. For all other types, the value
returned is None.
"""
if self._impl.precision or self._impl.scale:
return self._impl.precision
@property
def scale(self) -> Union[int, None]:
"""
This read-only attribute returns the scale of the attribute when the
attribute's type is DB_TYPE_NUMBER. For all other types, the value
returned is None.
"""
if self._impl.precision or self._impl.scale:
return self._impl.scale
@property
def type(self) -> Union["DbObjectType", DbType]:
"""
This read-only attribute returns the type of the attribute. This will
be an Oracle Object Type if the variable binds Oracle objects;
otherwise, it will be one of the database type constants.
"""
if self._type is None:
if self._impl.objtype is not None:
self._type = DbObjectType._from_impl(self._impl.objtype)
else:
self._type = self._impl.dbtype
return self._type
class DbObjectType(metaclass=BaseMetaClass):
def __call__(self, value: Sequence = None) -> DbObject:
"""
The object type may be called directly and serves as an alternative way
of calling :meth:`~DbObjectType.newobject()`.
"""
return self.newobject(value)
def __eq__(self, other):
if isinstance(other, DbObjectType):
return other._impl == self._impl
return NotImplemented
def __repr__(self):
return f"<oracledb.DbObjectType {self._get_full_name()}>"
@classmethod
def _from_impl(cls, impl):
typ = cls.__new__(cls)
typ._impl = impl
typ._attributes = None
typ._element_type = None
return typ
def _get_full_name(self):
"""
Returns the full name of the type.
"""
return self._impl._get_fqn()
@property
def attributes(self) -> list["DbObjectAttr"]:
"""
This read-only attribute returns a list of the attributes that make up
the object type.
"""
if self._attributes is None:
self._attributes = [
DbObjectAttr._from_impl(i) for i in self._impl.attrs
]
return self._attributes
@property
def iscollection(self) -> bool:
"""
This read-only attribute returns a boolean indicating if the object
type refers to a collection or not.
"""
return self._impl.is_collection
@property
def name(self) -> str:
"""
This read-only attribute returns the name of the type.
"""
return self._impl.name
@property
def element_type(self) -> Union["DbObjectType", DbType]:
"""
This read-only attribute returns the type of elements found in
collections of this type, if iscollection is True; otherwise, it
returns None. If the collection contains objects, this will be another
object type; otherwise, it will be one of the database type constants.
"""
if self._element_type is None:
if self._impl.element_metadata.objtype is not None:
typ_impl = self._impl.element_metadata.objtype
self._element_type = DbObjectType._from_impl(typ_impl)
else:
self._element_type = self._impl.element_metadata.dbtype
return self._element_type
def newobject(self, value: Sequence = None) -> DbObject:
"""
Returns a new Oracle object of the given type. This object can then be
modified by setting its attributes and then bound to a cursor for
interaction with Oracle. If the object type refers to a collection, a
sequence may be passed and the collection will be initialized with the
items in that sequence.
"""
obj_impl = self._impl.create_new_object()
obj = DbObject._from_impl(obj_impl)
if value is not None:
obj.extend(value)
return obj
@property
def package_name(self) -> str:
"""
This read-only attribute returns the name of the package containing the
PL/SQL type or None if the type is not a PL/SQL type.
"""
return self._impl.package_name
@property
def schema(self) -> str:
"""
This read-only attribute returns the name of the schema that owns the
type.
"""
return self._impl.schema

View File

@@ -0,0 +1,329 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# defaults.py
#
# Contains the Defaults class used for managing default values used throughout
# the module.
# -----------------------------------------------------------------------------
from . import base_impl
from . import errors
from .base import BaseMetaClass
class Defaults(metaclass=BaseMetaClass):
"""
A singleton Defaults object contains attributes to adjust default
behaviors of python-oracledb. It is accessed using the :data:`defaults`
attribute of the imported module.
"""
def __init__(self) -> None:
self._impl = base_impl.DEFAULTS
@property
def arraysize(self) -> int:
"""
This read-write attribute specifies the default arraysize to use when
cursors are created.
It is an attribute for tuning the performance of fetching rows from
Oracle Database. It does not affect data insertion.
This value is the default for :attr:`Cursor.arraysize` and
:attr:`AsyncCursor.arraysize`.
This attribute has an initial value of *100*.
"""
return self._impl.arraysize
@arraysize.setter
def arraysize(self, value: int):
self._impl.arraysize = value
@property
def config_dir(self) -> str:
"""
This read-write attribute specifies the directory in which the optional
configuration file ``tnsnames.ora`` will be read in python-oracledb
Thin mode. It is also used in Thick mode if
:attr:`Defaults.thick_mode_dsn_passthrough` is *False*.
At time of ``import oracledb`` the value of
``oracledb.defaults.config_dir`` will be set to (first one wins):
- the value of ``$TNS_ADMIN``, if ``TNS_ADMIN`` is set.
- ``$ORACLE_HOME/network/admin``, if ``$ORACLE_HOME`` is set.
Otherwise, ``oracledb.defaults.config_dir`` will not be set.
At completion of a call to :meth:`oracledb.init_oracle_client()` in
python-oracledb Thick mode, the value of ``config_dir`` may get
changed.
"""
return self._impl.config_dir
@config_dir.setter
def config_dir(self, value: str):
self._impl.config_dir = value
@property
def fetch_lobs(self) -> bool:
"""
This read-write attribute specifies whether queries that contain LOBs
should return LOB objects or their contents instead.
When the value of this attribute is *True*, then queries to LOB columns
return LOB locators. When the value of this attribute is *False*, then
CLOBs and NCLOBs are fetched as strings, and BLOBs are fetched as
bytes. If LOBs are larger than 1 GB, then this attribute should be set
to *True* and the LOBs should be streamed.
The value of ``oracledb.defaults.fetch_lobs`` does not affect LOBs
returned as OUT binds.
The value of ``fetch_lobs`` can be overridden at statement execution by
passing an equivalent parameter.
An output type handler such as the one previously required in the
obsolete cx_Oracle driver can alternatively be used to adjust the
returned type. If a type handler exists and returns a variable (that
is, `cursor.var(...)`), then that return variable is used. If the type
handler returns *None*, then the value of
``oracledb.defaults.fetch_lobs`` is used.
This attribute has an initial value of *True*.
"""
return self._impl.fetch_lobs
@fetch_lobs.setter
def fetch_lobs(self, value: bool):
self._impl.fetch_lobs = value
@property
def fetch_decimals(self) -> bool:
"""
This read-write attribute specifies whether queries that contain
numbers should be fetched as Python decimal.Decimal objects or floating
point numbers. This can help avoid issues with converting numbers from
Oracle Database's decimal format to Python's binary format.
The value of ``fetch_decimals`` can be overridden at statement
execution by passing an equivalent parameter.
An output type handler such as previously required in the obsolete
cx_Oracle driver can alternatively be used to adjust the returned type.
If a type handler exists and returns a variable (that is,
``cursor.var(...)``), then that return variable is used. If the type
handler returns *None*, then the value of
``oracledb.defaults.fetch_decimals`` is used to determine whether to
return ``decimal.Decimal`` values.
This attribute has an initial value of *False*.
"""
return self._impl.fetch_decimals
@fetch_decimals.setter
def fetch_decimals(self, value: bool):
self._impl.fetch_decimals = value
@property
def prefetchrows(self) -> int:
"""
This read-write attribute specifies the default number of rows to
prefetch when cursors are executed.
This is an attribute for tuning the performance of fetching rows from
Oracle Database. It does not affect data insertion.
This value is the default for :attr:`Cursor.prefetchrows` and
:attr:`AsyncCursor.prefetchrows`.
This attribute is ignored when using :meth:`Connection.fetch_df_all()`
or :meth:`Connection.fetch_df_batches()` since these methods always set
the internal prefetch size to their relevant ``arraysize`` or ``size``
parameter value.
This attribute has an initial value of *2*.
"""
return self._impl.prefetchrows
@prefetchrows.setter
def prefetchrows(self, value: int):
self._impl.prefetchrows = value
@property
def stmtcachesize(self) -> int:
"""
This read-write attribute specifies the default size of the statement
cache.
This is an attribute for tuning statement execution performance when a
statement is executed more than once.
This value is the default for :attr:`Connection.stmtcachesize`,
:attr:`ConnectionPool.stmtcachesize`,
:attr:`AsyncConnection.stmtcachesize`, and
:attr:`AsyncConnectionPool.stmtcachesize`.
This attribute has an initial value of *20*.
"""
return self._impl.stmtcachesize
@stmtcachesize.setter
def stmtcachesize(self, value: int):
self._impl.stmtcachesize = value
@property
def program(self) -> str:
"""
This read-write attribute is a string recorded by Oracle Database
as the program from which the connection originates. This is the value
used in the PROGRAM column of the V$SESSION view.
This attribute has an initial value that is populated by
`sys.executable <https://docs.python.org/3/library/sys.html#
sys.executable>`__.
This attribute is only used in python-oracledb Thin mode.
"""
return self._impl.program
@program.setter
def program(self, value: str):
if base_impl.sanitize(value) != value:
errors._raise_err(errors.ERR_INVALID_NETWORK_NAME, name="program")
self._impl.program = value
@property
def machine(self) -> str:
"""
This read-write attribute is a string recorded by Oracle Database as
the name of machine from which the connection originates. This is the
value used in the MACHINE column of the V$SESSION view.
This attribute takes the host name where the application is running as
its initial value.
This attribute is only used in python-oracledb Thin mode.
"""
return self._impl.machine
@machine.setter
def machine(self, value: str):
if base_impl.sanitize(value) != value:
errors._raise_err(errors.ERR_INVALID_NETWORK_NAME, name="machine")
self._impl.machine = value
@property
def terminal(self) -> str:
"""
This read-write attribute specifies the terminal identifier from which
the connection originates. This is the value used in the TERMINAL
column of the V$SESSION view.
This attribute has an initial value of "unknown".
This attribute is only used in python-oracledb Thin mode.
"""
return self._impl.terminal
@terminal.setter
def terminal(self, value: str):
self._impl.terminal = value
@property
def osuser(self) -> str:
"""
This read-write attribute is a string recorded by Oracle Database
as the operating system user who originated the connection. This is the
value used in the OSUSER column of the V$SESSION view.
This attribute takes the login name of the user as its initial value.
This attribute is only used in python-oracledb Thin mode.
"""
return self._impl.osuser
@osuser.setter
def osuser(self, value: str):
if base_impl.sanitize(value) != value:
errors._raise_err(errors.ERR_INVALID_NETWORK_NAME, name="osuser")
self._impl.osuser = value
@property
def driver_name(self) -> str:
"""
This read-write attribute is a string recorded by Oracle Database
as the name of the driver which originated the connection. This is the
value used in the CLIENT_DRIVER column of the V$SESSION_CONNECT_INFO
view.
This attribute has an initial value of *None*. It is used as required
in python-oracledb Thick and Thin mode.
In python-oracledb Thick mode, this attribute is used if the
``driver_name`` parameter is not specified in
:meth:`oracledb.init_oracle_client()`. In Thin mode, this attribute is
used if the ``driver_name`` parameter is not specified in
:meth:`oracledb.connect()`, :meth:`oracledb.connect_async()`,
:meth:`oracledb.create_pool()`, or
:meth:`oracledb.create_pool_async()`. If the value of this attribute is
*None*, the value set when connecting in python-oracledb Thick mode is
like "python-oracledb thk : <version>" and in Thin mode is like
"python-oracledb thn : <version>".
"""
return self._impl.driver_name
@driver_name.setter
def driver_name(self, value: str):
self._impl.driver_name = value
@property
def thick_mode_dsn_passthrough(self) -> bool:
"""
This read-write attribute determines whether
connection strings passed as the ``dsn`` parameter to
:meth:`oracledb.connect()`, :meth:`oracledb.create_pool()`,
:meth:`oracledb.connect_async()`, and
:meth:`oracledb.create_pool_async()` in python-oracledb Thick mode will
be parsed by Oracle Client libraries or by python-oracledb itself.
The value of ``thick_mode_dsn_passthrough`` is ignored in
python-oracledb Thin mode, which always parses all connect strings
(including reading a tnsnames.ora file, if required).
This attribute has an initial value of *True*.
"""
return self._impl.thick_mode_dsn_passthrough
@thick_mode_dsn_passthrough.setter
def thick_mode_dsn_passthrough(self, value: str):
self._impl.thick_mode_dsn_passthrough = value
defaults = Defaults()

View File

@@ -0,0 +1,140 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2025 Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# driver_mode.py
#
# Contains a simple method for checking and returning which mode the driver is
# currently using. The driver only supports creating connections and pools with
# either the thin implementation or the thick implementation, not both
# simultaneously.
# -----------------------------------------------------------------------------
import threading
from . import errors
# The DriverModeHandler class is used to manage which mode the driver is using.
#
# The "thin_mode" flag contains the current state:
# None: neither thick nor thin implementation has been used yet
# False: thick implementation is being used
# True: thin implementation is being used
#
# The "requested_thin_mode" flag is set to the mode that is being requested:
# False: thick implementation is being initialized
# True: thin implementation is being initialized
class DriverModeManager:
"""
Manages the mode the driver is using. The "thin_mode" flag contains the
current state:
None: neither thick nor thin implementation has been used yet
False: thick implementation is being used
True: thin implementation is being used
The "requested_thin_mode" is set to the mode that is being requested, but
only while initialization is taking place (otherwise, it contains the value
None):
False: thick implementation is being initialized
True: thin implementation is being initialized
The condition is used to ensure that only one thread is performing
initialization.
"""
def __init__(self):
self.thin_mode = None
self.requested_thin_mode = None
self.condition = threading.Condition()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
with self.condition:
if (
exc_type is None
and exc_value is None
and exc_tb is None
and self.requested_thin_mode is not None
):
self.thin_mode = self.requested_thin_mode
self.requested_thin_mode = None
self.condition.notify()
@property
def thin(self):
if self.requested_thin_mode is not None:
return self.requested_thin_mode
return self.thin_mode
manager = DriverModeManager()
def get_manager(requested_thin_mode=None):
"""
Returns the manager, but only after ensuring that no other threads are
attempting to initialize the mode.
"""
with manager.condition:
if manager.thin_mode is None:
if manager.requested_thin_mode is not None:
manager.condition.wait()
if manager.thin_mode is None:
if requested_thin_mode is None:
manager.requested_thin_mode = True
else:
manager.requested_thin_mode = requested_thin_mode
elif (
requested_thin_mode is not None
and requested_thin_mode != manager.thin_mode
):
if requested_thin_mode:
errors._raise_err(errors.ERR_THICK_MODE_ENABLED)
else:
errors._raise_err(errors.ERR_THIN_CONNECTION_ALREADY_CREATED)
return manager
def is_thin_mode() -> bool:
"""
Returns a boolean indicating if python-oracledb is in Thin mode.
Immediately after python-oracledb is imported, this function will return
*True* indicating that python-oracledb defaults to Thin mode. If a call to
:func:`oracledb.init_oracle_client()` returns successfully, then a
subsequent call to ``is_thin_mode()`` will return False indicating that
Thick mode is enabled. Once the first standalone connection or connection
pool is created, or a successful call to ``oracledb.init_oracle_client()``
is made, or :meth:`oracledb.enable_thin_mode()` is called, then
python-oracledbs mode is fixed and the value returned by
``is_thin_mode()`` will never change for the lifetime of the process.
The attribute :attr:`Connection.thin` can be used to check a connection's
mode. The attribute :attr:`ConnectionPool.thin` can be used to check a
pool's mode.
"""
if manager.thin_mode is not None:
return manager.thin_mode
return True

View File

@@ -0,0 +1,81 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# dsn.py
#
# Contains makedsn(), a method available for backwards compatibility with
# cx_Oracle. Use of the ConnectParams class or the keyword arguments to
# connect() and create_pool() is recommended instead.
# -----------------------------------------------------------------------------
from . import errors
def _check_arg(name: str, value: str) -> None:
"""
Checks the argument to ensure that it does not contain (, ) or = as these
characters are not permitted within connect strings.
"""
if "(" in value or ")" in value or "=" in value:
errors._raise_err(errors.ERR_INVALID_MAKEDSN_ARG, name=name)
def makedsn(
host: str,
port: int,
sid: str = None,
service_name: str = None,
region: str = None,
sharding_key: str = None,
super_sharding_key: str = None,
) -> str:
"""
Returns a string suitable for use as the ``dsn`` parameter for
:meth:`~oracledb.connect()`. This string is identical to the strings that
are defined by the Oracle names server or defined in the ``tnsnames.ora``
file.
"""
connect_data_parts = []
_check_arg("host", host)
if service_name is not None:
_check_arg("service_name", service_name)
connect_data_parts.append(f"(SERVICE_NAME={service_name})")
elif sid is not None:
_check_arg("sid", sid)
connect_data_parts.append(f"(SID={sid})")
if region is not None:
_check_arg("region", region)
connect_data_parts.append(f"(REGION={region})")
if sharding_key is not None:
_check_arg("sharding_key", sharding_key)
connect_data_parts.append(f"(SHARDING_KEY={sharding_key})")
if super_sharding_key is not None:
_check_arg("super_sharding_key", super_sharding_key)
connect_data_parts.append(f"(SUPER_SHARDING_KEY={super_sharding_key})")
connect_data = "".join(connect_data_parts)
return (
f"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={host})"
f"(PORT={port}))(CONNECT_DATA={connect_data}))"
)

View File

@@ -0,0 +1,76 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# enums.py
#
# Contains the enumerations of various constants used throughout the package.
# -----------------------------------------------------------------------------
import enum
from . import base_impl
class AuthMode(enum.IntFlag):
DEFAULT = base_impl.AUTH_MODE_DEFAULT
PRELIM = base_impl.AUTH_MODE_PRELIM
SYSASM = base_impl.AUTH_MODE_SYSASM
SYSBKP = base_impl.AUTH_MODE_SYSBKP
SYSDBA = base_impl.AUTH_MODE_SYSDBA
SYSDGD = base_impl.AUTH_MODE_SYSDGD
SYSKMT = base_impl.AUTH_MODE_SYSKMT
SYSOPER = base_impl.AUTH_MODE_SYSOPER
SYSRAC = base_impl.AUTH_MODE_SYSRAC
class PipelineOpType(enum.IntFlag):
CALL_FUNC = base_impl.PIPELINE_OP_TYPE_CALL_FUNC
CALL_PROC = base_impl.PIPELINE_OP_TYPE_CALL_PROC
COMMIT = base_impl.PIPELINE_OP_TYPE_COMMIT
EXECUTE = base_impl.PIPELINE_OP_TYPE_EXECUTE
EXECUTE_MANY = base_impl.PIPELINE_OP_TYPE_EXECUTE_MANY
FETCH_ALL = base_impl.PIPELINE_OP_TYPE_FETCH_ALL
FETCH_MANY = base_impl.PIPELINE_OP_TYPE_FETCH_MANY
FETCH_ONE = base_impl.PIPELINE_OP_TYPE_FETCH_ONE
class PoolGetMode(enum.IntEnum):
FORCEGET = base_impl.POOL_GETMODE_FORCEGET
NOWAIT = base_impl.POOL_GETMODE_NOWAIT
TIMEDWAIT = base_impl.POOL_GETMODE_TIMEDWAIT
WAIT = base_impl.POOL_GETMODE_WAIT
class Purity(enum.IntEnum):
DEFAULT = base_impl.PURITY_DEFAULT
NEW = base_impl.PURITY_NEW
SELF = base_impl.PURITY_SELF
class VectorFormat(enum.IntEnum):
BINARY = base_impl.VECTOR_FORMAT_BINARY
FLOAT32 = base_impl.VECTOR_FORMAT_FLOAT32
FLOAT64 = base_impl.VECTOR_FORMAT_FLOAT64
INT8 = base_impl.VECTOR_FORMAT_INT8

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,125 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# exceptions.py
#
# Contains the exception classes mandated by the Python Database API.
# -----------------------------------------------------------------------------
class Warning(Exception):
"""
Exception raised for warnings.
Exception messages of this class will have the prefix DPY and an error
number in the range 9000 - 9999.
"""
class Error(Exception):
"""
Exception that is the base class of all other exceptions defined by
python-oracledb.
"""
class DatabaseError(Error):
"""
Exception raised for errors that are related to the database. It is a
subclass of Error.
Exception messages of this class will have the prefix DPY and an error
number in the range 4000 - 4999.
"""
class DataError(DatabaseError):
"""
Exception raised for errors that are due to problems with the processed
data. It is a subclass of DatabaseError.
Exception messages of this class are generated by the database and will
have a prefix such as ORA.
"""
class IntegrityError(DatabaseError):
"""
Exception raised when the relational integrity of the database is affected.
It is a subclass of DatabaseError.
Exception messages of this class are generated by the database and will
have a prefix such as ORA.
"""
class InterfaceError(Error):
"""
Exception raised for errors that are related to the database interface
rather than the database itself. It is a subclass of Error.
Exception messages of this class will have the prefix DPY and an error
number in the range 1000 - 1999.
"""
class InternalError(DatabaseError):
"""
Exception raised when the database encounters an internal error. It is a
subclass of DatabaseError.
Exception messages of this class will have the prefix DPY and an error
number in the range 5000 - 5999.
"""
class NotSupportedError(DatabaseError):
"""
Exception raised when a method or database API was used which is not
supported by the database. It is a subclass of DatabaseError.
Exception messages of this class will have the prefix DPY and an error
number in the range 3000 - 3999.
"""
class OperationalError(DatabaseError):
"""
Exception raised for errors that are related to the operation of the
database but are not necessarily under the control of the programmer. It is
a subclass of DatabaseError.
Exception messages of this class will have the prefix DPY and an error
number in the range 6000 - 6999.
"""
class ProgrammingError(DatabaseError):
"""
Exception raised for programming errors. It is a subclass of DatabaseError.
Exception messages of this class will have the prefix DPY and an error
number in the range 2000 - 2999.
"""

View File

@@ -0,0 +1,310 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2023, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# fetch_info.py
#
# Contains the FetchInfo class which stores metadata about columns that are
# being fetched.
# -----------------------------------------------------------------------------
from typing import Union
import oracledb
from . import constants
from .base import BaseMetaClass
from .base_impl import (
DbType,
DB_TYPE_DATE,
DB_TYPE_TIMESTAMP,
DB_TYPE_TIMESTAMP_LTZ,
DB_TYPE_TIMESTAMP_TZ,
DB_TYPE_BINARY_FLOAT,
DB_TYPE_BINARY_DOUBLE,
DB_TYPE_BINARY_INTEGER,
DB_TYPE_NUMBER,
DB_TYPE_VECTOR,
)
from .dbobject import DbObjectType
class FetchInfo(metaclass=BaseMetaClass):
"""
Identifies metadata of columns that are being fetched.
"""
def __eq__(self, other):
return tuple(self) == other
def __getitem__(self, index):
"""
Return the parts mandated by the Python Database API.
"""
if index == 0 or index == -7:
return self.name
elif index == 1 or index == -6:
return self.type_code
elif index == 2 or index == -5:
return self.display_size
elif index == 3 or index == -4:
return self.internal_size
elif index == 4 or index == -3:
return self.precision
elif index == 5 or index == -2:
return self.scale
elif index == 6 or index == -1:
return self.null_ok
elif isinstance(index, slice):
return tuple(self).__getitem__(index)
raise IndexError("list index out of range")
def __len__(self):
"""
Length mandated by the Python Database API.
"""
return 7
def __repr__(self):
return repr(tuple(self))
def __str__(self):
return str(tuple(self))
@classmethod
def _from_impl(cls, impl):
info = cls.__new__(cls)
info._impl = impl
info._type = None
return info
@property
def annotations(self) -> Union[dict, None]:
"""
This read-only attribute returns a dictionary containing the
`annotations <https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=
GUID-1AC16117-BBB6-4435-8794-2B99F8F68052>`__ associated with the
fetched column. If there are no annotations, the value *None* is
returned. Annotations require Oracle Database version 23, or later. If
using python-oracledb Thick mode, Oracle Client version 23 or later is
also required.
"""
return self._impl.annotations
@property
def display_size(self) -> Union[int, None]:
"""
This read-only attribute returns the display size of the column.
"""
if self._impl.max_size > 0:
return self._impl.max_size
dbtype = self._impl.dbtype
if (
dbtype is DB_TYPE_DATE
or dbtype is DB_TYPE_TIMESTAMP
or dbtype is DB_TYPE_TIMESTAMP_LTZ
or dbtype is DB_TYPE_TIMESTAMP_TZ
):
return 23
elif (
dbtype is DB_TYPE_BINARY_FLOAT
or dbtype is DB_TYPE_BINARY_DOUBLE
or dbtype is DB_TYPE_BINARY_INTEGER
or dbtype is DB_TYPE_NUMBER
):
if self._impl.precision:
display_size = self._impl.precision + 1
if self._impl.scale > 0:
display_size += self._impl.scale + 1
else:
display_size = 127
return display_size
@property
def domain_name(self) -> Union[str, None]:
"""
This read-only attribute returns the name of the `data use case
domain
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-17D3A9C6
-D993-4E94-BF6B-CACA56581F41>`__ associated with the fetched column. If
there is no data use case domain, the value *None* is returned. `Data
use case domains <https://www.oracle.com/pls/topic/lookup?ctx=dblatest
&id=GUID-4743FDE1-7C6E-471B-BC9D-442383CCA2F9>`__ require Oracle
Database version 23, or later. If using python-oracledb Thick mode,
Oracle Client version 23 or later is also required.
"""
return self._impl.domain_name
@property
def domain_schema(self) -> Union[str, None]:
"""
This read-only attribute returns the schema of the `data use case
domain <https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-
17D3A9C6-D993-4E94-BF6B-CACA56581F41>`__ associated with the fetched
column. If there is no data use case domain, the value *None* is
returned. `Data use case domains <https://www.oracle.com/pls/topic/
lookup?ctx=dblatest&id=GUID-4743FDE1-7C6E-471B-BC9D-442383CCA2F9>`__
require Oracle Database version 23, or later. If using python-oracledb
Thick mode, Oracle Client version 23 or later is also required.
"""
return self._impl.domain_schema
@property
def internal_size(self) -> Union[int, None]:
"""
This read-only attribute returns the internal size of the column as
mandated by the Python Database API.
"""
if self._impl.max_size > 0:
return self._impl.buffer_size
@property
def is_json(self) -> bool:
"""
This read-only attribute returns whether the column is known to contain
JSON data. This will be *True* when the type code is
:data:`oracledb.DB_TYPE_JSON` as well as when an "IS JSON" constraint
is enabled on LOB and VARCHAR2 columns.
"""
return self._impl.is_json
@property
def is_oson(self) -> bool:
"""
This read-only attribute returns whether the column is known to contain
binary encoded `OSON <https://www.oracle.com/pls/topic/lookup?ctx=
dblatest&id=GUID-911D302C-CFAF-406B-B6A5-4E99DD38ABAD>`__ data. This
will be *True* when an "IS JSON FORMAT OSON" check constraint is
enabled on BLOB columns.
"""
return self._impl.is_oson
@property
def name(self) -> str:
"""
This read-only attribute returns the name of the column as mandated by
the Python Database API.
"""
return self._impl.name
@property
def null_ok(self) -> bool:
"""
This read-only attribute returns whether nulls are allowed in the
column as mandated by the Python Database API.
"""
return self._impl.nulls_allowed
@property
def precision(self) -> Union[int, None]:
"""
This read-only attribute returns the precision of the column as
mandated by the Python Database API.
"""
if self._impl.precision or self._impl.scale:
return self._impl.precision
@property
def scale(self) -> Union[int, None]:
"""
This read-only attribute returns the scale of the column as mandated by
the Python Database API.
"""
if self._impl.precision or self._impl.scale:
return self._impl.scale
@property
def type(self) -> Union[DbType, DbObjectType]:
"""
This read-only attribute returns the type of the column. This will be
an :ref:`Oracle Object Type <dbobjecttype>` if the column contains
Oracle objects; otherwise, it will be one of the
:ref:`database type constants <dbtypes>` defined at the module level.
"""
if self._type is None:
if self._impl.objtype is not None:
self._type = DbObjectType._from_impl(self._impl.objtype)
else:
self._type = self._impl.dbtype
return self._type
@property
def type_code(self) -> DbType:
"""
This read-only attribute returns the type of the column as mandated by
the Python Database API. The type will be one of the
:ref:`database type constants <dbtypes>` defined at the module level.
"""
return self._impl.dbtype
@property
def vector_dimensions(self) -> Union[int, None]:
"""
This read-only attribute returns the number of dimensions required by
VECTOR columns. If the column is not a VECTOR column or allows for any
number of dimensions, the value returned is *None*.
"""
if self._impl.dbtype is DB_TYPE_VECTOR:
flags = self._impl.vector_flags
if not (flags & constants.VECTOR_META_FLAG_FLEXIBLE_DIM):
return self._impl.vector_dimensions
@property
def vector_format(self) -> Union[oracledb.VectorFormat, None]:
"""
This read-only attribute returns the storage type used by VECTOR
columns. The value of this attribute can be:
- :data:`oracledb.VECTOR_FORMAT_BINARY` which represents 8-bit unsigned
integers
- :data:`oracledb.VECTOR_FORMAT_INT8` which represents 8-bit signed
integers
- :data:`oracledb.VECTOR_FORMAT_FLOAT32` which represents 32-bit
floating-point numbers
- :data:`oracledb.VECTOR_FORMAT_FLOAT64` which represents 64-bit
floating-point numbers
If the column is not a VECTOR column or allows for any type of storage,
the value returned is *None*.
"""
if (
self._impl.dbtype is DB_TYPE_VECTOR
and self._impl.vector_format != 0
):
return oracledb.VectorFormat(self._impl.vector_format)
@property
def vector_is_sparse(self) -> Union[bool, None]:
"""
This read-only attribute returns a boolean indicating if the vector is
sparse or not.
If the column contains vectors that are SPARSE, the value returned is
*True*. If the column contains vectors that are DENSE, the value
returned is *False*. If the column is not a VECTOR column, the value
returned is *None*.
"""
if self._impl.dbtype is DB_TYPE_VECTOR:
flags = self._impl.vector_flags
return bool(flags & constants.VECTOR_META_FLAG_SPARSE_VECTOR)

View File

@@ -0,0 +1,46 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# future.py
#
# Module for handling backwards incompatible changes.
# -----------------------------------------------------------------------------
FEATURES = []
# future object used for managing backwards incompatible changes
class Future:
def __getattr__(self, name):
if name in FEATURES:
return super().__getattr__(name)
return None
def __setattr__(self, name, value):
if name in FEATURES:
return super().__setattr__(name, value)
__future__ = Future()

View File

@@ -0,0 +1,318 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# lob.py
#
# Contains the LOB class for managing BLOB, CLOB, NCLOB and BFILE data.
# -----------------------------------------------------------------------------
from typing import Optional, Union
from .base import BaseMetaClass
from .base_impl import DbType, DB_TYPE_BFILE, DB_TYPE_BLOB
from . import errors
class BaseLOB(metaclass=BaseMetaClass):
def __del__(self):
self._impl.free_lob()
def _check_is_bfile(self):
if self._impl.dbtype is not DB_TYPE_BFILE:
errors._raise_err(errors.ERR_OPERATION_ONLY_SUPPORTED_ON_BFILE)
def _check_not_bfile(self):
if self._impl.dbtype is DB_TYPE_BFILE:
errors._raise_err(errors.ERR_OPERATION_NOT_SUPPORTED_ON_BFILE)
def _check_value_to_write(self, value):
"""
Checks the value to write and returns the actual value to write.
Character LOBs must write strings but can accept UTF-8 encoded bytes
(which will be decoded to strings). Binary LOBs must write bytes but
can accept strings (which will be encoded in UTF-8).
"""
if self.type is DB_TYPE_BLOB:
if isinstance(value, str):
return value.encode()
elif isinstance(value, bytes):
return value
else:
if isinstance(value, str):
return value
elif isinstance(value, bytes):
return value.decode()
raise TypeError("expecting string or bytes")
@classmethod
def _from_impl(cls, impl):
if isinstance(impl, BaseLOB):
return impl
lob = cls.__new__(cls)
lob._impl = impl
return lob
def getfilename(self) -> tuple:
"""
Returns a two-tuple consisting of the directory alias and file name for
a BFILE type LOB.
"""
self._check_is_bfile()
return self._impl.get_file_name()
def setfilename(self, dir_alias: str, name: str) -> None:
"""
Sets the directory alias and name of a BFILE type LOB.
"""
self._check_is_bfile()
self._impl.set_file_name(dir_alias, name)
@property
def type(self) -> DbType:
"""
This read-only attribute returns the type of the LOB as one of the
database type constants.
"""
return self._impl.dbtype
class LOB(BaseLOB):
def __reduce__(self):
value = self.read()
return (type(value), (value,))
def __str__(self):
return self.read()
def close(self) -> None:
"""
Closes the LOB. Call this when writing is completed so that the indexes
associated with the LOB can be updated - but only if open() was called
first.
"""
self._impl.close()
def fileexists(self) -> bool:
"""
Returns a boolean indicating if the file referenced by a BFILE type LOB
exists.
"""
self._check_is_bfile()
return self._impl.file_exists()
def getchunksize(self) -> int:
"""
Returns the chunk size for the LOB. Reading and writing to the LOB in
chunks of multiples of this size will improve performance.
"""
self._check_not_bfile()
return self._impl.get_chunk_size()
def isopen(self) -> bool:
"""
Returns a boolean indicating if the LOB has been opened using the
method open().
"""
return self._impl.get_is_open()
def open(self) -> None:
"""
Opens the LOB for writing. This will improve performance when writing
to the LOB in chunks and there are functional or extensible indexes
associated with the LOB. If this method is not called, each write will
perform an open internally followed by a close after the write has been
completed.
"""
self._impl.open()
def read(
self, offset: int = 1, amount: Optional[int] = None
) -> Union[str, bytes]:
"""
Returns a portion (or all) of the data in the LOB. Note that the amount
and offset are in bytes for BLOB and BFILE type LOBs and in UCS-2 code
points for CLOB and NCLOB type LOBs. UCS-2 code points are equivalent
to characters for all but supplemental characters. If supplemental
characters are in the LOB, the offset and amount will have to be chosen
carefully to avoid splitting a character.
"""
if amount is None:
amount = self._impl.get_max_amount()
if amount >= offset:
amount = amount - offset + 1
else:
amount = 1
elif amount <= 0:
errors._raise_err(errors.ERR_INVALID_LOB_AMOUNT)
if offset <= 0:
errors._raise_err(errors.ERR_INVALID_LOB_OFFSET)
return self._impl.read(offset, amount)
def size(self) -> int:
"""
Returns the size of the data in the LOB. For BLOB and BFILE type LOBs,
this is the number of bytes. For CLOB and NCLOB type LOBs, this is the
number of UCS-2 code points. UCS-2 code points are equivalent to
characters for all but supplemental characters.
"""
return self._impl.get_size()
def trim(
self, new_size: int = 0, *, newSize: Optional[int] = None
) -> None:
"""
Trims the LOB to the new size (the second parameter is deprecated and
should not be used).
"""
self._check_not_bfile()
if newSize is not None:
if new_size != 0:
errors._raise_err(
errors.ERR_DUPLICATED_PARAMETER,
deprecated_name="newSize",
new_name="new_size",
)
new_size = newSize
self._impl.trim(new_size)
def write(self, data: Union[str, bytes], offset: int = 1) -> None:
"""
Writes the data to the LOB at the given offset. The offset is in bytes
for BLOB type LOBs and in UCS-2 code points for CLOB and NCLOB type
LOBs. UCS-2 code points are equivalent to characters for all but
supplemental characters. If supplemental characters are in the LOB, the
offset will have to be chosen carefully to avoid splitting a character.
Note that if you want to make the LOB value smaller, you must use the
trim() function.
"""
self._check_not_bfile()
self._impl.write(self._check_value_to_write(data), offset)
class AsyncLOB(BaseLOB):
async def close(self) -> None:
"""
Closes the LOB. Call this when writing is completed so that the indexes
associated with the LOB can be updated - but only if open() was called
first.
"""
await self._impl.close()
async def fileexists(self) -> bool:
"""
Returns a boolean indicating if the file referenced by a BFILE type LOB
exists.
"""
self._check_is_bfile()
return await self._impl.file_exists()
async def getchunksize(self) -> int:
"""
Returns the chunk size for the LOB. Reading and writing to the LOB in
chunks of multiples of this size will improve performance.
"""
self._check_not_bfile()
return await self._impl.get_chunk_size()
async def isopen(self) -> bool:
"""
Returns a boolean indicating if the LOB has been opened using the
method open().
"""
return await self._impl.get_is_open()
async def open(self) -> None:
"""
Opens the LOB for writing. This will improve performance when writing
to the LOB in chunks and there are functional or extensible indexes
associated with the LOB. If this method is not called, each write will
perform an open internally followed by a close after the write has been
completed.
"""
await self._impl.open()
async def read(
self, offset: int = 1, amount: Optional[int] = None
) -> Union[str, bytes]:
"""
Returns a portion (or all) of the data in the LOB. Note that the amount
and offset are in bytes for BLOB and BFILE type LOBs and in UCS-2 code
points for CLOB and NCLOB type LOBs. UCS-2 code points are equivalent
to characters for all but supplemental characters. If supplemental
characters are in the LOB, the offset and amount will have to be chosen
carefully to avoid splitting a character.
"""
if amount is None:
amount = self._impl.get_max_amount()
if amount >= offset:
amount = amount - offset + 1
else:
amount = 1
if offset <= 0:
errors._raise_err(errors.ERR_INVALID_LOB_OFFSET)
return await self._impl.read(offset, amount)
async def size(self) -> int:
"""
Returns the size of the data in the LOB. For BLOB and BFILE type LOBs
this is the number of bytes. For CLOB and NCLOB type LOBs this is the
number of UCS-2 code points. UCS-2 code points are equivalent to
characters for all but supplemental characters.
"""
return await self._impl.get_size()
async def trim(
self, new_size: int = 0, *, newSize: Optional[int] = None
) -> None:
"""
Trims the LOB to the new size (the second parameter is deprecated and
should not be used).
"""
self._check_not_bfile()
if newSize is not None:
if new_size != 0:
errors._raise_err(
errors.ERR_DUPLICATED_PARAMETER,
deprecated_name="newSize",
new_name="new_size",
)
new_size = newSize
await self._impl.trim(new_size)
async def write(self, data: Union[str, bytes], offset: int = 1) -> None:
"""
Writes the data to the LOB at the given offset. The offset is in bytes
for BLOB type LOBs and in UCS-2 code points for CLOB and NCLOB type
LOBs. UCS-2 code points are equivalent to characters for all but
supplemental characters. If supplemental characters are in the LOB, the
offset will have to be chosen carefully to avoid splitting a character.
Note that if you want to make the LOB value smaller, you must use the
trim() function.
"""
self._check_not_bfile()
await self._impl.write(self._check_value_to_write(data), offset)

View File

@@ -0,0 +1,491 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# pipeline.py
#
# Contains the Pipeline class used for executing multiple operations.
# -----------------------------------------------------------------------------
from typing import Any, Callable, Optional, Union
from . import utils
from .base import BaseMetaClass
from .base_impl import PipelineImpl, PipelineOpImpl, PipelineOpResultImpl
from .defaults import defaults
from .enums import PipelineOpType
from .errors import _Error
from .fetch_info import FetchInfo
class PipelineOp(metaclass=BaseMetaClass):
def __repr__(self):
cls_name = self.__class__._public_name
return f"<{cls_name} of type {self.op_type.name}>"
def _create_result(self):
"""
Internal method used for creating a result object that is returned when
running a pipeline.
"""
impl = PipelineOpResultImpl(self._impl)
result = PipelineOpResult.__new__(PipelineOpResult)
result._operation = self
result._impl = impl
return result
@property
def arraysize(self) -> int:
"""
This read-only attribute returns the array size that will be used when
fetching query rows with :meth:`Pipeline.add_fetchall()`. For all other
operations, the value returned is *0*.
"""
return self._impl.arraysize
@property
def fetch_decimals(self) -> bool:
"""
Returns whether or not to fetch columns of type ``NUMBER`` as
``decimal.Decimal`` values for a query.
"""
return self._impl.fetch_decimals
@property
def fetch_lobs(self) -> bool:
"""
Returns whether or not to fetch LOB locators for a query.
"""
return self._impl.fetch_lobs
@property
def keyword_parameters(self) -> Any:
"""
This read-only attribute returns the keyword parameters to the stored
procedure or function being called by the operation, if applicable.
"""
return self._impl.keyword_parameters
@property
def name(self) -> Union[str, None]:
"""
This read-only attribute returns the name of the stored procedure or
function being called by the operation, if applicable.
"""
return self._impl.name
@property
def num_rows(self) -> int:
"""
This read-only attribute returns the number of rows to fetch when
performing a query of a specific number of rows. For all other
operations, the value returned is *0*.
"""
return self._impl.num_rows
@property
def op_type(self) -> PipelineOpType:
"""
This read-only attribute returns the type of operation that is taking
place.
"""
return PipelineOpType(self._impl.op_type)
@property
def parameters(self) -> Any:
"""
This read-only attribute returns the parameters to the stored procedure
or function or the parameters bound to the statement being executed by
the operation, if applicable.
"""
return self._impl.parameters
@property
def return_type(self) -> Any:
"""
This read-only attribute returns the return type of the stored function
being called by the operation, if applicable.
"""
return self._impl.return_type
@property
def rowfactory(self) -> Union[Callable, None]:
"""
This read-only attribute returns the row factory callable function to
be used in a query executed by the operation, if applicable.
"""
return self._impl.rowfactory
@property
def statement(self) -> Union[str, None]:
"""
This read-only attribute returns the statement being executed by the
operation, if applicable.
"""
return self._impl.statement
class PipelineOpResult(metaclass=BaseMetaClass):
def __repr__(self):
cls_name = self.__class__._public_name
return (
f"<{cls_name} for operation of type {self.operation.op_type.name}>"
)
@property
def columns(self) -> Union[list[FetchInfo], None]:
"""
This read-only attribute is a list of FetchInfo objects. This
attribute will be *None* for operations that do not return rows.
"""
if self._impl.fetch_metadata is not None:
return [FetchInfo._from_impl(i) for i in self._impl.fetch_metadata]
@property
def error(self) -> Union[_Error, None]:
"""
This read-only attribute returns the error that occurred when running
this operation. If no error occurred, then the value *None* is
returned.
"""
return self._impl.error
@property
def operation(self) -> PipelineOp:
"""
This read-only attribute returns the PipelineOp operation object that
generated the result.
"""
return self._operation
@property
def return_value(self) -> Any:
"""
This read-only attribute returns the return value of the called PL/SQL
function, if a function was called for the operation.
"""
return self._impl.return_value
@property
def rows(self) -> Union[list, None]:
"""
This read-only attribute returns the rows that were fetched by the
operation, if a query was executed.
"""
return self._impl.rows
@property
def warning(self) -> Union[_Error, None]:
"""
This read-only attribute returns any warning that was encountered when
running this operation. If no warning was encountered, then the value
*None* is returned.
"""
return self._impl.warning
class Pipeline(metaclass=BaseMetaClass):
def __repr__(self):
cls_name = self.__class__._public_name
return f"<{cls_name} with {len(self._impl.operations)} operations>"
def _add_op(self, op_impl):
"""
Internal method for adding an PipelineOpImpl instance to the list of
operations, creating an associated PipelineOp instance to correspond to
it.
"""
self._impl.operations.append(op_impl)
op = PipelineOp.__new__(PipelineOp)
op._impl = op_impl
self._operations.append(op)
return op
def add_callfunc(
self,
name: str,
return_type: Any,
parameters: Optional[Union[list, tuple]] = None,
keyword_parameters: Optional[dict] = None,
) -> PipelineOp:
"""
Adds an operation to the pipeline that calls a stored PL/SQL function
with the given parameters and return type. The created PipelineOp
object is also returned from this function.
When the Pipeline is executed, the PipelineOpResult object that is
returned for this operation will have the
:attr:`~PipelineOpResult.return_value` attribute populated with the
return value of the PL/SQL function if the call completes
successfully.
"""
utils.verify_stored_proc_args(parameters, keyword_parameters)
op_impl = PipelineOpImpl(
op_type=PipelineOpType.CALL_FUNC,
name=name,
return_type=return_type,
parameters=parameters,
keyword_parameters=keyword_parameters,
)
return self._add_op(op_impl)
def add_callproc(
self,
name: str,
parameters: Optional[Union[list, tuple]] = None,
keyword_parameters: Optional[dict] = None,
) -> PipelineOp:
"""
Adds an operation that calls a stored procedure with the given
parameters. The created PipelineOp object is also returned from
this function.
"""
utils.verify_stored_proc_args(parameters, keyword_parameters)
op_impl = PipelineOpImpl(
op_type=PipelineOpType.CALL_PROC,
name=name,
parameters=parameters,
keyword_parameters=keyword_parameters,
)
return self._add_op(op_impl)
def add_commit(self) -> PipelineOp:
"""
Adds an operation that performs a commit.
"""
op_impl = PipelineOpImpl(op_type=PipelineOpType.COMMIT)
return self._add_op(op_impl)
def add_execute(
self,
statement: str,
parameters: Optional[Union[list, tuple, dict]] = None,
) -> PipelineOp:
"""
Adds an operation that executes a statement with the given parameters.
The created PipelineOp object is also returned from this function.
Do not use this for queries that return rows. Instead use
:meth:`Pipeline.add_fetchall()`, :meth:`Pipeline.add_fetchmany()`, or
:meth:`Pipeline.add_fetchone()`.
"""
op_impl = PipelineOpImpl(
op_type=PipelineOpType.EXECUTE,
statement=statement,
parameters=parameters,
)
return self._add_op(op_impl)
def add_executemany(
self,
statement: str,
parameters: Union[list, int],
) -> PipelineOp:
"""
Adds an operation that executes a SQL statement once using all bind
value mappings or sequences found in the sequence parameters. This can
be used to insert, update, or delete multiple rows in a table. It can
also invoke a PL/SQL procedure multiple times.
The created PipelineOp object is also returned from this function.
The ``parameters`` parameter can be a list of tuples, where each tuple
item maps to one bind variable placeholder in ``statement``. It can
also be a list of dictionaries, where the keys match the bind variable
placeholder names in ``statement``. If there are no bind values, or
values have previously been bound, the ``parameters`` value can be an
integer specifying the number of iterations.
"""
op_impl = PipelineOpImpl(
op_type=PipelineOpType.EXECUTE_MANY,
statement=statement,
parameters=parameters,
)
return self._add_op(op_impl)
def add_fetchall(
self,
statement: str,
parameters: Optional[Union[list, tuple, dict]] = None,
arraysize: Optional[int] = None,
rowfactory: Optional[Callable] = None,
fetch_lobs: Optional[bool] = None,
fetch_decimals: Optional[bool] = None,
) -> PipelineOp:
"""
Adds an operation that executes a query and returns all of the rows
from the result set. The created PipelineOp object is also returned
from this function.
When the Pipeline is executed, the PipelineOpResult object that is
returned for this operation will have the
:attr:`~PipelineOpResult.rows` attribute populated with the list of
rows returned by the query.
The default value for ``arraysize`` is
:attr:`oracledb.defaults.arraysize <Defaults.arraysize>`.
Internally, this operation's :attr:`Cursor.prefetchrows` size is set
to the value of the explicit or default ``arraysize`` parameter value.
The ``fetch_lobs`` parameter specifies whether to return LOB locators
or ``str``/``bytes`` values when fetching LOB columns. The default
value is :data:`oracledb.defaults.fetch_lobs <Defaults.fetch_lobs>`.
The ``fetch_decimals`` parameter specifies whether to return
``decimal.Decimal`` values when fetching columns of type ``NUMBER``.
The default value is
:data:`oracledb.defaults.fetch_decimals <Defaults.fetch_decimals>`.
"""
if arraysize is None:
arraysize = defaults.arraysize
op_impl = PipelineOpImpl(
op_type=PipelineOpType.FETCH_ALL,
statement=statement,
parameters=parameters,
arraysize=arraysize,
rowfactory=rowfactory,
fetch_lobs=fetch_lobs,
fetch_decimals=fetch_decimals,
)
return self._add_op(op_impl)
def add_fetchmany(
self,
statement: str,
parameters: Optional[Union[list, tuple, dict]] = None,
num_rows: Optional[int] = None,
rowfactory: Optional[Callable] = None,
fetch_lobs: Optional[bool] = None,
fetch_decimals: Optional[bool] = None,
) -> PipelineOp:
"""
Adds an operation that executes a query and returns up to the specified
number of rows from the result set. The created PipelineOp object is
also returned from this function.
When the Pipeline is executed, the PipelineOpResult object that is
returned for this operation will have the
:attr:`~PipelineOpResult.rows` attribute populated with the list of
rows returned by the query.
The default value for ``num_rows`` is the value of
:attr:`oracledb.defaults.arraysize <Defaults.arraysize>`.
Internally, this operation's :attr:`Cursor.prefetchrows` size is set to
the value of the explicit or default ``num_rows`` parameter, allowing
all rows to be fetched in one round-trip.
Since only one fetch is performed for a query operation, consider
adding a ``FETCH NEXT`` clause to the statement to prevent the
database processing rows that will never be fetched.
The ``fetch_lobs`` parameter specifies whether to return LOB locators
or ``str``/``bytes`` values when fetching LOB columns. The default
value is :data:`oracledb.defaults.fetch_lobs <Defaults.fetch_lobs>`.
The ``fetch_decimals`` parameter specifies whether to return
``decimal.Decimal`` values when fetching columns of type ``NUMBER``.
The default value is
:data:`oracledb.defaults.fetch_decimals <Defaults.fetch_decimals>`.
"""
if num_rows is None:
num_rows = defaults.arraysize
op_impl = PipelineOpImpl(
op_type=PipelineOpType.FETCH_MANY,
statement=statement,
parameters=parameters,
num_rows=num_rows,
rowfactory=rowfactory,
fetch_lobs=fetch_lobs,
fetch_decimals=fetch_decimals,
)
return self._add_op(op_impl)
def add_fetchone(
self,
statement: str,
parameters: Optional[Union[list, tuple, dict]] = None,
rowfactory: Optional[Callable] = None,
fetch_lobs: Optional[bool] = None,
fetch_decimals: Optional[bool] = None,
) -> PipelineOp:
"""
Adds an operation that executes a query and returns the first row of
the result set if one exists (or *None*, if no rows exist). The
created PipelineOp object is also returned from this function.
When the Pipeline is executed, the PipelineOpResult object that is
returned for this operation will have the
:attr:`~PipelineOpResult.rows` attribute populated with this row if the
query is performed successfully.
Internally, this operation's :attr:`Cursor.prefetchrows` and
:attr:`Cursor.arraysize` sizes will be set to *1*.
Since only one fetch is performed for a query operation, consider
adding a ``WHERE`` condition or using a ``FETCH NEXT`` clause in the
statement to prevent the database processing rows that will never be
fetched.
The ``fetch_lobs`` parameter specifies whether to return LOB locators
or ``str``/``bytes`` values when fetching LOB columns. The default
value is :data:`oracledb.defaults.fetch_lobs <Defaults.fetch_lobs>`.
The ``fetch_decimals`` parameter specifies whether to return
``decimal.Decimal`` values when fetching columns of type ``NUMBER``.
The default value is
:data:`oracledb.defaults.fetch_decimals <Defaults.fetch_decimals>`.
"""
op_impl = PipelineOpImpl(
op_type=PipelineOpType.FETCH_ONE,
statement=statement,
parameters=parameters,
rowfactory=rowfactory,
fetch_lobs=fetch_lobs,
fetch_decimals=fetch_decimals,
)
return self._add_op(op_impl)
@property
def operations(self) -> list[PipelineOp]:
"""
This read-only attribute returns the list of operations associated with
the pipeline.
"""
return self._operations
def create_pipeline() -> Pipeline:
"""
Creates a pipeline object which can be used to process a set of operations
against a database.
"""
pipeline = Pipeline.__new__(Pipeline)
pipeline._impl = PipelineImpl()
pipeline._operations = []
return pipeline

View File

@@ -0,0 +1,274 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# azure_config_provider.py
#
# Python file contains the hook method config_azure_hook() that fetches config
# store from Azure App Configuration.
# -----------------------------------------------------------------------------
import json
import re
import oracledb
from urllib.parse import urlparse, parse_qs
from azure.appconfiguration import AzureAppConfigurationClient
from azure.keyvault.secrets import SecretClient
from azure.core.exceptions import ResourceNotFoundError
from azure.identity import (
ClientSecretCredential,
CertificateCredential,
ManagedIdentityCredential,
ChainedTokenCredential,
EnvironmentCredential,
)
def _get_authentication_method(parameters):
auth_method = parameters.get("authentication", parameters.get("method"))
if auth_method is not None:
auth_method = auth_method.upper()
if auth_method == "AZURE_DEFAULT":
auth_method = None
return auth_method
def _get_credential(parameters):
"""
Returns the appropriate credential given the input supplied by the original
connect string.
"""
tokens = []
auth_method = _get_authentication_method(parameters)
if auth_method is None or auth_method == "AZURE_SERVICE_PRINCIPAL":
if "azure_client_secret" in parameters:
tokens.append(
ClientSecretCredential(
_get_required_parameter(parameters, "azure_tenant_id"),
_get_required_parameter(parameters, "azure_client_id"),
_get_required_parameter(parameters, "azure_client_secret"),
)
)
elif "azure_client_certificate_path" in parameters:
tokens.append(
CertificateCredential(
_get_required_parameter(parameters, "azure_tenant_id"),
_get_required_parameter(parameters, "azure_client_id"),
_get_required_parameter(
parameters, "azure_client_certificate_path"
),
)
)
if auth_method is None or auth_method == "AZURE_MANAGED_IDENTITY":
client_id = parameters.get("azure_managed_identity_client_id")
if client_id is not None:
tokens.append(ManagedIdentityCredential(client_id=client_id))
if len(tokens) == 0:
message = (
"Authentication options were not available in Connection String"
)
raise Exception(message)
elif len(tokens) == 1:
return tokens[0]
tokens.append(EnvironmentCredential())
return ChainedTokenCredential(*tokens)
def _get_password(pwd_string, parameters):
try:
pwd = json.loads(pwd_string)
except json.JSONDecodeError:
message = (
"Password is expected to be JSON"
" containing Azure Vault details."
)
raise Exception(message)
pwd["value"] = pwd.pop("uri")
pwd["type"] = "azurevault"
# make authentication section
pwd["authentication"] = authentication = {}
authentication["method"] = auth_method = _get_authentication_method(
parameters
)
if auth_method is None or auth_method == "AZURE_SERVICE_PRINCIPAL":
if "azure_client_secret" in parameters:
authentication["azure_tenant_id"] = _get_required_parameter(
parameters, "azure_tenant_id"
)
authentication["azure_client_id"] = _get_required_parameter(
parameters, "azure_client_id"
)
authentication["azure_client_secret"] = _get_required_parameter(
parameters, "azure_client_secret"
)
elif "azure_client_certificate_path" in parameters:
authentication["azure_tenant_id"] = (
_get_required_parameter(parameters, "azure_tenant_id"),
)
authentication["azure_client_id"] = (
_get_required_parameter(parameters, "azure_client_id"),
)
authentication["azure_client_certificate_path"] = (
_get_required_parameter(
parameters, "azure_client_certificate_path"
)
)
if auth_method is None or auth_method == "AZURE_MANAGED_IDENTITY":
authentication["azure_managed_identity_client_id"] = parameters.get(
"azure_managed_identity_client_id"
)
return pwd
def _get_required_parameter(parameters, name, location="connection string"):
try:
return parameters[name]
except KeyError:
message = f'Parameter named "{name}" is missing from {location}'
raise Exception(message) from None
def _get_setting(client, key, sub_key, label, required=True):
"""
Returns the configuration setting given the client, key and label.
"""
try:
if key.endswith("/"):
actual_key = f"{key}{sub_key}"
else:
actual_key = f"{key}/{sub_key}"
obj = client.get_configuration_setting(key=actual_key, label=label)
except ResourceNotFoundError:
if required:
message = f"Missing required configuration key: {actual_key}"
raise Exception(message)
return None
return obj.value
def _parse_parameters(protocol_arg: str) -> dict:
"""
Parse the parameters from the protocol argument string.
"""
pos = protocol_arg.find("?")
parsed_url = urlparse(protocol_arg[pos + 1 :])
parsed_values = parse_qs(parsed_url.path)
parameters = {
key.lower(): value[0] for key, value in parsed_values.items()
}
config_name = protocol_arg[:pos].rstrip("/")
if not config_name.endswith(".azconfig.io"):
config_name += ".azconfig.io"
parameters["appconfigname"] = config_name
return parameters
def password_type_azure_vault_hook(args):
uri = _get_required_parameter(args, "value", '"password" key section')
credential = args.get("credential")
if credential is None:
# if credential not present, this might be coming
# from oci config provider, so create credential
# for azure key vault.
auth = args.get("authentication")
if auth is None:
raise Exception(
"Azure Vault authentication details were not provided."
)
credential = _get_credential(auth)
pattern = re.compile(
r"(?P<vault_url>https://[A-Za-z0-9._-]+)/"
r"secrets/(?P<secretKey>[A-Za-z][A-Za-z0-9-]*)$"
)
match = pattern.match(uri)
if match is None:
raise Exception("Invalid Azure Vault details")
vault_url = match.group("vault_url")
secret_key = match.group("secretKey")
secret_client = SecretClient(vault_url, credential)
return secret_client.get_secret(secret_key).value
def _process_config(parameters, connect_params):
"""
Processes the configuration stored in the Azure App configuration store.
"""
credential = _get_credential(parameters)
client = AzureAppConfigurationClient(
"https://" + _get_required_parameter(parameters, "appconfigname"),
credential,
)
key = _get_required_parameter(parameters, "key")
label = parameters.get("label")
# get the common parameters
config = {}
config["connect_descriptor"] = _get_setting(
client, key, "connect_descriptor", label
)
config["user"] = _get_setting(client, key, "user", label, required=False)
pwd = _get_setting(client, key, "password", label, required=False)
if pwd is not None:
config["password"] = _get_password(pwd, parameters)
config["config_time_to_live"] = _get_setting(
client, key, "config_time_to_live", label, required=False
)
config["config_time_to_live_grace_period"] = _get_setting(
client, key, "config_time_to_live_grace_period", label, required=False
)
# get the python-oracledb specific parameters
settings = _get_setting(client, key, "pyo", label, required=False)
if settings is not None:
config["pyo"] = json.loads(settings)
# set the configuration
connect_params.set_from_config(config)
def config_azure_hook(protocol, protocol_arg, connect_params):
"""
Hook for handling parameters stored in an Azure configuration store.
"""
parameters = _parse_parameters(protocol_arg)
_process_config(parameters, connect_params)
oracledb.register_password_type("azurevault", password_type_azure_vault_hook)
oracledb.register_protocol("config-azure", config_azure_hook)

View File

@@ -0,0 +1,81 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# azure_tokens.py
#
# Methods that generates an OAuth2 access token using the MSAL SDK
# -----------------------------------------------------------------------------
import msal
import oracledb
def generate_token(token_auth_config, refresh=False):
"""
Generates an Azure access token based on provided credentials.
"""
user_auth_type = token_auth_config.get("auth_type") or ""
auth_type = user_auth_type.lower()
if auth_type == "azureserviceprincipal":
return _service_principal_credentials(token_auth_config)
else:
raise ValueError(
f"Unrecognized auth_type authentication method: {user_auth_type}"
)
def _service_principal_credentials(token_auth_config):
"""
Returns the access token for authentication as a service principal.
"""
msal_config = {
"authority": token_auth_config["authority"],
"client_id": token_auth_config["client_id"],
"client_credential": token_auth_config["client_credential"],
}
# Initialize the Confidential Client Application
cca = msal.ConfidentialClientApplication(**msal_config)
auth_response = cca.acquire_token_for_client(
scopes=[token_auth_config["scopes"]]
)
if "access_token" in auth_response:
return auth_response["access_token"]
def azure_token_hook(params: oracledb.ConnectParams):
"""
Azure-specific hook for generating a token.
"""
if params.extra_auth_params is not None:
def token_callback(refresh):
return generate_token(params.extra_auth_params, refresh)
params.set(access_token=token_callback)
# Register the token hook for Azure
oracledb.register_params_hook(azure_token_hook)

View File

@@ -0,0 +1,253 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# oci_config_provider.py
#
# Python file contains the hook method config_oci_hook() that fetches config
# store from OCI Object Storage.
# -----------------------------------------------------------------------------
import base64
import json
import oci
import oracledb
import re
from urllib.parse import urlparse, parse_qs
oci_from_file = oci.config.from_file
oci_client_error = oci.exceptions.ClientError
oci_object_storage_client = oci.object_storage.ObjectStorageClient
oci_secrets_client = oci.secrets.SecretsClient
"""
Pattern to parse OCI Object Connect String
"""
cloud_net_naming_pattern_oci = re.compile(
r"(?P<objservername>[^/]+)/n/(?P<namespace>[^/]+)/b/(?P<bucketname>[^/]+)/o/(?P<filename>[^/]+)(/c/(?P<alias>[^/]+))?"
)
def _get_config(parameters, connect_params):
config = {}
credential, signer = _get_credential(parameters)
auth_method = parameters.get("authentication")
if auth_method is not None:
auth_method = auth_method.upper()
if auth_method is None or auth_method == "OCI_DEFAULT":
client_oci = oci_object_storage_client(credential)
elif (
auth_method == "OCI_INSTANCE_PRINCIPAL"
or auth_method == "OCI_RESOURCE_PRINCIPAL"
):
client_oci = oci_object_storage_client(
config=credential, signer=signer
)
get_object_request = {
"object_name": _get_required_parameter(parameters, "filename"),
"bucket_name": _get_required_parameter(parameters, "bucketname"),
"namespace_name": _get_required_parameter(parameters, "namespace"),
}
get_object_response = client_oci.get_object(**get_object_request)
resp = _stream_to_string(get_object_response.data)
settings = json.loads(resp)
user_alias = parameters.get("alias")
if user_alias:
settings = settings[user_alias]
# Connect Descriptor
config["connect_descriptor"] = _get_required_parameter(
settings, "connect_descriptor"
)
# user and password
if connect_params.user is None:
config["user"] = settings.get("user")
if "password" in settings:
config["password"] = pwd = settings["password"]
if pwd["type"] == "ocivault":
authentication = pwd.setdefault("authentication", {})
authentication.setdefault("method", auth_method)
authentication["credential"] = credential
# config cache settings
config["config_time_to_live"] = settings.get("config_time_to_live")
config["config_time_to_live_grace_period"] = settings.get(
"config_time_to_live_grace_period"
)
# pyo parameters settings
config["pyo"] = settings.get("pyo", None)
# set the configuration
connect_params.set_from_config(config)
def _get_credential(parameters):
"""
Returns the appropriate credential given the input supplied by the original
connect string.
"""
auth_method = parameters.get("authentication", parameters.get("method"))
if auth_method is not None:
auth_method = auth_method.upper()
# if region is not in connection string, retrieve from object server name.
region = parameters.get(
"oci_region", _retrieve_region(parameters.get("objservername"))
)
try:
if auth_method is None or auth_method == "OCI_DEFAULT":
# Default Authentication
# default path ~/.oci/config
return oci_from_file(), None
except oci.exceptions.ClientError:
# try to create config with connection string parameters.
if "oci_tenancy" in parameters and "oci_user" in parameters:
with open(parameters["oci_key_file"], "r") as file_content:
public_key = file_content.read()
provider = dict(
tenancy=parameters["oci_tenancy"],
user=parameters["oci_user"],
fingerprint=parameters["oci_fingerprint"],
key_file=parameters["oci_key_file"],
private_key_content=public_key,
region=region,
)
return provider, None
if auth_method == "OCI_INSTANCE_PRINCIPAL":
signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
return (
dict(region=region),
signer,
)
elif auth_method == "OCI_RESOURCE_PRINCIPAL":
signer = oci.auth.signers.get_resource_principals_signer()
return {}, signer
else:
msg = "Authentication options were not available in Connection String"
raise Exception(msg)
def _get_required_parameter(parameters, name, location="connection string"):
try:
return parameters[name]
except KeyError:
message = f'Parameter named "{name}" is missing from {location}'
raise Exception(message) from None
def _parse_parameters(protocol_arg: str) -> dict:
"""
Parse the parameters from the protocol argument string.
"""
pos = protocol_arg.find("?")
parsed_url = urlparse(protocol_arg[pos + 1 :])
parsed_values = parse_qs(parsed_url.path)
parameters = {
key.lower(): value[0] for key, value in parsed_values.items()
}
match = cloud_net_naming_pattern_oci.match(protocol_arg[:pos])
if match:
parameters["objservername"] = match.group("objservername")
parameters["namespace"] = match.group("namespace")
parameters["bucketname"] = match.group("bucketname")
parameters["filename"] = match.group("filename")
if match.group("alias"):
parameters["alias"] = match.group("alias")
return parameters
def password_type_oci_vault_hook(args):
secret_id = _get_required_parameter(
args, "value", '"password" key section'
)
authentication = args.get("authentication")
if authentication is None:
raise Exception(
"OCI Key Vault authentication details were not provided."
)
# if credentials are not present, create credentials with given
# authentication details.
credential = authentication.get("credential")
if credential is None:
credential, signer = _get_credential(authentication)
auth_method = authentication.get("method")
if auth_method is not None:
auth_method = auth_method.upper()
if auth_method is None or auth_method == "OCI_DEFAULT":
secret_client_oci = oci_secrets_client(credential)
elif auth_method == "OCI_INSTANCE_PRINCIPAL":
signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
secret_client_oci = oci_secrets_client(
config=credential, signer=signer
)
elif auth_method == "OCI_RESOURCE_PRINCIPAL":
signer = oci.auth.signers.get_resource_principals_signer()
secret_client_oci = oci_secrets_client(
config=credential, signer=signer
)
get_secret_bundle_response = secret_client_oci.get_secret_bundle(
secret_id=secret_id
)
# decoding the vault content
b64content = get_secret_bundle_response.data.secret_bundle_content.content
return base64.b64decode(b64content).decode()
def _retrieve_region(objservername):
if objservername is not None:
arr = objservername.split(".")
return arr[1].lower().replace("_", "-")
def _stream_to_string(stream):
return b"".join(stream).decode()
def config_oci_hook(
protocol: str, protocol_arg: str, connect_params: oracledb.ConnectParams
):
"""
Hook for handling parameters stored in an OCI Object store.
"""
parameters = _parse_parameters(protocol_arg)
_get_config(parameters, connect_params)
oracledb.register_password_type("ocivault", password_type_oci_vault_hook)
oracledb.register_protocol("config-ociobject", config_oci_hook)

View File

@@ -0,0 +1,169 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# oci_tokens.py
#
# Methods that generates an OCI access token using the OCI SDK
# -----------------------------------------------------------------------------
import oci
import oracledb
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
def generate_token(token_auth_config, refresh=False):
"""
Generates an OCI access token based on provided credentials.
"""
user_auth_type = token_auth_config.get("auth_type") or ""
auth_type = user_auth_type.lower()
if auth_type == "configfileauthentication":
return _config_file_based_authentication(token_auth_config)
elif auth_type == "simpleauthentication":
return _simple_authentication(token_auth_config)
elif auth_type == "instanceprincipal":
return _instance_principal_authentication(token_auth_config)
else:
raise ValueError(
f"Unrecognized auth_type authentication method {user_auth_type}"
)
def _get_key_pair():
"""
Generates a public-private key pair for proof of possession.
"""
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096,
)
private_key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
).decode("utf-8")
public_key_pem = (
private_key.public_key()
.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
.decode("utf-8")
)
if not oracledb.is_thin_mode():
p_key = "".join(
line.strip()
for line in private_key_pem.splitlines()
if not (
line.startswith("-----BEGIN") or line.startswith("-----END")
)
)
private_key_pem = p_key
return {"private_key": private_key_pem, "public_key": public_key_pem}
def _generate_access_token(client, token_auth_config):
"""
Token generation logic used by authentication methods.
"""
key_pair = _get_key_pair()
scope = token_auth_config.get("scope", "urn:oracle:db::id::*")
details = oci.identity_data_plane.models.GenerateScopedAccessTokenDetails(
scope=scope, public_key=key_pair["public_key"]
)
response = client.generate_scoped_access_token(
generate_scoped_access_token_details=details
)
return (response.data.token, key_pair["private_key"])
def _config_file_based_authentication(token_auth_config):
"""
Config file base authentication implementation: config parameters
are provided in a file.
"""
file_location = token_auth_config.get(
"file_location", oci.config.DEFAULT_LOCATION
)
profile = token_auth_config.get("profile", oci.config.DEFAULT_PROFILE)
# Load OCI config
config = oci.config.from_file(file_location, profile)
oci.config.validate_config(config)
# Initialize service client with default config file
client = oci.identity_data_plane.DataplaneClient(config)
return _generate_access_token(client, token_auth_config)
def _simple_authentication(token_auth_config):
"""
Simple authentication: config parameters are passed as parameters
"""
config = {
"user": token_auth_config["user"],
"key_file": token_auth_config["key_file"],
"fingerprint": token_auth_config["fingerprint"],
"tenancy": token_auth_config["tenancy"],
"region": token_auth_config["region"],
"profile": token_auth_config["profile"],
}
oci.config.validate_config(config)
client = oci.identity_data_plane.DataplaneClient(config)
return _generate_access_token(client, token_auth_config)
def _instance_principal_authentication(token_auth_config):
"""
Instance principal authentication: for compute instances
with dynamic group access.
"""
signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
client = oci.identity_data_plane.DataplaneClient(config={}, signer=signer)
return _generate_access_token(client, token_auth_config)
def oci_token_hook(params: oracledb.ConnectParams):
"""
OCI-specific hook for generating a token.
"""
if params.extra_auth_params is not None:
def token_callback(refresh):
return generate_token(params.extra_auth_params, refresh)
params.set(access_token=token_callback)
# Register the token hook for OCI
oracledb.register_params_hook(oci_token_hook)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,746 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# soda.py
#
# Contains the classes for managing Simple Oracle Document Access (SODA):
# SodaDatabase, SodaCollection, SodaDocument, SodaDocCursor and SodaOperation.
# -----------------------------------------------------------------------------
from typing import Any, Optional, Union
from typing_extensions import Self
import json
from .base import BaseMetaClass
from . import errors
class SodaDatabase(metaclass=BaseMetaClass):
def __repr__(self):
cls_name = self.__class__._public_name
return f"<{cls_name} on {self._conn!r}>"
@classmethod
def _from_impl(cls, conn, impl):
db = cls.__new__(cls)
db._conn = conn
db._impl = impl
return db
def _create_doc_impl(
self, content: Any, key: str = None, media_type: str = None
) -> "SodaDocument":
"""
Internal method used for creating a document implementation object with
the given content, key and media type.
"""
if isinstance(content, str):
content_bytes = content.encode()
elif isinstance(content, bytes):
content_bytes = content
elif self._impl.supports_json:
return self._impl.create_json_document(content, key)
else:
content_bytes = json.dumps(content).encode()
return self._impl.create_document(content_bytes, key, media_type)
def createCollection(
self,
name: str,
metadata: Optional[Union[str, dict]] = None,
mapMode: bool = False,
) -> "SodaCollection":
"""
Creates a SODA collection with the given name and returns a new SODA
collection object. If you try to create a collection, and a collection
with the same name and metadata already exists, then that existing
collection is opened without error.
If ``metadata`` is specified, it is expected to be a string containing
valid JSON or a dictionary that will be transformed into a JSON string.
This JSON permits you to specify the configuration of the collection
including storage options; specifying the presence or absence of
columns for creation timestamp, last modified timestamp and version;
whether the collection can store only JSON documents; and methods of
key and version generation. The default metadata creates a collection
that only supports JSON documents and uses system generated keys.
If the ``mapMode`` parameter is set to *True*, the new collection is
mapped to an existing table instead of creating a table. If a
collection is created in this way, dropping the collection will not
drop the existing table either.
"""
if metadata is not None and not isinstance(metadata, str):
metadata = json.dumps(metadata)
collection_impl = self._impl.create_collection(name, metadata, mapMode)
return SodaCollection._from_impl(self, collection_impl)
def createDocument(
self,
content: Any,
key: Optional[str] = None,
mediaType: str = "application/json",
) -> "SodaDocument":
"""
Creates a SODA document usable for SODA write operations. You only need
to use this method if your collection requires client-assigned keys or
has non-JSON content; otherwise, you can pass your content directly to
SODA write operations. SodaDocument attributes
:attr:`~SodaDoc.createdOn`, :attr:`~SodaDoc.lastModified`, and
:attr:`~SodaDoc.version` will be *None*.
The ``content`` parameter can be a dictionary or list which will be
transformed into a JSON string and then UTF-8 encoded. It can also be a
string which will be UTF-8 encoded or it can be a bytes object which
will be stored unchanged. If a bytes object is provided and the content
is expected to be JSON, note that SODA only supports UTF-8, UTF-16LE,
and UTF-16BE encodings.
The ``key`` parameter should only be supplied if the collection in
which the document is to be placed requires client-assigned keys.
The ``mediaType`` parameter should only be supplied if the collection
in which the document is to be placed supports non-JSON documents and
the content for this document is non-JSON. Using a standard MIME type
for this value is recommended but any string will be accepted.
"""
doc_impl = self._create_doc_impl(content, key, mediaType)
return SodaDocument._from_impl(doc_impl)
def getCollectionNames(
self, startName: Optional[str] = None, limit: int = 0
) -> list[str]:
"""
Returns a list of the names of collections in the database that match
the criteria, in alphabetical order.
If the ``startName`` parameter is specified, the list of names returned
will start with this value and also contain any names that fall after
this value in alphabetical order.
If the ``limit`` parameter is specified and is non-zero, the number of
collection names returned will be limited to this value.
"""
return self._impl.get_collection_names(startName, limit)
def openCollection(self, name: str) -> "SodaCollection":
"""
Opens an existing collection with the given name and returns a new SODA
collection object. If a collection with that name does not exist,
*None* is returned.
"""
collection_impl = self._impl.open_collection(name)
if collection_impl is not None:
return SodaCollection._from_impl(self, collection_impl)
class SodaCollection(metaclass=BaseMetaClass):
@classmethod
def _from_impl(cls, db, impl):
coll = cls.__new__(cls)
coll._db = db
coll._impl = impl
return coll
def _process_doc_arg(self, arg):
if isinstance(arg, SodaDocument):
return arg._impl
return self._db._create_doc_impl(arg)
def createIndex(self, spec: Union[dict, str]) -> None:
"""
Creates an index on a SODA collection.
The ``spec`` parameter is expected to be a dictionary or a JSON-encoded
string.
Note that a commit should be performed before attempting to create an
index.
"""
if isinstance(spec, dict):
spec = json.dumps(spec)
elif not isinstance(spec, str):
raise TypeError("expecting a dictionary or string")
self._impl.create_index(spec)
def drop(self) -> bool:
"""
Drops the collection from the database, if it exists. Note that if the
collection was created with ``mapMode`` set to *True*, the underlying
table will not be dropped.
A boolean value is returned indicating if the collection was actually
dropped.
"""
return self._impl.drop()
def dropIndex(self, name: str, force: bool = False) -> bool:
"""
Drops the index with the specified name, if it exists.
The force parameter, if set to *True*, can be used to force the
dropping of an index that the underlying Oracle Database domain index
does not normally permit. This is only applicable to spatial and JSON
search indexes.
A boolean value is returned indicating if the index was actually
dropped.
"""
return self._impl.drop_index(name, force)
def find(self) -> "SodaOperation":
"""
Begins an operation that will act upon documents in the collection. It
creates and returns a SodaOperation object which is used to specify the
criteria and the operation that will be performed on the documents that
match that criteria.
"""
return SodaOperation(self)
def getDataGuide(self) -> "SodaDocument":
"""
Returns a SODA document object containing property names, data types,
and lengths inferred from the JSON documents in the collection. It can
be useful for exploring the schema of a collection. Note that this
method is only supported for JSON-only collections where a JSON search
index has been created with the dataguide option enabled. If there
are no documents in the collection, *None* is returned.
"""
doc_impl = self._impl.get_data_guide()
if doc_impl is not None:
return SodaDocument._from_impl(doc_impl)
def insertMany(self, docs: list) -> None:
"""
Inserts a list of documents into the collection at one time. Each of
the input documents can be a dictionary or list or an existing SODA
document object.
This method requires Oracle Client 18.5 (or later) and is available
only as a preview.
"""
doc_impls = [self._process_doc_arg(d) for d in docs]
self._impl.insert_many(doc_impls, hint=None, return_docs=False)
def insertManyAndGet(
self, docs: list, hint: Optional[str] = None
) -> list["SodaDocument"]:
"""
Similar to :meth:`SodaCollection.insertMany()`, this method inserts a
list of documents into the collection at one time. The only difference
is that it returns a list of SODA Document objects. Note that for
performance reasons the returned documents do not contain the content.
The ``hint`` parameter, if specified, supplies a hint to the database
when processing the SODA operation. This is expected to be a string in
the same format as SQL hints but without any comment characters, for
example hint="MONITOR". While you could use this to pass any SQL hint,
the hints MONITOR (turn on monitoring) and NO_MONITOR (turn off
monitoring) are the most useful. Use of the ``hint`` parameter requires
Oracle Client 21.3 or later (or Oracle Client 19 from 19.11).
This method requires Oracle Client 18.5 (or later).
"""
doc_impls = [self._process_doc_arg(d) for d in docs]
if hint is not None and not isinstance(hint, str):
raise TypeError("expecting a string")
return_doc_impls = self._impl.insert_many(
doc_impls, hint, return_docs=True
)
return [SodaDocument._from_impl(i) for i in return_doc_impls]
def insertOne(self, doc: Any) -> None:
"""
Inserts a given document into the collection. The input document can be
a dictionary or list or an existing SODA document object.
"""
doc_impl = self._process_doc_arg(doc)
self._impl.insert_one(doc_impl, hint=None, return_doc=False)
def insertOneAndGet(
self, doc: Any, hint: Optional[str] = None
) -> "SodaDocument":
"""
Similar to :meth:`~SodaCollection.insertOne()`, this method inserts a
given document into the collection. The only difference is that it
returns a SODA Document object. Note that for performance reasons the
returned document does not contain the content.
The ``hint`` parameter, if specified, supplies a hint to the database
when processing the SODA operation. This is expected to be a string in
the same format as SQL hints but without any comment characters, for
example hint="MONITOR". While you could use this to pass any SQL hint,
the hints MONITOR (turn on monitoring) and NO_MONITOR (turn off
monitoring) are the most useful. Use of the ``hint`` parameter requires
Oracle Client 21.3 or later (or Oracle Client 19 from 19.11).
"""
doc_impl = self._process_doc_arg(doc)
if hint is not None and not isinstance(hint, str):
raise TypeError("expecting a string")
return_doc_impl = self._impl.insert_one(
doc_impl, hint, return_doc=True
)
return SodaDocument._from_impl(return_doc_impl)
def listIndexes(self) -> list:
"""
Returns a list of specifications for the indexes found on the
collection.
This method requires Oracle Client 21.3 or later (or Oracle Client 19
from 19.13).
"""
return [json.loads(s) for s in self._impl.list_indexes()]
@property
def metadata(self) -> dict:
"""
This read-only attribute returns a dictionary containing the metadata
that was used to create the collection.
"""
return json.loads(self._impl.get_metadata())
@property
def name(self) -> str:
"""
This read-only attribute returns the name of the collection.
"""
return self._impl.name
def save(self, doc: Any) -> None:
"""
Saves a document into the collection. This method is equivalent to
:meth:`~SodaCollection.insertOne()` except that if client-assigned keys
are used, and the document with the specified key already exists in the
collection, it will be replaced with the input document.
This method requires Oracle Client 19.9 (or later) in addition to the
usual SODA requirements.
"""
doc_impl = self._process_doc_arg(doc)
self._impl.save(doc_impl, hint=None, return_doc=False)
def saveAndGet(
self, doc: Any, hint: Optional[str] = None
) -> "SodaDocument":
"""
Saves a document into the collection. This method is equivalent to
:meth:`~SodaCollection.insertOneAndGet()` except that if
client-assigned keys are used, and the document with the specified key
already exists in the collection, it will be replaced with the input
document.
The ``hint`` parameter, if specified, supplies a hint to the database
when processing the SODA operation. This is expected to be a string in
the same format as SQL hints but without any comment characters, for
example hint="MONITOR". While you could use this to pass any SQL hint,
the hints MONITOR (turn on monitoring) and NO_MONITOR (turn off
monitoring) are the most useful. Use of the ``hint`` parameter requires
Oracle Client 21.3 or later (or Oracle Client 19 from 19.11).
This method requires Oracle Client 19.9 (or later) in addition to the
usual SODA requirements.
"""
doc_impl = self._process_doc_arg(doc)
if hint is not None and not isinstance(hint, str):
raise TypeError("expecting a string")
return_doc_impl = self._impl.save(doc_impl, hint, return_doc=True)
return SodaDocument._from_impl(return_doc_impl)
def truncate(self) -> None:
"""
Removes all of the documents in the collection, similarly to what is
done for rows in a table by the TRUNCATE TABLE statement.
"""
self._impl.truncate()
class SodaDocument(metaclass=BaseMetaClass):
@classmethod
def _from_impl(cls, impl):
doc = cls.__new__(cls)
doc._impl = impl
return doc
@property
def createdOn(self) -> str:
"""
This read-only attribute returns the creation time of the document in
ISO 8601 format. Documents created by
:meth:`SodaDatabase.createDocument()` or fetched from collections where
this attribute is not stored will return *None*.
"""
return self._impl.get_created_on()
def getContent(self) -> Union[dict, list]:
"""
Returns the content of the document as a dictionary or list. This
method assumes that the content is application/json and will raise an
exception if this is not the case. If there is no content, however,
*None* will be returned.
"""
content, encoding = self._impl.get_content()
if isinstance(content, bytes) and self.mediaType == "application/json":
return json.loads(content.decode(encoding))
return content
def getContentAsBytes(self) -> bytes:
"""
Returns the content of the document as a bytes object. If there is no
content, however, *None* will be returned.
"""
content, encoding = self._impl.get_content()
if isinstance(content, bytes):
return content
elif content is not None:
return str(content).encode()
def getContentAsString(self) -> str:
"""
Returns the content of the document as a string. If the document
encoding is not known, UTF-8 will be used. If there is no content,
however, *None* will be returned.
"""
content, encoding = self._impl.get_content()
if isinstance(content, bytes):
return content.decode(encoding)
elif content is not None:
return str(content)
@property
def key(self) -> str:
"""
This read-only attribute returns the unique key assigned to this
document. Documents created by :meth:`SodaDatabase.createDocument()`
may not have a value assigned to them and return *None*.
"""
return self._impl.get_key()
@property
def lastModified(self) -> str:
"""
This read-only attribute returns the last modified time of the document
in ISO 8601 format. Documents created by
:meth:`SodaDatabase.createDocument()` or fetched from collections where
this attribute is not stored will return *None*.
"""
return self._impl.get_last_modified()
@property
def mediaType(self) -> str:
"""
This read-only attribute returns the media type assigned to the
document. By convention this is expected to be a MIME type but no
checks are performed on this value. If a value is not specified when
calling :meth:`SodaDatabase.createDocument()` or the document is
fetched from a collection where this component is not stored, the
string “application/json” is returned.
"""
return self._impl.get_media_type()
@property
def version(self) -> str:
"""
This read-only attribute returns the version assigned to this document.
Documents created by :meth:`SodaDatabase.createDocument()` or fetched
from collections where this attribute is not stored will return *None*.
"""
return self._impl.get_version()
class SodaDocCursor(metaclass=BaseMetaClass):
def __iter__(self):
return self
def __next__(self):
if self._impl is None:
errors._raise_err(errors.ERR_CURSOR_NOT_OPEN)
doc_impl = self._impl.get_next_doc()
if doc_impl is not None:
return SodaDocument._from_impl(doc_impl)
raise StopIteration
@classmethod
def _from_impl(cls, impl):
cursor = cls.__new__(cls)
cursor._impl = impl
return cursor
def close(self) -> None:
"""
Closes the cursor now, rather than whenever __del__ is called. The
cursor will be unusable from this point forward; an Error exception
will be raised if any operation is attempted with the cursor.
"""
if self._impl is None:
errors._raise_err(errors.ERR_CURSOR_NOT_OPEN)
self._impl.close()
self._impl = None
class SodaOperation(metaclass=BaseMetaClass):
def __init__(self, collection: SodaCollection) -> None:
self._collection = collection
self._key = None
self._keys = None
self._version = None
self._filter = None
self._hint = None
self._skip = None
self._limit = None
self._fetch_array_size = None
self._lock = False
def count(self) -> int:
"""
Returns a count of the number of documents in the collection that match
the criteria. If :meth:`~SodaOperation.skip()` or
:meth:`~SodaOperation.limit()` were called on this object, an
exception is raised.
"""
return self._collection._impl.get_count(self)
def fetchArraySize(self, value: int) -> Self:
"""
This is a tuning method to specify the number of documents that are
internally fetched in batches by calls to
:meth:`~SodaOperation.getCursor()` and
:meth:`~SodaOperation.getDocuments()`. It does not affect how many
documents are returned to the application.
If ``fetchArraySize()`` is not used, or the ``value`` parameter is *0*,
the array size will default to *100*.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
This method is only available when using Oracle Client 19.5, or later.
"""
if not isinstance(value, int) or value < 0:
raise TypeError("expecting integer >= 0")
if value == 0:
self._fetch_array_size = None
else:
self._fetch_array_size = value
return self
def filter(self, value: Union[dict, str]) -> Self:
"""
Sets a filter specification for complex document queries and ordering
of JSON documents. Filter specifications must be provided as a
dictionary or JSON-encoded string and can include comparisons, regular
expressions, logical and spatial operators, among others.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
"""
if isinstance(value, dict):
self._filter = json.dumps(value)
elif isinstance(value, str):
self._filter = value
else:
raise TypeError("expecting string or dictionary")
return self
def getCursor(self) -> "SodaDocCursor":
"""
Returns a SodaDocCursor object that can be used to iterate over the
documents that match the criteria.
"""
impl = self._collection._impl.get_cursor(self)
return SodaDocCursor._from_impl(impl)
def getDocuments(self) -> list["SodaDocument"]:
"""
Returns a list of SodaDocument objects that match the criteria.
"""
return [d for d in self.getCursor()]
def getOne(self) -> Union["SodaDocument", None]:
"""
Returns a single SodaDocument object that matches the criteria. Note
that if multiple documents match the criteria only the first one is
returned.
"""
doc_impl = self._collection._impl.get_one(self)
if doc_impl is not None:
return SodaDocument._from_impl(doc_impl)
def hint(self, value: str) -> Self:
"""
Specifies a hint that will be provided to the SODA operation when it is
performed. This is expected to be a string in the same format as SQL
hints but without any comment characters. While you could use this to
pass any SQL hint, the hints MONITOR (turn on monitoring) and
NO_MONITOR (turn off monitoring) are the most useful. Use of this
method requires Oracle Client 21.3 or later (or Oracle Client 19 from
19.11).
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
"""
if not isinstance(value, str):
raise TypeError("expecting a string")
self._hint = value
return self
def lock(self) -> Self:
"""
Specifies whether the documents fetched from the collection should be
locked (equivalent to SQL "select for update"). Use of this method
requires Oracle Client 21.3 or later (or Oracle Client 19 from 19.11).
The next commit or rollback on the connection made after the operation
is performed will "unlock" the documents. Ensure that the connection is
not in autocommit mode or the documents will be unlocked immediately
after the operation is complete.
This method should only be used with read operations (other than
:func:`~SodaOperation.count()`) and should not be used in conjunction
with non-terminal methods :meth:`~SodaOperation.skip()` and
:meth:`~SodaOperation.limit()`.
If this method is specified in conjunction with a write operation, this
method is ignored.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
"""
self._lock = True
return self
def key(self, value: str) -> Self:
"""
Specifies that the document with the specified key should be returned.
This causes any previous calls made to this method and
:meth:`~SodaOperation.keys()` to be ignored.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
"""
if not isinstance(value, str):
raise TypeError("expecting string")
self._key = value
self._keys = None
return self
def keys(self, value: list) -> Self:
"""
Specifies that documents that match the keys found in the supplied
sequence should be returned. This causes any previous calls made to
this method and :meth:`~SodaOperation.key()` to be ignored.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
"""
value_as_list = list(value)
for element in value_as_list:
if not isinstance(element, str):
raise TypeError("expecting sequence of strings")
self._keys = value_as_list
self._key = None
return self
def limit(self, value: int) -> Self:
"""
Specifies that only the specified number of documents should be
returned. This method is only usable for read operations such as
:meth:`~SodaOperation.getCursor()` and
:meth:`~SodaOperation.getDocuments()`. For write operations, any value
set using this method is ignored.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
"""
if not isinstance(value, int) or value <= 0:
raise TypeError("expecting positive integer")
self._limit = value
return self
def remove(self) -> int:
"""
Removes all of the documents in the collection that match the criteria.
The number of documents that have been removed is returned.
"""
return self._collection._impl.remove(self)
def replaceOne(self, doc: Any) -> bool:
"""
Replaces a single document in the collection with the specified
document. The input document can be a dictionary or list or an existing
SODA document object. A boolean indicating if a document was replaced
or not is returned.
Currently, the method :meth:`~SodaOperation.key()` must be called
before this method can be called.
"""
doc_impl = self._collection._process_doc_arg(doc)
return self._collection._impl.replace_one(
self, doc_impl, return_doc=False
)
def replaceOneAndGet(self, doc: Any) -> "SodaDocument":
"""
Similar to :meth:`~SodaOperation.replaceOne()`, this method replaces a
single document in the collection with the specified document. The only
difference is that it returns a SodaDocument object. Note that for
performance reasons the returned document does not contain the content.
"""
doc_impl = self._collection._process_doc_arg(doc)
return_doc_impl = self._collection._impl.replace_one(
self, doc_impl, return_doc=True
)
return SodaDocument._from_impl(return_doc_impl)
def skip(self, value: int) -> Self:
"""
Specifies the number of documents that match the other criteria that
will be skipped. This method is only usable for read operations such as
:meth:`~SodaOperation.getOne()`, :meth:`~SodaOperation.getCursor()`,
and :meth:`~SodaOperation.getDocuments()`. For write operations, any
value set using this method is ignored.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
"""
if not isinstance(value, int) or value < 0:
raise TypeError("expecting integer >= 0")
self._skip = value
return self
def version(self, value: str) -> Self:
"""
Specifies that documents with the specified version should be returned.
Typically this is used with :meth:`~SodaOperation.key()` to implement
optimistic locking, so that the write operation called later does not
affect a document that someone else has modified.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
"""
if not isinstance(value, str):
raise TypeError("expecting string")
self._version = value
return self

View File

@@ -0,0 +1,114 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# sparse_vector.py
#
# Contains the SparseVector class which stores information about a sparse
# vector. Sparse vectors are available in Oracle Database 23.6 and higher.
# -----------------------------------------------------------------------------
import array
from typing import Union
from .base import BaseMetaClass
from .base_impl import get_array_type_code_uint32, SparseVectorImpl
ARRAY_TYPE_CODE_UINT32 = get_array_type_code_uint32()
class SparseVector(metaclass=BaseMetaClass):
"""
Provides information about sparse vectors.
"""
def __init__(
self,
num_dimensions: int,
indices: Union[list, array.array],
values: Union[list, array.array],
):
"""
Creates and returns a :ref:`SparseVector object <sparsevectorsobj>`.
The ``num_dimensions`` parameter is the number of dimensions contained
in the vector.
The ``indices`` parameter is the indices (zero-based) of non-zero
values in the vector.
The ``values`` parameter is the non-zero values stored in the vector.
"""
if (
not isinstance(indices, array.array)
or indices.typecode != ARRAY_TYPE_CODE_UINT32
):
indices = array.array(ARRAY_TYPE_CODE_UINT32, indices)
if not isinstance(values, array.array):
values = array.array("d", values)
if len(indices) != len(values):
raise TypeError("indices and values must be of the same length!")
self._impl = SparseVectorImpl.from_values(
num_dimensions, indices, values
)
def __repr__(self):
cls_name = self.__class__._public_name
return (
f"{cls_name}({self.num_dimensions}, "
f"{self.indices}, {self.values})"
)
def __str__(self):
return (
f"[{self.num_dimensions}, {list(self.indices)}, "
f"{list(self.values)}]"
)
@classmethod
def _from_impl(cls, impl):
vector = cls.__new__(cls)
vector._impl = impl
return vector
@property
def indices(self) -> array.array:
"""
Returns the indices (zero-based) of non-zero values in the vector.
"""
return self._impl.indices
@property
def num_dimensions(self) -> int:
"""
Returns the number of dimensions contained in the vector.
"""
return self._impl.num_dimensions
@property
def values(self) -> array.array:
"""
Returns the non-zero values stored in the vector.
"""
return self._impl.values

View File

@@ -0,0 +1,378 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# subscr.py
#
# Contains the Subscription class and Message classes used for managing
# subscriptions to database events and the messages that are sent when those
# events are detected.
# -----------------------------------------------------------------------------
from typing import Callable, Optional, Union
from .base import BaseMetaClass
from . import connection
class Subscription(metaclass=BaseMetaClass):
def __repr__(self):
return f"<oracledb.Subscription on {self.connection!r}>"
@classmethod
def _from_impl(cls, impl):
subscr = cls.__new__(cls)
subscr._impl = impl
return subscr
@property
def callback(self) -> Callable:
"""
This read-only attribute returns the callback that was registered when
the subscription was created.
"""
return self._impl.callback
@property
def connection(self) -> "connection.Connection":
"""
This read-only attribute returns the connection that was used to
register the subscription when it was created.
"""
return self._impl.connection
@property
def id(self) -> int:
"""
This read-only attribute returns the value of REGID found in the
database view USER_CHANGE_NOTIFICATION_REGS or the value of REG_ID
found in the database view USER_SUBSCR_REGISTRATIONS. For AQ
subscriptions, this value is *0*.
"""
return self._impl.id
@property
def ip_address(self) -> str:
"""
This read-only attribute returns the IP address used for callback
notifications from the database server. If not set during construction,
this value is *None*.
"""
return self._impl.ip_address
@property
def ipAddress(self) -> str:
"""
Deprecated. Use property ip_address instead.
"""
return self.ip_address
@property
def name(self) -> str:
"""
This read-only attribute returns the name used to register the
subscription when it was created.
"""
return self._impl.name
@property
def namespace(self) -> int:
"""
This read-only attribute returns the namespace used to register the
subscription when it was created.
"""
return self._impl.namespace
@property
def operations(self) -> int:
"""
This read-only attribute returns the operations that will send
notifications for each table or query that is registered using this
subscription.
"""
return self._impl.operations
@property
def port(self) -> int:
"""
This read-only attribute returns the port used for callback
notifications from the database server. If not set during
construction, this value is *0*.
"""
return self._impl.port
@property
def protocol(self) -> int:
"""
This read-only attribute returns the protocol used to register the
subscription when it was created.
"""
return self._impl.protocol
@property
def qos(self) -> int:
"""
This read-only attribute returns the quality of service flags used to
register the subscription when it was created.
"""
return self._impl.qos
def registerquery(
self, statement: str, args: Optional[Union[list, dict]] = None
) -> int:
"""
Registers the query for subsequent notification when tables referenced
by the query are changed. This behaves similarly to
:meth:`Cursor.execute()` but only queries are permitted and the
``args`` parameter, if specified, must be a sequence or dictionary. If
the ``qos`` parameter included the flag
:data:`oracledb.SUBSCR_QOS_QUERY` when the subscription was created,
then the ID for the registered query is returned; otherwise, *None* is
returned.
"""
if args is not None and not isinstance(args, (list, dict)):
raise TypeError("expecting args to be a dictionary or list")
return self._impl.register_query(statement, args)
@property
def timeout(self) -> int:
"""
This read-only attribute returns the timeout (in seconds) that was
specified when the subscription was created. A value of *0* indicates
that there is no timeout.
"""
return self._impl.timeout
class Message(metaclass=BaseMetaClass):
def __init__(self, subscription: Subscription) -> None:
self._subscription = subscription
self._consumer_name = None
self._dbname = None
self._queries = []
self._queue_name = None
self._registered = False
self._tables = []
self._txid = None
self._type = 0
self._msgid = None
@property
def consumer_name(self) -> Union[str, None]:
"""
This read-only attribute returns the name of the consumer which
generated the notification. It will be populated if the
subscription was created with the namespace
:data:`oracledb.SUBSCR_NAMESPACE_AQ` and the queue is a multiple
consumer queue.
"""
return self._consumer_name
@property
def consumerName(self) -> Union[str, None]:
"""
Deprecated. Use property consumer_name instead.
"""
return self.consumer_name
@property
def dbname(self) -> Union[str, None]:
"""
This read-only attribute returns the name of the database that
generated the notification.
"""
return self._dbname
@property
def msgid(self) -> Union[bytes, None]:
"""
This read-only attribute returns the message id of the AQ message that
generated the notification. It will only be populated if the
subscription was created with the namespace
:data:`oracledb.SUBSCR_NAMESPACE_AQ`.
"""
return self._msgid
@property
def queries(self) -> list["MessageQuery"]:
"""
This read-only attribute returns a list of message query objects that
give information about query result sets changed for this notification.
This attribute will be an empty list if the ``qos`` parameter did not
include the flag :data:`~oracledb.SUBSCR_QOS_QUERY` when the
subscription was created.
"""
return self._queries
@property
def queue_name(self) -> Union[str, None]:
"""
This read-only attribute returns the name of the queue which generated
the notification. It will only be populated if the subscription was
created with the namespace :data:`oracledb.SUBSCR_NAMESPACE_AQ`.
"""
return self._queue_name
@property
def queueName(self) -> Union[str, None]:
"""
Deprecated. Use property queue_name instead.
"""
return self.queue_name
@property
def registered(self) -> bool:
"""
This read-only attribute returns whether the subscription which
generated this notification is still registered with the database. The
subscription is automatically deregistered with the database when the
subscription timeout value is reached or when the first notification is
sent (when the quality of service flag
:data:`oracledb.SUBSCR_QOS_DEREG_NFY` is used).
"""
return self._registered
@property
def subscription(self) -> Subscription:
"""
This read-only attribute returns the subscription object for which this
notification was generated.
"""
return self._subscription
@property
def tables(self) -> list["MessageTable"]:
"""
This read-only attribute returns a list of message table objects that
give information about the tables changed for this notification. This
attribute will be an empty list if the ``qos`` parameter included the
flag :data:`~oracledb.SUBSCR_QOS_QUERY` when the subscription was
created.
"""
return self._tables
@property
def txid(self) -> Union[bytes, None]:
"""
This read-only attribute returns the id of the transaction that
generated the notification.
"""
return self._txid
@property
def type(self) -> int:
"""
This read-only attribute returns the type of message that has been
sent.
"""
return self._type
class MessageQuery(metaclass=BaseMetaClass):
def __init__(self) -> None:
self._id = 0
self._operation = 0
self._tables = []
@property
def id(self) -> int:
"""
This read-only attribute returns the query id of the query for which
the result set changed. The value will match the value returned by
:meth:`Subscription.registerquery()` when the related query was
registered.
"""
return self._id
@property
def operation(self) -> int:
"""
This read-only attribute returns the operation that took place on the
query result set that was changed. Valid values for this attribute are
:data:`~oracledb.EVENT_DEREG` and :data:`~oracledb.EVENT_QUERYCHANGE`.
"""
return self._operation
@property
def tables(self) -> list["MessageTable"]:
"""
This read-only attribute returns a list of message table objects that
give information about the table changes that caused the query result
set to change for this notification.
"""
return self._tables
class MessageRow(metaclass=BaseMetaClass):
def __init__(self) -> None:
self._operation = 0
self._rowid = None
@property
def operation(self) -> int:
"""
This read-only attribute returns the operation that took place on the
row that was changed.
"""
return self._operation
@property
def rowid(self) -> Union[str, None]:
"""
This read-only attribute returns the rowid of the row that was changed.
"""
return self._rowid
class MessageTable(metaclass=BaseMetaClass):
def __init__(self) -> None:
self._name = None
self._operation = 0
self._rows = []
@property
def name(self) -> Union[str, None]:
"""
This read-only attribute returns the name of the table that was
changed.
"""
return self._name
@property
def operation(self) -> int:
"""
This read-only attribute returns the operation that took place on the
table that was changed.
"""
return self._operation
@property
def rows(self) -> list["MessageRow"]:
"""
This read-only attribute returns a list of message row objects that
give information about the rows changed on the table. This value is
only filled in if the ``qos`` parameter to the
:meth:`Connection.subscribe()` method included the flag
:data:`~oracledb.SUBSCR_QOS_ROWIDS`.
"""
return self._rows

View File

@@ -0,0 +1,392 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# utils.py
#
# Contains utility classes and methods.
# -----------------------------------------------------------------------------
import functools
from typing import Any, Callable, Optional, Union
import uuid
from .arrow_array import ArrowArray
from .dataframe import DataFrame
from . import base_impl
from . import driver_mode
from . import errors
from . import thick_impl
def clientversion() -> tuple:
"""
This function can only be called when python-oracledb is in Thick mode.
Using it in Thin mode will throw an exception.
"""
return thick_impl.clientversion()
def enable_thin_mode():
"""
Makes python-oracledb be in Thin mode. After this method is called, Thick
mode cannot be enabled. If python-oracledb is already in Thick mode, then
calling ``enable_thin_mode()`` will fail. If Thin mode connections have
already been opened, or a connection pool created in Thin mode, then
calling ``enable_thin_mode()`` is a no-op.
Since python-oracledb defaults to Thin mode, almost all applications do not
need to call this method. However, because it bypasses python-oracledb's
internal mode-determination heuristic, it may be useful for applications
with multiple threads that concurrently create :ref:`standalone connections
<standaloneconnection>` when the application starts.
"""
with driver_mode.get_manager(requested_thin_mode=True):
pass
def from_arrow(obj: Any) -> Union[DataFrame, ArrowArray]:
"""
This method converts a data frame to a
:ref:`DataFrame <oracledataframeobj>` or
:ref:`ArrowArray <oraclearrowarrayobj>` instance.
If ``obj`` supports the Arrow PyCapsule interface ``__arrow_c_stream__``
method, then ``from_arrow()`` returns the instance as a :ref:`DataFrame
<oracledataframeobj>`. If ``obj`` does not support that method, but does
support ``__arrow_c_array__``, then an :ref:`ArrowArray
<oraclearrowarrayobj>` is returned.
"""
if hasattr(obj, "__arrow_c_stream__"):
return DataFrame._from_arrow(obj)
elif hasattr(obj, "__arrow_c_array__"):
return ArrowArray._from_arrow(obj)
msg = "object must implement the PyCapsule stream or array interfaces"
raise ValueError(msg)
def init_oracle_client(
lib_dir: Optional[Union[str, bytes]] = None,
config_dir: Optional[Union[str, bytes]] = None,
error_url: Optional[str] = None,
driver_name: Optional[str] = None,
):
"""
Enables python-oracledb Thick mode by initializing the Oracle Client
library, see :ref:`enablingthick`. If a standalone connection or pool has
already been created in Thin mode, ``init_oracle_client()`` will raise an
exception and python-oracledb will remain in Thin mode.
If a standalone connection or pool has *not* already been created in Thin
mode, but ``init_oracle_client()`` raises an exception, python-oracledb
will remain in Thin mode but further calls to ``init_oracle_client()`` can
be made, if desired.
The ``init_oracle_client()`` method can be called multiple times in each
Python process as long as the arguments are the same each time.
The ``lib_dir`` parameter is a string or a bytes object that specifies the
directory containing Oracle Client libraries. If the ``lib_dir`` parameter
is set, then the specified directory is the only one searched for the
Oracle Client libraries; otherwise, the operating system library search
path is used to locate the Oracle Client library. If you are using Python
3.11 and later, then the value specified in this parameter is encoded
using `locale.getencoding() <https://docs.python.org/3/library/locale.html
#locale.getencoding>`__. For all other Python versions, the encoding
"utf-8" is used. If a bytes object is specified in this parameter, then
this value will be used as is without any encoding.
The ``config_dir`` parameter is a string or a bytes object that specifies
the directory in which the
:ref:`Optional Oracle Net Configuration <optnetfiles>` and
:ref:`Optional Oracle Client Configuration <optclientfiles>` files reside.
If the ``config_dir`` parameter is set, then the specified directory is
used to find Oracle Client library configuration files. This is
equivalent to setting the environment variable ``TNS_ADMIN`` and overrides
any value already set in ``TNS_ADMIN``. If this parameter is not set, the
:ref:`Oracle standard <usingconfigfiles>` way of locating Oracle Client
library configuration files is used. If you are using Python 3.11 and
later, then the value specified in this parameter is encoded using
`locale.getencoding() <https://docs.python.org/3/library/locale.html#
locale.getencoding>`__. For all other Python versions, the encoding
"utf-8" is used. If a bytes object is specified in this parameter, then
this value will be used as is without any encoding.
The ``error_url`` parameter is a string that specifies the URL which is
included in the python-oracledb exception message if the Oracle Client
libraries cannot be loaded. If the ``error_url`` parameter is set, then
the specified value is included in the message of the exception raised
when the Oracle Client library cannot be loaded; otherwise, the
:ref:`installation` URL is included. This parameter lets your application
display custom installation instructions.
The ``driver_name`` parameter is a string that specifies the driver name
value. If the ``driver_name`` parameter is set, then the specified value
can be found in database views that give information about connections.
For example, it is in the CLIENT_DRIVER column of the
V$SESSION_CONNECT_INFO view. From Oracle Database 12.2, the name displayed
can be 30 characters. The standard is to set this value to ``"<name> :
version>"``, where <name> is the name of the driver and <version> is its
version. There should be a single space character before and after the
colon. If this parameter is not set, then the value specified in
:attr:`oracledb.defaults.driver_name <defaults.driver_name>` is used. If
the value of this attribute is *None*, then the default value in
python-oracledb Thick mode is like "python-oracledb thk : <version>". See
:ref:`otherinit`.
At successful completion of a call to ``oracledb.init_oracle_client()``,
the attribute :attr:`oracledb.defaults.config_dir <Defaults.config_dir>`
will be set as determined below (first one wins):
- the value of the ``oracledb.init_oracle_client()`` parameter
``config_dir``, if one was passed.
- the value of :attr:`oracledb.defaults.config_dir <Defaults.config_dir>`
if it has one. i.e.
:attr:`oracledb.defaults.config_dir <Defaults.config_dir>` remains
unchanged after ``oracledb.init_oracle_client()`` completes.
- the value of the environment variable ``$TNS_ADMIN``, if it is set.
- the value of ``$ORACLE_HOME/network/admin`` if the environment variable
``$ORACLE_HOME`` is set.
- the directory of the loaded Oracle Client library, appended with
``network/admin``. Note this directory is not determinable on AIX.
- otherwise the value *None* is used. (Leaving
:attr:`oracledb.defaults.config_dir <Defaults.config_dir>` unchanged).
"""
thick_impl.init_oracle_client(lib_dir, config_dir, error_url, driver_name)
def normalize_sessionless_transaction_id(
value: Optional[Union[bytes, str]] = None,
) -> bytes:
"""
Normalize and validate the transaction_id.
- If `value` is a string, it's UTF-8 encoded.
- If `value` is None, a UUID4-based transaction_id is generated.
- If `value` is not str/bytes/None, raises TypeError.
- If transaction_id exceeds 64 bytes, raises ValueError.
Returns:
bytes: Normalized transaction_id
"""
if value is None:
value = uuid.uuid4().bytes
elif isinstance(value, str):
value = value.encode("utf-8")
elif not isinstance(value, bytes):
raise TypeError("invalid transaction_id: must be str, bytes, or None")
if len(value) > 64:
raise ValueError(
f"transaction_id size exceeds 64 bytes (got {len(value)})"
)
return value
def params_initer(f):
"""
Decorator function which is used on the ConnectParams and PoolParams
classes. It creates the implementation object using the implementation
class stored on the parameter class. It first, however, calls the original
method to ensure that the keyword parameters supplied are valid (the
original method itself does nothing).
"""
@functools.wraps(f)
def wrapped_f(self, *args, **kwargs):
f(self, *args, **kwargs)
self._impl = self._impl_class()
if kwargs:
self._impl.set(kwargs)
return wrapped_f
def params_setter(f):
"""
Decorator function which is used on the ConnectParams and PoolParams
classes. It calls the set() method on the parameter implementation object
with the supplied keyword arguments. It first, however, calls the original
method to ensure that the keyword parameters supplied are valid (the
original method itself does nothing).
"""
@functools.wraps(f)
def wrapped_f(self, *args, **kwargs):
f(self, *args, **kwargs)
self._impl.set(kwargs)
return wrapped_f
def register_params_hook(hook_function: Callable) -> None:
"""
Registers a user parameter hook function that will be called internally by
python-oracledb prior to connection or pool creation. The hook function
accepts a copy of the parameters that will be used to create the pool or
standalone connection and may modify them. For example, the cloud native
authentication plugins modify the "access_token" parameter with a function
that will acquire the token using information found in the
"extra_auth_parms" parameter.
Multiple hooks may be registered. They will be invoked in order of
registration.
"""
if hook_function is None or not callable(hook_function):
raise TypeError("hook_function must be a callable and cannot be None")
base_impl.REGISTERED_PARAMS_HOOKS.append(hook_function)
def register_password_type(
password_type: str, hook_function: Callable
) -> None:
"""
Registers a user password hook function that will be called internally by
python-oracledb when a password is supplied as a dictionary containing the
given ``password_type`` as the key "type". The hook function is called for
passwords specified as the ``password``, ``newpassword`` and
``wallet_parameter`` parameters in calls to :meth:`oracledb.connect()`,
:meth:`oracledb.create_pool()`, :meth:`oracledb.connect_async()`, and
:meth:`oracledb.create_pool_async()`.
Your hook function is expected to accept the dictionary supplied by the
application and return the valid password.
Calling :meth:`~oracledb.register_password_type()` with the
``hook_function`` parameter set to *None* will result in a previously
registered user function being removed and the default behavior restored.
"""
if not isinstance(password_type, str):
raise TypeError("password_type must be a string")
if hook_function is not None and not callable(hook_function):
raise TypeError("hook_function must be a callable")
password_type = password_type.lower()
if hook_function is None:
base_impl.REGISTERED_PASSWORD_TYPES.pop(password_type)
else:
base_impl.REGISTERED_PASSWORD_TYPES[password_type] = hook_function
def register_protocol(protocol: str, hook_function: Callable) -> None:
"""
Registers a user protocol hook function that will be called internally by
python-oracledb Thin mode prior to connection or pool creation. The hook
function will be invoked when :func:`oracledb.connect`,
:func:`oracledb.create_pool`, :meth:`oracledb.connect_async()`, or
:meth:`oracledb.create_pool_async()` are called with a ``dsn`` parameter
value prefixed with the specified protocol. The user function will also be
invoked when :meth:`ConnectParams.parse_connect_string()` is called in Thin
or Thick modes with a similar ``connect_string`` parameter value.
Your hook function is expected to construct valid connection details. For
example, if a hook function is registered for the "ldaps" protocol, then
calling :func:`oracledb.connect` with a connection string prefixed with
"ldaps://" will invoke the function. The function can then perform LDAP
lookup to retrieve and set the actual database information that will be
used internally by python-oracledb to complete the connection creation.
The ``protocol`` parameter is a string that will be matched against the
prefix appearing before "://" in connection strings.
The ``hook_function`` parameter should be a function with the signature::
hook_function(protocol, protocol_arg, params)
The hook function will be called with the following arguments:
- The ``protocol`` parameter is the value that was registered.
- The ``protocol_arg`` parameter is the section after "://" in the
connection string used in the connection or pool creation call, or passed
to :meth:`~ConnectParams.parse_connect_string()`.
- The ``params`` parameter is an instance of :ref:`ConnectParams
<connparam>`.
When your hook function is invoked internally prior to connection or pool
creation, ``params`` will be the ConnectParams instance originally passed
to the :func:`oracledb.connect`, :func:`oracledb.create_pool`,
:meth:`oracledb.connect_async()`, or :meth:`oracledb.create_pool_async()`
call, if such an instance was passed. Otherwise it will be a new
ConnectParams instance. The hook function should parse ``protocol`` and
``protocol_arg`` and take any desired action to update ``params``
:ref:`attributes <connparamsattr>` with appropriate connection
parameters. Attributes can be set using :meth:`ConnectParams.set()` or
:meth:`ConnectParams.parse_connect_string()`. The ConnectParams instance
will then be used to complete the connection or pool creation.
When your hook function is invoked by
:meth:`ConnectParams.parse_connect_string()`, then ``params`` will be the
invoking ConnectParams instance that you can update using
:meth:`ConnectParams.set()` or
:meth:`ConnectParams.parse_connect_string()`.
Internal hook functions for the "tcp" and "tcps" protocols are
pre-registered but can be overridden if needed. If any other protocol has
not been registered, then connecting will result in the error ``DPY-4021:
invalid protocol``.
Calling :meth:`~oracledb.register_protocol()` with the ``hook_function``
parameter set to *None* will result in a previously registered user
function being removed and the default behavior restored.
"""
if not isinstance(protocol, str):
raise TypeError("protocol must be a string")
if hook_function is not None and not callable(hook_function):
raise TypeError("hook_function must be a callable")
protocol = protocol.lower()
if hook_function is None:
base_impl.REGISTERED_PROTOCOLS.pop(protocol)
else:
base_impl.REGISTERED_PROTOCOLS[protocol] = hook_function
def unregister_params_hook(hook_function: Callable) -> None:
"""
Unregisters a user parameter function that was earlier registered with a
call to :meth:`oracledb.register_params_hook()`.
"""
base_impl.REGISTERED_PARAMS_HOOKS.remove(hook_function)
def verify_stored_proc_args(
parameters: Union[list, tuple], keyword_parameters: dict
) -> None:
"""
Verifies that the arguments to a call to a stored procedure or function
are acceptable.
"""
if parameters is not None and not isinstance(parameters, (list, tuple)):
errors._raise_err(errors.ERR_ARGS_MUST_BE_LIST_OR_TUPLE)
if keyword_parameters is not None and not isinstance(
keyword_parameters, dict
):
errors._raise_err(errors.ERR_KEYWORD_ARGS_MUST_BE_DICT)

View File

@@ -0,0 +1,186 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# var.py
#
# Contains the Var class used for managing variables used during bind and
# fetch. These hold the metadata as well as any necessary buffers.
# -----------------------------------------------------------------------------
from typing import Any, Callable, Optional, Union
from .dbobject import DbObjectType
from .base import BaseMetaClass
from .base_impl import DbType
class Var(metaclass=BaseMetaClass):
def __repr__(self):
value = self._impl.get_all_values()
if not self._impl.is_array and len(value) == 1:
value = value[0]
typ = self._type
return f"<oracledb.Var of type {typ.name} with value {repr(value)}>"
@classmethod
def _from_impl(cls, impl, typ=None):
var = cls.__new__(cls)
var._impl = impl
if typ is not None:
var._type = typ
elif impl.metadata.objtype is not None:
var._type = DbObjectType._from_impl(impl.metadata.objtype)
else:
var._type = impl.metadata.dbtype
return var
@property
def actual_elements(self) -> int:
"""
This read-only attribute returns the actual number of elements in the
variable. This corresponds to the number of elements in a PL/SQL
index-by table for variables that are created using the method
:meth:`Cursor.arrayvar()`. For all other variables, this value will be
identical to the attribute num_elements.
"""
if self._impl.is_array:
return self._impl.num_elements_in_array
return self._impl.num_elements
@property
def actualElements(self) -> int:
"""
Deprecated. Use property actual_elements instead.
"""
return self.actual_elements
@property
def buffer_size(self) -> int:
"""
This read-only attribute returns the size of the buffer allocated for
each element in bytes.
"""
return self._impl.metadata.buffer_size
@property
def bufferSize(self) -> int:
"""
Deprecated. Use property buffer_size intead().
"""
return self.buffer_size
@property
def convert_nulls(self) -> bool:
"""
This read-only attribute returns whether null values are converted
using the supplied ``outconverter``.
"""
return self._impl.convert_nulls
def getvalue(self, pos: int = 0) -> Any:
"""
Returns the value at the given position in the variable. For variables
created using the method :meth:`Cursor.arrayvar()`, the value returned
will be a list of each of the values in the PL/SQL index-by table. For
variables bound to DML returning statements, the value returned will
also be a list corresponding to the returned data for the given
execution of the statement (as identified by the ``pos`` parameter).
"""
return self._impl.get_value(pos)
@property
def inconverter(self) -> Optional[Callable]:
"""
This read-only attribute specifies the method used to convert data from
Python to the Oracle database. The method signature is converter(value)
and the expected return value is the value to bind to the database. If
this attribute is *None*, the value is bound directly without any
conversion.
"""
return self._impl.inconverter
@property
def num_elements(self) -> int:
"""
This read-only attribute returns the number of elements allocated in an
array, or the number of scalar items that can be fetched into the
variable or bound to the variable.
"""
return self._impl.num_elements
@property
def numElements(self) -> int:
"""
Deprecated. Use property num_elements instead.
"""
return self.num_elements
@property
def outconverter(self) -> Optional[Callable]:
"""
This read-only attribute specifies the method used to convert data from
the Oracle database to Python. The method signature is converter(value)
and the expected return value is the value to return to Python. If this
attribute is *None*, the value is returned directly without any
conversion.
"""
return self._impl.outconverter
def setvalue(self, pos: int, value: Any) -> None:
"""
Sets the value at the given position in the variable.
"""
self._impl.set_value(pos, value)
@property
def size(self) -> int:
"""
This read-only attribute returns the size of the variable. For strings
this value is the size in characters. For all others, this is the same
value as the attribute buffer_size.
"""
return self._impl.metadata.max_size
@property
def type(self) -> Union[DbType, DbObjectType]:
"""
This read-only attribute returns the type of the variable. This will be
an :ref:`Oracle Object Type <dbobjecttype>` if the variable binds
Oracle objects; otherwise, it will be one of the
:ref:`database type constants <dbtypes>`.
Database type constants are now used when the variable is not used for
binding Oracle objects.
"""
return self._type
@property
def values(self) -> list:
"""
This read-only attribute returns a copy of the value of all actual
positions in the variable as a list. This is the equivalent of calling
getvalue() for each valid position and the length will correspond to
the value of the actual_elements attribute.
"""
return self._impl.get_all_values()

View File

@@ -0,0 +1,33 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2021, 2026, Oracle and/or its affiliates.
#
# This software is dual-licensed to you under the Universal Permissive License
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
# either license.
#
# If you elect to accept the software under the Apache License, Version 2.0,
# the following applies:
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# version.py
#
# Defines the version of the package. This is the only place where this is
# found. The setup.cfg configuration file and the documentation configuration
# file doc/src/conf.py both reference this file directly.
# -----------------------------------------------------------------------------
__version__ = "3.4.2"