Skip to content

Commit

Permalink
Pep249 (#57)
Browse files Browse the repository at this point in the history
* add pep 249 support

* add async pep 249 support

* add pep 249 async support

* add __init__.py files

* update min python to 3.9 in actions

* fix imports

* remove more | from type hints

* run isort

* fix linting

* update doc strings

* add has_results property

* format class variables

* fix tests for nextset
  • Loading branch information
ajshedivy authored Sep 9, 2024
1 parent 4d9c4b2 commit 7d34625
Show file tree
Hide file tree
Showing 33 changed files with 1,627 additions and 303 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ["3.8", "3.10"]
python: ["3.9", "3.10"]
task:
- name: Test
run: |
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ doc/_build/
*.pem
local_test/
local_test.py

# local test files
_*

# keep __init__.py files
!__*

# python

*.pyc
Expand Down
14 changes: 7 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ repos:
types: [python]
exclude: ^tests/

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.9.0
hooks:
- id: mypy
types: [python]
additional_dependencies: [ dataclasses-json>=0.6.4, websocket-client>=1.2.1]
exclude: '^tests/'
# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v1.9.0
# hooks:
# - id: mypy
# types: [python]
# additional_dependencies: [ dataclasses-json>=0.6.4, websocket-client>=1.2.1]
# exclude: '^tests/'

- repo: https://github.com/ambv/black
rev: 24.4.0
Expand Down
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
- Add PEP 249 support
- add pep249abc dependency

## [v0.1.5](https://github.com/Mapepire-IBMi/mapepire-python/releases/tag/v0.1.5) - 2024-08-30
-- add cl tests
- add query manager
- add cl tests
- add query manager

## TP1
- Add query manager
Expand Down
92 changes: 84 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
- [Setup](#setup)
- [Install with `pip`](#install-with-pip)
- [Server Component Setup](#server-component-setup)
- [Example usage](#example-usage)
- [Usage](#usage)
- [1. Using the `SQLJob` object to run queries synchronously](#1-using-the-sqljob-object-to-run-queries-synchronously)
- [Query and run](#query-and-run)
- [Asynchronous Query Execution](#asynchronous-query-execution)
- [Pooling (beta)](#pooling-beta)
- [2. Using the `PoolJob` object to run queries asynchronously](#2-using-the-pooljob-object-to-run-queries-asynchronously)
- [3. Using the `Pool` object to run queries "concurrently"](#3-using-the-pool-object-to-run-queries-concurrently)
- [4. Using PEP 249 Implementation](#4-using-pep-249-implementation)
- [`fetchmany()` and `fetchall()` methods](#fetchmany-and-fetchall-methods)
- [PEP 249 Asynchronous Implementation](#pep-249-asynchronous-implementation)
- [Development Setup](#development-setup)
- [Setup python virtual environment with pip and venv](#setup-python-virtual-environment-with-pip-and-venv)
- [Create a new virtual environment](#create-a-new-virtual-environment)
Expand Down Expand Up @@ -69,9 +73,15 @@ pip install mapepire-python
### Server Component Setup
To use mapire-python, you will need to have the Mapepire Server Component running on your IBM i server. Follow these instructions to set up the server component: [Mapepire Server Installation](https://mapepire-ibmi.github.io/guides/sysadmin/)

## Example usage
# Usage

Setup the server credentials used to connect to the server. One way to do this is to create a `mapepire.ini` file in the root of your project with the following content:
There are four main ways to run queries using `mapepire-python`:
1. Using the `SQLJob` object to run queries synchronously
2. Using the `PoolJob` object to run queries asynchronously
3. Using the `Pool` object to run queries "concurrently"
4. Using PEP 249 Implementation

All of these methods require the `DaemonServer` object for connecting to the server. One way to set up the `DaemonServer` is to create a `mapepire.ini` file in the root of your project with the following content:

```ini
[mapepire]
Expand All @@ -81,7 +91,8 @@ USER="USER"
PASSWORD="PASSWORD"
```

The following script sets up a `DaemonServer` object that will be used to connect with the Server Component. Then a single `SQLJob` is created to facilitate the connection from the client side.

## 1. Using the `SQLJob` object to run queries synchronously

```python
import configparser
Expand Down Expand Up @@ -262,7 +273,7 @@ with SQLJob(creds) as sql_job:
print(result)
```

### Asynchronous Query Execution
## 2. Using the `PoolJob` object to run queries asynchronously

The `PoolJob` object can be used to create and run queries asynchronously:

Expand Down Expand Up @@ -323,7 +334,7 @@ if __name__ == '__main__':
```


## Pooling (beta)
## 3. Using the `Pool` object to run queries "concurrently"

The `Pool` object can be used to create a pool of `PoolJob` objects to run queries concurrently.

Expand Down Expand Up @@ -375,6 +386,71 @@ This script will create a pool of 3 `PoolJob` objects and run the query `values
['004460/QUSER/QZDASOINIT', '005096/QUSER/QZDASOINIT', '005319/QUSER/QZDASOINIT']
```

## 4. Using PEP 249 Implementation

PEP 249 is the Python Database API Specification v2.0. The `mapepire-python` client provides a PEP 249 implementation that allows you to use the `Connection` and `Cursor` objects to interact with the Mapepire server. The same `DaemonServer` object is used by the PEP 249 implementation to connect to the server.

```python
import configparser
from mapepire_python import connect
from mapepire_python.data_types import DaemonServer

config = configparser.ConfigParser()
config.read('mapepire.ini')

creds = DaemonServer(
host=config['mapepire']['SERVER'],
port=config['mapepire']['PORT'],
user=config['mapepire']['USER'],
password=config['mapepire']['PASSWORD'],
ignoreUnauthorized=True
)

with connect(creds) as conn:
with conn.execute("select * from sample.employee") as cursor:
result = cursor.fetchone()
print(result)
```

### `fetchmany()` and `fetchall()` methods

The `Cursor` object provides the `fetchmany()` and `fetchall()` methods to fetch multiple rows from the result set:

```python

with connect(creds) as conn:
with conn.execute("select * from sample.employee") as cursor:
results = cursor.fetchmany(size=2)
print(results)
```
---

```python

with connect(creds) as conn:
with conn.execute("select * from sample.employee") as cursor:
results = cursor.fetchall()
print(results)
```

## PEP 249 Asynchronous Implementation

The PEP 249 implementation also provides an asynchronous interface for running queries. The `connect` function returns an asynchronous context manager that can be used with the `async with` statement:

```python
import asyncio
from mapepire_python.asycnio import connect

async def main():
async with connect(creds) as conn:
async with await conn.execute("select * from sample.employee") as cursor:
result = await cursor.fetchone()
print(result)

if __name__ == '__main__':
asyncio.run(main())
```


# Development Setup

Expand Down
1 change: 1 addition & 0 deletions environment-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ dependencies:
- websocket-client>=1.2.1
- pyee
- websockets
- pep249abc
46 changes: 46 additions & 0 deletions mapepire_python/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from typing import Any, Dict, Union

from .core import (
Connection,
Cursor,
DatabaseError,
DataError,
Error,
IntegrityError,
InterfaceError,
InternalError,
NotSupportedError,
OperationalError,
ProgrammingError,
)
from .data_types import DaemonServer

__all__ = [
"apilevel",
"threadsafety",
"paramstyle",
"DatabaseError",
"DataError",
"Error",
"InterfaceError",
"IntegrityError",
"InternalError",
"NotSupportedError",
"OperationalError",
"ProgrammingError",
"CONNECTION_CLOSED",
"convert_runtime_errors",
"connect",
"Connection",
"Cursor",
]

# pylint: disable=invalid-name
apilevel = "2.0"
threadsafety = 1
paramstyle = "qmark"


def connect(connection_details: Union[DaemonServer, dict], opts: Dict[str, Any] = {}) -> Connection:
"""Connect to a Mapepire Server, returning a connection."""
return Connection(connection_details, opts)
46 changes: 46 additions & 0 deletions mapepire_python/asyncio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from typing import Any, Dict, Union

from ..asyncio.connection import AsyncConnection
from ..core.exceptions import (
DatabaseError,
DataError,
Error,
IntegrityError,
InterfaceError,
InternalError,
NotSupportedError,
OperationalError,
ProgrammingError,
)
from ..data_types import DaemonServer
from .cursor import AsyncCursor

__all__ = [
"apilevel",
"threadsafety",
"paramstyle",
"connect",
"AsyncConnection",
"AsyncCursor",
"Error",
"InterfaceError",
"DatabaseError",
"DataError",
"IntegrityError",
"InternalError",
"NotSupportedError",
"OperationalError",
"ProgrammingError",
]

# pylint: disable=invalid-name
apilevel = "2.0"
threadsafety = 1
paramstyle = "qmark"


def connect(
connection_details: Union[DaemonServer, dict], opts: Dict[str, Any] = {}
) -> AsyncConnection:
"""Connect to a Mapepire Server, returning a connection."""
return AsyncConnection(connection_details, opts)
81 changes: 81 additions & 0 deletions mapepire_python/asyncio/connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from typing import Optional, Sequence, Union

from pep249 import aiopep249
from pep249.aiopep249 import ProcArgs, ProcName, QueryParameters, SQLQuery

from ..core.connection import Connection
from ..data_types import DaemonServer
from .cursor import AsyncCursor
from .utils import to_thread


class AsyncConnection(aiopep249.AsyncCursorExecuteMixin, aiopep249.AsyncConnection):
"""
A DB API 2.0 compliant async connection for Mapepire, as outlined in
PEP 249.
Can be constructed by passing a connection details object as a dict,
or a `DaemonServer` object:
```
import asyncio
from mapepire_python.asyncio import connect
from mapepire_python.data_types import DaemonServer
creds = DaemonServer(
host=SERVER,
port=PORT,
user=USER,
password=PASS,
ignoreUnauthorized=True
)
>>> async def main():
... async with connect(creds) as conn:
... async with await conn.execute("select * from sample.employee") as cur:
... print(await cur.fetchone())
>>> if __name__ == '__main__':
... asyncio.run(main())
```
"""

def __init__(self, database: Union[DaemonServer, dict], opts={}) -> None:
super().__init__()
self._connection = Connection(database, opts=opts)

async def cursor(self) -> AsyncCursor:
return AsyncCursor(self, self._connection.cursor())

async def close(self) -> None:
await to_thread(self._connection.close)

async def execute(
self, operation: SQLQuery, parameters: Optional[QueryParameters] = None
) -> AsyncCursor:
cursor = await self.cursor()
return await cursor.execute(operation, parameters)

async def executemany(
self, operation: SQLQuery, seq_of_parameters: Sequence[QueryParameters]
) -> AsyncCursor:
cursor = await self.cursor()
return await cursor.executemany(operation, seq_of_parameters)

async def callproc(
self, procname: ProcName, parameters: Optional[ProcArgs] = None
) -> Optional[ProcArgs]:
cursor = await self.cursor()
return await cursor.callproc(procname, parameters)

async def executescript(self, script: SQLQuery) -> AsyncCursor:
"""A lazy implementation of SQLite's `executescript`."""
return await self.execute(script)

async def commit(self) -> None:
to_thread(self._connection.commit)

async def rollback(self) -> None:
to_thread(self._connection.rollback)
Loading

0 comments on commit 7d34625

Please sign in to comment.