Skip to content

Commit

Permalink
Import from Subversion 2.0.63; reworked versioning
Browse files Browse the repository at this point in the history
  • Loading branch information
mkleehammer committed Oct 7, 2008
0 parents commit c3f6b46
Show file tree
Hide file tree
Showing 37 changed files with 11,386 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
setup.cfg
MANIFEST
build
dist
*.pdb
*.pyc
*.pyo
tmp
web/*.cmd
13 changes: 13 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
include src\*.h
include src\*.cpp
include tests\*
include README.txt
prune setup.cfg

include web\*
prune web\*.cmd

# For some reason, I keep getting setup.PY. Probably
# because I use PATHEXT on Windows.
prune setup.PY
include setup.py
143 changes: 143 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@

Overview
========

This project is a Python database module for ODBC that implements the Python DB API 2.0
specification.

homepage: http://sourceforge.net/projects/pyodbc
source: http://github.com/mkleehammer/pyodbc

This module requires:

* Python 2.4 or greater
* ODBC 3.0 or greater

On Windows, the easiest way to install is to use the Windows installer program available at
http://sourceforge.net/projects/pyodbc.

Source can be obtained at

To build from source, either check the source out of version control or download a source
extract and run:

python setup.py build install

Module Specific Behavior
=======================

General
-------

* The pyodbc.connect function accepts a single parameter: the ODBC connection string. This
string is not read or modified by pyodbc, so consult the ODBC documentation or your ODBC
driver's documentation for details. The general format is:

cnxn = pyodbc.connect('DSN=mydsn;UID=userid;PWD=pwd')

* Connection caching in the ODBC driver manager is automatically enabled.

* Autocommit is not supported. Always call cnxn.commit() since the DB API specification
requires a rollback when a connection is closed that was not specifically committed.

* When a connection is closed, all cursors created from the connection are closed.


Data Types
----------

* Dates, times, and timestamps use the Python datetime module's date, time, and datetime
classes. These classes can be passed directly as parameters and will be returned when
querying date/time columns.

* Binary data is passed and returned in Python buffer objects.

* Decimal and numeric columns are passed and returned using the Python 2.4 decimal class.


Convenience Methods
-------------------

* Cursors are iterable and returns Row objects.

cursor.execute("select a,b from tmp")
for row in cursor:
print row


* The DB API PEP does not specify the return type for Cursor.execute, so pyodbc tries to be
maximally convenient:

1) If a SELECT is executed, the Cursor itself is returned to allow code like the following:

for row in cursor.execute("select a,b from tmp"):
print row

2) If an UPDATE, INSERT, or DELETE statement is issued, the number of rows affected is
returned:

count = cursor.execute("delete from tmp where a in (1,2,3)")

3) Otherwise (CREATE TABLE, etc.), None is returned.


* An execute method has been added to the Connection class. It creates a Cursor and returns
whatever Cursor.execute returns. This allows for the following:

for row in cnxn.execute("select a,b from tmp"):
print row

or

rows = cnxn.execute("select * from tmp where a in (1,2,3)").fetchall()

Since each call creates a new Cursor, only use this when executing a single statement.


* Both Cursor.execute and Connection.execute allow parameters to be passed as additional
parameters following the query.

cnxn.execute("select a,b from tmp where a=? or a=?", 1, 2)

The specification is not entirely clear, but most other drivers require parameters to be
passed in a sequence. To ensure compatibility, pyodbc will also accept this format:

cnxn.execute("select a,b from tmp where a=? or a=?", (1, 2))


* Row objects are derived from tuple to match the API specification, but they also support
accessing columns by name.

for row in cnxn.execute("select A,b from tmp"):
print row.a, row.b


* The following are not supported or are ignored: nextset, setinputsizes, setoutputsizes.


* Values in Row objects can be replaced, either by name or index. Sometimes it is convenient
to "preprocess" values.

row = cursor.execute("select a,b from tmp").fetchone()

row.a = calc(row.a)
row[1] = calc(row.b)


Goals / Design
==============

* This module should not require any 3rd party modules other than ODBC.

* Only built-in data types should be used where possible.

a) Reduces the number of libraries to learn.

b) Reduces the number of modules and libraries to install.

c) Eventually a standard is usually introduced. For example, many previous database drivers
used the mxDate classes. Now that Python 2.3 has introduced built-in date/time classes,
using those modules is more complicated than using the built-ins.

* It should adhere to the DB API specification, but be maximally convenient where possible.
The most common usages should be optimized for convenience and speed.
165 changes: 165 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#!/usr/bin/python

import sys, os, re
from distutils.core import setup, Command
from distutils.extension import Extension
from distutils.errors import *
from os.path import exists, abspath, dirname, join, isdir

OFFICIAL_BUILD = 9999

def main():

version_str, version = get_version()

files = [ 'pyodbcmodule.cpp', 'cursor.cpp', 'row.cpp', 'connection.cpp', 'buffer.cpp', 'params.cpp', 'errors.cpp', 'getdata.cpp' ]
files = [ join('src', f) for f in files ]
libraries = []

extra_compile_args = None
extra_link_args = None

if os.name == 'nt':
# Windows native
files.append(join('src', 'pyodbc.rc'))
libraries.append('odbc32')
extra_compile_args = [ '/W4' ]

# Add debugging symbols
extra_compile_args = [ '/W4', '/Zi', '/Od' ]
extra_link_args = [ '/DEBUG' ]

elif os.environ.get("OS", '').lower().startswith('windows'):
# Windows Cygwin (posix on windows)
# OS name not windows, but still on Windows
libraries.append('odbc32')

elif sys.platform == 'darwin':
# OS/X now ships with iODBC.
libraries.append('iodbc')

else:
# Other posix-like: Linux, Solaris, etc.
# What is the proper way to detect iODBC, MyODBC, unixODBC, etc.?
libraries.append('odbc')

if exists('MANIFEST'):
os.remove('MANIFEST')

setup (name = "pyodbc",
version = version_str,
description = "DB API Module for ODBC",

long_description = ('A Python DB API 2 module for ODBC. This project provides an up-to-date, '
'convenient interface to ODBC using native data types like datetime and decimal.'),

maintainer = "Michael Kleehammer",
maintainer_email = "[email protected]",

ext_modules = [ Extension('pyodbc', files,
libraries=libraries,
define_macros = [ ('PYODBC_%s' % name, value) for name,value in zip(['MAJOR', 'MINOR', 'MICRO', 'BUILD'], version) ],
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args
) ],

classifiers = [ 'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: MIT License',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Programming Language :: Python',
'Topic :: Database',
],

url = 'http://pyodbc.sourceforge.net',
download_url = 'http://github.com/pyodbc/pyodbc/tree/master')


def get_version():
"""
Returns the version of the product as (description, [major,minor,micro,beta]).
If the release is official, `beta` will be 9999 (OFFICIAL_BUILD).
1. If in a git repository, use the latest tag (git describe).
2. If in an unzipped source directory (from setup.py sdist),
read the version from the PKG-INFO file.
3. Use 2.1.0.0 and complain a lot.
"""
# My goal is to (1) provide accurate tags for official releases but (2) not have to manage tags for every test
# release.
#
# Official versions are tagged using 3 numbers: major, minor, micro. A build of a tagged version should produce
# the version using just these pieces, such as 2.1.4.
#
# Unofficial versions are "working towards" the next version. So the next unofficial build after 2.1.4 would be a
# beta for 2.1.5. Using 'git describe' we can find out how many changes have been made after 2.1.4 and we'll use
# this count as the beta id (beta1, beta2, etc.)
#
# Since the 4 numbers are put into the Windows DLL, we want to make sure the beta versions sort *after* the
# official, so we set the final build number to 9999, but we don't show it.

name = None # branch/feature name. Should be None for official builds.
numbers = None # The 4 integers that make up the version.

# If this is a source release the version will have already been assigned and be in the PKG-INFO file.

name, numbers = _get_version_pkginfo()

# If not a source release, we should be in a git repository. Look for the latest tag.

if not numbers:
name, numbers = _get_version_git()

if not numbers:
print 'WARNING: Unable to determine version. Using 2.1.0.0'
name, numbers = '2.1.0-unsupported', [2,1,0,0]

return name, numbers


def _get_version_pkginfo():
filename = join(dirname(abspath(__file__)), 'PKG-INFO')
if exists(filename):
re_ver = re.compile(r'^Version: \s+ (\d+)\.(\d+)\.(\d+) (?: -beta(\d+))?', re.VERBOSE)
for line in open(filename):
match = re_ver.search(line)
if match:
name = line.split(':', 1)[1].strip()
numbers = [ int(n or 0) for n in match.groups() ]
return name, numbers

return None, None


def _get_version_git():
n, result = getoutput('git describe --tags')
if n:
print 'WARNING: git describe failed with: %s %s' % (n, result)
return None, None

match = re.match(r'(\d+).(\d+).(\d+) (?: -(\d+)-g[0-9a-z]+)?', result, re.VERBOSE)
if not match:
return None, None

numbers = [ int(n or OFFICIAL_BUILD) for n in match.groups() ]
if numbers[-1] == OFFICIAL_BUILD:
name = '%s.%s.%s' % tuple(numbers[:3])
if numbers[-1] != OFFICIAL_BUILD:
# This is a beta of the next micro release, so increment the micro number to reflect this.
numbers[-2] += 1
name = '%s.%s.%s-beta%s' % tuple(numbers)
return name, numbers



def getoutput(cmd):
pipe = os.popen(cmd, 'r')
text = pipe.read().rstrip('\n')
status = pipe.close() or 0
return status, text

if __name__ == '__main__':
main()
58 changes: 58 additions & 0 deletions src/buffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "pyodbc.h"
#include "buffer.h"
#include "pyodbcmodule.h"

Py_ssize_t
PyBuffer_GetMemory(PyObject* buffer, const char** pp)
{
PyBufferProcs* procs = buffer->ob_type->tp_as_buffer;

if (!procs || !PyType_HasFeature(buffer->ob_type, Py_TPFLAGS_HAVE_GETCHARBUFFER))
{
// Can't access the memory directly because the buffer object doesn't support it.
return -1;
}

if (procs->bf_getsegcount(buffer, 0) != 1)
{
// Can't access the memory directly because there is more than one segment.
return -1;
}

#if PY_VERSION_HEX >= 0x02050000
char* pT = 0;
#else
const char* pT = 0;
#endif
Py_ssize_t cb = procs->bf_getcharbuffer(buffer, 0, &pT);

if (pp)
*pp = pT;

return cb;
}

Py_ssize_t
PyBuffer_Size(PyObject* self)
{
if (!PyBuffer_Check(self))
{
PyErr_SetString(PyExc_TypeError, "Not a buffer!");
return 0;
}

Py_ssize_t total_len = 0;
self->ob_type->tp_as_buffer->bf_getsegcount(self, &total_len);
return total_len;
}
Loading

0 comments on commit c3f6b46

Please sign in to comment.