Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Documentation
on:
push:
branches:
- main
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: pip install zensical
- run: zensical build --clean

- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v4
with:
path: site
- uses: actions/deploy-pages@v4
id: deployment
5 changes: 4 additions & 1 deletion .github/workflows/npyodbc_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ jobs:
run: |
pip install 'numpy<2'

# Ensure we are testing with npyodbc compiled against numpy>=2
# Ensure we are testing with npyodbc compiled against numpy>=2.
# Run this somewhere besides the current working directory, because python will
# inadvertently try to import the local npyodbc code which doesn't have the compiled
# extension otherwise.
python -c 'import npyodbc; assert npyodbc.numpy_abi_version >= 0x2000000'
pytest test_numpy.py
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ db.sqlite
.gdb_history

.hypothesis/

site/
173 changes: 173 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Getting Started

## Background

`npyodbc` builds on top of [`pyodbc`](https://github.com/mkleehammer/pyodbc),
extending it to write directly to [NumPy](https://numpy.org) arrays without
passing through Python object types. This yields substantial performance
benefits, but it comes at the cost of a slightly more complex build system. We
use `pyodbc` as a subproject within the `npyodbc` project, and leverage
[`meson`](https://mesonbuild.com) as the build system to help patch and extend
`pyodbc`. With the help of `meson` we can use the latest release of `pyodbc` and
extend it to include returning result sets from an ODBC compliant SQL server as
NumPy arrays.

Documentation for `pyodbc` can be found on their
[wiki](https://github.com/mkleehammer/pyodbc/wiki). We will only include
components of that wiki that directly relate to how to use `npyodbc`. For
advanced `pyodbc` usage, please refer to the `pyodbc` wiki.

## Introduction

All example code will assume you have Microsoft SQL 2022 running locally in a
Docker container. Start the npyodbc Docker container by navigating to
`./containers/` and executing the following command in your
terminal:

```bash
# Build the Docker image and tag it as npyodbc docker
build . --tag npyodbc

# Start a container using the npyodbc image and name it npyodbc as well docker
run -p 1401:1433 --name npyodbc --hostname npyodbc -m 16GB -d npyodbc

# (OPTIONAL) Log into the container docker exec -it npyodbc bash
```

If you log into the container, you can access the SQL 2022 command line by
navigating to it and running the `sqlcmd` command. Below we include the user
name and password for logging into the SQL 2022 server.

```bash
# Navigate to the SQL bin directory cd
/opt/mssql-tools/bin

# Open the SQL command line utility ./sqlcmd -U SA -P StrongPassword2022! -S localhost
```

You can now add a test table manually here that you can access using `npyodbc`.

```sql
DROP TABLE test;
CREATE TABLE test (columnA VARBINARY(20), columnB VARBINARY(20));
INSERT INTO test VALUES(CAST('zort' AS VARBINARY(20)), CAST('troz' AS VARBINARY(20)));
INSERT INTO test VALUES(CAST('poit' AS VARBINARY(20)), CAST('rubber pants' AS VARBINARY(20)));
GO;
```

You must supply the `GO` command otherwise the commands will not get executed by
SQL 2022. The `QUIT` command will exit out of `sqlcmd` if you so wish to do so.

## Creating a Table With `npyodbc`

```python
import npyodbc

driver = "ODBC Driver 17 for SQL Server"
server = "localhost,1401"
uid = "SA"
# NOTE: This is not secure and should not be used only for testing purposes.
pwd = "StrongPassword2022!"
connection_string = f"DRIVER={driver};SERVER={server};UID={uid};PWD={pwd}"
# Connect to the database running in Docker, or in the VSCode devcontainer.
connection = npyodbc.connect(connection_string)

# Create a test table.
with connection as conn:
try:
conn.execute("DROP TABLE test;")
except ProgrammingError:
print("Table `test` already exists.")
with connection as conn:
conn.execute("CREATE TABLE test (columnA VARBINARY(20), columnB VARBINARY(20));")

# Add data to the test table.
with connection as conn:
conn.execute(
"INSERT INTO test "
"VALUES(CAST('zort' AS VARBINARY(20)), CAST('troz' AS VARBINARY(20)));"
)
conn.execute(
"INSERT INTO test "
"VALUES(CAST('poit' AS VARBINARY(20)), CAST('rubber pants' AS VARBINARY(20)));"
)

# Retrieve the data from the test table.
connection.execute("SELECT * FROM test;").fetchall()
```

We have used the `with` context in Python for executing commands to the connected
database. If you want, you can also create a cursor with the statement to execute, and
then commit the command to the connection, example below.

```python
import npyodbc

driver = "ODBC Driver 17 for SQL Server"
server = "localhost,1401"
uid = "SA"
# NOTE: This is not secure and should not be used only for testing purposes.
pwd = "StrongPassword2022!"
connection_string = f"DRIVER={driver};SERVER={server};UID={uid};PWD={pwd}"
# Connect to the database running in Docker, or in the VSCode devcontainer.
connection = npyodbc.connect(connection_string)

# Create a cursor
cursor = connection.cursor()
sql = "CREATE TABLE test (columnA VARBINARY(20), columnB VARBINARY(20));"
cursor.execute(sql)
connection.commit()
```

## Creating a Table Using `sqlcmd`

If instead you want to use `sqlcmd` to create the table, you can do so. You will
need to log into Docker container, or use the terminal in the VSCode
devcontainer.

```bash
# Change directory to the tool
cd /opt/mssql-tools/bin
# Start the sqlcmd tool
./sqlcmd -S localhost -U SA -P StrongPassword2022!
```

Below we will create a test table and add data to it.

```sql
1> CREATE TABLE test (columnA VARBINARY(20), columnB VARBINARY(20));
2> INSERT INTO test VALUES(CAST('zort' AS VARBINARY(20)), CAST('troz' AS VARBINARY(20)));
3> INSERT INTO test VALUES(CAST('poit' AS VARBINARY(20)), CAST('rubber pants' AS VARBINARY(20)));
4> GO;
```

Finally we can query the table.

```sql
1> SELECT * FROM test;
```

```bash
columnA columnB
------------------------------------------ ------------------------------------------
0x7A6F7274 0x74726F7A
0x706F6974 0x7275626265722070616E7473

```

Note that what is returned is `BINARY`. If you want the string representation of what is
in the table, you need to convert it.

```sql
1> SELECT CONVERT(VARCHAR(20), columnA) AS columnA,
2> CONVERT(VARCHAR(20), columnB) AS columnB
3> FROM test;
2> GO;
```

```bash
columnA columnB
-------------------- --------------------
zort troz
poit rubber pants
```
125 changes: 125 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
icon: lucide/rocket
---

# `npyodbc`

`npyodbc` is a python interface to Open Database Connectivity (ODBC) drivers
built on top of [`pyodbc`](https://github.com/mkleehammer/pyodbc) that
incorporates native support for `numpy`.

## Features

- Native support for `numpy` arrays
- Compatible with many ODBC drivers
- Can be used anywhere `pyodbc` is used

## Quickstart

### Requirements

You'll need a database and an ODBC driver to use `npyodbc`. There are many
different options:

* Google BigQuery
* Hive from Ubuntu/Debian
* Microsoft Access
* Microsoft Excel
* Microsoft SQL Server
* MySQL
* Netezza
* Oracle
* PostgreSQL
* SQLite
* Teradata
* Vertica

Install the appropriate ODBC driver for the database you want to connect to
before continuing.

### Installation

You can install `npyodbc` via pip:

```sh
pip install npyodbc
```

If you've cloned this repository and want to develop `npyodbc`,

```sh
pip install -e '.[dev,test]'
```

### Usage

Let's set up a containerized database as an example (you'll need `docker` before
we begin):

1. Create a `docker-compose.yml` containing the following:
```docker-compose
services:
postgres:
image: postgres:11
environment:
- POSTGRES_DB=postgres_db
- POSTGRES_USER=postgres_user
- POSTGRES_PASSWORD=postgres_pwd
- POSTGRES_HOST_AUTH_METHOD=trust

ports:
- "5432:5432"
```

2. Start the container: `docker compose up --build`
3. Configure your ODBC driver by setting `/etc/odbc.ini`:
```ini
[PostgreSQL Unicode]
Description = PostgreSQL connection to database
Driver = PostgreSQL Unicode
Servername = localhost
Port = 5432
Database = postgres_db
Username = postgres_user
Password = postgres_pwd
```
We also need to configure `/etc/odbcinst.ini`:
```ini
[PostgreSQL Unicode]
Description = PostgreSQL ODBC driver (Unicode)
Driver = /usr/lib/psqlodbcw.so
```
Now your system is ready to connect.
3. Create a database to connect to:
```bash
# Set your environment to match the settings in the container
export PGHOST=localhost
export PGPORT=5432
export PGDATABASE=postgres_db
export PGUSER=postgres_user
export PGPASSWORD=postgres_pwd

# Create a new database
psql -c "CREATE DATABASE test WITH encoding='UTF8' LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8'"
```
4. Connect with `npyodbc`:
```python
import npyodbc

# Set the connection string to match the settings in your `odbc.ini` and `odbcinst.ini`
connection = npyodbc.connect(
"DRIVER={PostgreSQL Unicode};SERVER=localhost;PORT=5432;UID=postgres_user;PWD=postgres_pwd;DATABASE=test"
)

cursor = connection.cursor()

# Create a table and insert some values
cursor.execute('create table t1(col text)')
cursor.execute('insert into t1 values (?)', 'a test string')

# Retrieve entries from the table as Python objects
rows = cursor.execute('select * from t1').fetchall()

# Returns [('a test string',)]
print(rows)
```
8 changes: 8 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# API Reference

Since `npyodbc` builds on top of `pyodbc`, this reference contains only
documentation about features introduced by `npyodbc`. Consult the [`pyodbc`
wiki](https://github.com/mkleehammer/pyodbc/wiki) for more information about
base `pyodbc` features.

::: npyodbc
8 changes: 8 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ deps = []
cflags = []
lflags = []

python.install_sources(
[
'npyodbc/__init__.py',
'npyodbc/__init__.pyi',
'npyodbc/_npyodbc.pyi',
],
subdir: 'npyodbc',
)

# Build the npyodbc module
python.extension_module(
Expand Down
Loading
Loading