Full Code of kennethreitz/records for AI

master ea4273695cee cached
21 files
42.4 KB
10.8k tokens
98 symbols
1 requests
Download .txt
Repository: kennethreitz/records
Branch: master
Commit: ea4273695cee
Files: 21
Total size: 42.4 KB

Directory structure:
gitextract_bs22qwwk/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .travis.yml
├── HISTORY.rst
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── README.rst
├── examples/
│   └── randomuser-sqlite.py
├── records.py
├── requirements.txt
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_105.py
│   ├── test_69.py
│   ├── test_records.py
│   └── test_transactions.py
└── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
github: kennethreitz


================================================
FILE: .github/workflows/ci.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: pytest

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]

    steps:
    - name: Checkout repository
      uses: actions/checkout@v3

    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}


    - name: Install dependencies
      run: pip install -r requirements.txt

    - name: Test with pytest
      run: pytest


================================================
FILE: .gitignore
================================================
.env


================================================
FILE: .travis.yml
================================================
sudo: false
language: python
python:
  - "2.7"
  - "3.4"
  - "3.5"
  - "3.6"
install: pip install tox-travis
script: tox


================================================
FILE: HISTORY.rst
================================================
v0.6.0 (04-29-2024)
===================

- Support for Python 3.6+ only.
- Support for SQLAlchemy 2+.
- Dropped support for Python 2.7 and 3.4, with the move to SQLAlchemy 2+.

v0.5.1 (09-01-2017)
===================

- Depend on ``tablib[pandas]``.
- Support for Bulk quies: ``Database.bulk_query()`` & ``Database.bulk_query_file()``.

v0.5.0 (11-15-2016)
===================

- Support for transactions: ``t = Database.transaction(); t.commit()``


v0.4.3 (02-16-2016)
===================

- The cake is a lie.

v0.4.2 (02-15-2016)
===================

- Packaging fix.

v0.4.1 (02-15-2016)
===================

- Bugfix for Python 3.

v0.4.0 (02-13-2016)
===================

- Refactored to be fully powered by SQLAlchemy!
- Support for all major databases (thanks, SQLAlchemy!).
- Support for non-alphanumeric column names.
- New ``Record`` class, for representing/accessing result rows.
- ``ResultSet`` renamed ``RecordCollection``.
- Removed Interactive Mode from the CLI.


v0.3.0 (02-11-2016)
===================

- New ``record`` command-line tool available!
- Various improvements.

v0.2.0 (02-10-2016)
===================

- Results are now represented as `Record`, a namedtuples class with dict-like qualities.
- New `ResultSet.export` method, for exporting to various formats.
- Slicing a `ResultSet` now works, and results in a new `ResultSet`.
- Lots of bugfixes and improvements!

v0.1.0 (02-07-2016)
===================

- Initial release.


================================================
FILE: LICENSE
================================================
ISC License

Copyright (c) 2016, Kenneth Reitz <me@kennethreitz.org>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


================================================
FILE: MANIFEST.in
================================================
include README.rst LICENSE HISTORY.rst requirements.txt

================================================
FILE: Makefile
================================================
testall:
	tox
test: init
	pipenv run pytest tests
init:
	pipenv install --skip-lock --dev
publish:
	python setup.py register
	python setup.py sdist upload
	python setup.py bdist_wheel --universal upload
	rm -fr build dist .egg records.egg-info


================================================
FILE: README.md
================================================
# Records: SQL for Humans™

[![image](https://img.shields.io/pypi/v/records.svg)](https://pypi.python.org/pypi/records)

**Records is a very simple, but powerful, library for making raw SQL
queries to most relational databases.**

![image](https://farm1.staticflickr.com/569/33085227621_7e8da49b90_k_d.jpg)

Just write SQL. No bells, no whistles. This common task can be
surprisingly difficult with the standard tools available. This library
strives to make this workflow as simple as possible, while providing an
elegant interface to work with your query results.

*Database support includes RedShift, Postgres, MySQL, SQLite, Oracle,
and MS-SQL (drivers not included).*

## ☤ The Basics

We know how to write SQL, so let's send some to our database:

``` python
import records

db = records.Database('postgres://...')
rows = db.query('select * from active_users')    # or db.query_file('sqls/active-users.sql')
```

Grab one row at a time:

``` python
>>> rows[0]
<Record {"username": "model-t", "active": true, "name": "Henry Ford", "user_email": "model-t@gmail.com", "timezone": "2016-02-06 22:28:23.894202"}>
```

Or iterate over them:

``` python
for r in rows:
    print(r.name, r.user_email)
```

Values can be accessed many ways: `row.user_email`, `row['user_email']`,
or `row[3]`.

Fields with non-alphanumeric characters (like spaces) are also fully
supported.

Or store a copy of your record collection for later reference:

``` python
>>> rows.all()
[<Record {"username": ...}>, <Record {"username": ...}>, <Record {"username": ...}>, ...]
```

If you're only expecting one result:

``` python
>>> rows.first()
<Record {"username": ...}>
```

Other options include `rows.as_dict()` and `rows.as_dict(ordered=True)`.

## ☤ Features

-   Iterated rows are cached for future reference.
-   `$DATABASE_URL` environment variable support.
-   Convenience `Database.get_table_names` method.
-   Command-line <span class="title-ref">records</span> tool for
    exporting queries.
-   Safe parameterization:
    `Database.query('life=:everything', everything=42)`.
-   Queries can be passed as strings or filenames, parameters supported.
-   Transactions: `t = Database.transaction(); t.commit()`.
-   Bulk actions: `Database.bulk_query()` &
    `Database.bulk_query_file()`.

Records is proudly powered by [SQLAlchemy](http://www.sqlalchemy.org)
and [Tablib](https://tablib.readthedocs.io/en/latest/).

## ☤ Data Export Functionality

Records also features full Tablib integration, and allows you to export
your results to CSV, XLS, JSON, HTML Tables, YAML, or Pandas DataFrames
with a single line of code. Excellent for sharing data with friends, or
generating reports.

``` pycon
>>> print(rows.dataset)
username|active|name      |user_email       |timezone
--------|------|----------|-----------------|--------------------------
model-t |True  |Henry Ford|model-t@gmail.com|2016-02-06 22:28:23.894202
...
```

**Comma Separated Values (CSV)**

``` pycon
>>> print(rows.export('csv'))
username,active,name,user_email,timezone
model-t,True,Henry Ford,model-t@gmail.com,2016-02-06 22:28:23.894202
...
```

**YAML Ain't Markup Language (YAML)**

``` python
>>> print(rows.export('yaml'))
- {active: true, name: Henry Ford, timezone: '2016-02-06 22:28:23.894202', user_email: model-t@gmail.com, username: model-t}
...
```

**JavaScript Object Notation (JSON)**

``` python
>>> print(rows.export('json'))
[{"username": "model-t", "active": true, "name": "Henry Ford", "user_email": "model-t@gmail.com", "timezone": "2016-02-06 22:28:23.894202"}, ...]
```

**Microsoft Excel (xls, xlsx)**

``` python
with open('report.xls', 'wb') as f:
    f.write(rows.export('xls'))
```

**Pandas DataFrame**

``` python
>>> rows.export('df')
    username  active       name        user_email                   timezone
0    model-t    True Henry Ford model-t@gmail.com 2016-02-06 22:28:23.894202
```

You get the point. All other features of Tablib are also available, so
you can sort results, add/remove columns/rows, remove duplicates,
transpose the table, add separators, slice data by column, and more.

See the [Tablib Documentation](https://tablib.readthedocs.io/) for more
details.

## ☤ Installation

Of course, the recommended installation method is
[pipenv](http://pipenv.org):

    $ pipenv install records[pandas]
    ✨🍰✨

## ☤ Thank You

Thanks for checking this library out! I hope you find it useful.

Of course, there's always room for improvement. Feel free to [open an
issue](https://github.com/kennethreitz/records/issues) so we can make
Records better, stronger, faster.

--------------

[![Star History Chart](https://api.star-history.com/svg?repos=kennethreitz/records&type=Date)](https://star-history.com/#kennethreitz/records&Date)


================================================
FILE: README.rst
================================================
Records: SQL for Humans™
========================


.. image:: https://img.shields.io/pypi/v/records.svg
    :target: https://pypi.python.org/pypi/records


**Records is a very simple, but powerful, library for making raw SQL queries
to most relational databases.**

.. image:: https://farm1.staticflickr.com/569/33085227621_7e8da49b90_k_d.jpg

Just write SQL. No bells, no whistles. This common task can be
surprisingly difficult with the standard tools available.
This library strives to make this workflow as simple as possible,
while providing an elegant interface to work with your query results.

*Database support includes RedShift, Postgres, MySQL, SQLite, Oracle, and MS-SQL (drivers not included).*

☤ The Basics
------------

We know how to write SQL, so let's send some to our database:

.. code:: python

    import records

    db = records.Database('postgres://...')
    rows = db.query('select * from active_users')    # or db.query_file('sqls/active-users.sql')


Grab one row at a time:

.. code:: python

    >>> rows[0]
    <Record {"username": "model-t", "active": true, "name": "Henry Ford", "user_email": "model-t@gmail.com", "timezone": "2016-02-06 22:28:23.894202"}>

Or iterate over them:

.. code:: python

    for r in rows:
        print(r.name, r.user_email)

Values can be accessed many ways: ``row.user_email``, ``row['user_email']``, or ``row[3]``.

Fields with non-alphanumeric characters (like spaces) are also fully supported.

Or store a copy of your record collection for later reference:

.. code:: python

    >>> rows.all()
    [<Record {"username": ...}>, <Record {"username": ...}>, <Record {"username": ...}>, ...]

If you're only expecting one result:

.. code:: python

    >>> rows.first()
    <Record {"username": ...}>

Other options include ``rows.as_dict()`` and ``rows.as_dict(ordered=True)``.

☤ Features
----------

- Iterated rows are cached for future reference.
- ``$DATABASE_URL`` environment variable support.
- Convenience ``Database.get_table_names`` method.
- Command-line `records` tool for exporting queries.
- Safe parameterization: ``Database.query('life=:everything', everything=42)``.
- Queries can be passed as strings or filenames, parameters supported.
- Transactions: ``t = Database.transaction(); t.commit()``.
- Bulk actions: ``Database.bulk_query()`` & ``Database.bulk_query_file()``.

Records is proudly powered by `SQLAlchemy <http://www.sqlalchemy.org>`_
and `Tablib <https://tablib.readthedocs.io/en/latest/>`_.

☤ Data Export Functionality
---------------------------

Records also features full Tablib integration, and allows you to export
your results to CSV, XLS, JSON, HTML Tables, YAML, or Pandas DataFrames with a single line of code.
Excellent for sharing data with friends, or generating reports.

.. code:: pycon

    >>> print(rows.dataset)
    username|active|name      |user_email       |timezone
    --------|------|----------|-----------------|--------------------------
    model-t |True  |Henry Ford|model-t@gmail.com|2016-02-06 22:28:23.894202
    ...

**Comma Separated Values (CSV)**

.. code:: pycon

    >>> print(rows.export('csv'))
    username,active,name,user_email,timezone
    model-t,True,Henry Ford,model-t@gmail.com,2016-02-06 22:28:23.894202
    ...

**YAML Ain't Markup Language (YAML)**

.. code:: python

    >>> print(rows.export('yaml'))
    - {active: true, name: Henry Ford, timezone: '2016-02-06 22:28:23.894202', user_email: model-t@gmail.com, username: model-t}
    ...

**JavaScript Object Notation (JSON)**

.. code:: python

    >>> print(rows.export('json'))
    [{"username": "model-t", "active": true, "name": "Henry Ford", "user_email": "model-t@gmail.com", "timezone": "2016-02-06 22:28:23.894202"}, ...]

**Microsoft Excel (xls, xlsx)**

.. code:: python

    with open('report.xls', 'wb') as f:
        f.write(rows.export('xls'))
        
        
**Pandas DataFrame**

.. code:: python

    >>> rows.export('df')
        username  active       name        user_email                   timezone
    0    model-t    True Henry Ford model-t@gmail.com 2016-02-06 22:28:23.894202

You get the point. All other features of Tablib are also available,
so you can sort results, add/remove columns/rows, remove duplicates,
transpose the table, add separators, slice data by column, and more.

See the `Tablib Documentation <https://tablib.readthedocs.io/>`_ for more details.

☤ Installation
--------------

Of course, the recommended installation method is `pipenv <http://pipenv.org>`_::

    $ pipenv install records[pandas]
    ✨🍰✨

☤ Thank You
-----------

Thanks for checking this library out! I hope you find it useful.

Of course, there's always room for improvement. Feel free to `open an issue <https://github.com/kennethreitz/records/issues>`_ so we can make Records better, stronger, faster.




================================================
FILE: examples/randomuser-sqlite.py
================================================
#!/usr/bin/env python3
# coding: utf-8

import json
import requests
import records

# Fetch random user data from randomuser.me API
response = requests.get('http://api.randomuser.me/0.6/?nat=us&results=10')
user_data = response.json()['results']

# Database connection string
DATABASE_URL = 'sqlite:///users.db'

# Initialize the database
db = records.Database(DATABASE_URL)

# Create the 'persons' table
db.query('DROP TABLE IF EXISTS persons')
db.query('CREATE TABLE persons (key INTEGER PRIMARY KEY, fname TEXT, lname TEXT, email TEXT)')

# Insert user data into the 'persons' table
for record in user_data:
    user = record['user']
    key = user['registered']
    fname = user['name']['first']
    lname = user['name']['last']
    email = user['email']
    db.query(
        'INSERT INTO persons (key, fname, lname, email) VALUES (:key, :fname, :lname, :email)',
        key=key, fname=fname, lname=lname, email=email
    )

# Retrieve and print the contents of the 'persons' table as CSV
rows = db.query('SELECT * FROM persons')
print(rows.export('csv'))


================================================
FILE: records.py
================================================
# -*- coding: utf-8 -*-

import os
from sys import stdout
from collections import OrderedDict
from contextlib import contextmanager
from inspect import isclass

import tablib
from docopt import docopt
from sqlalchemy import create_engine, exc, inspect, text


def isexception(obj):
    """Given an object, return a boolean indicating whether it is an instance
    or subclass of :py:class:`Exception`.
    """
    if isinstance(obj, Exception):
        return True
    if isclass(obj) and issubclass(obj, Exception):
        return True
    return False


class Record(object):
    """A row, from a query, from a database."""

    __slots__ = ("_keys", "_values")

    def __init__(self, keys, values):
        self._keys = keys
        self._values = values

        # Ensure that lengths match properly.
        assert len(self._keys) == len(self._values)

    def keys(self):
        """Returns the list of column names from the query."""
        return self._keys

    def values(self):
        """Returns the list of values from the query."""
        return self._values

    def __repr__(self):
        return "<Record {}>".format(self.export("json")[1:-1])

    def __getitem__(self, key):
        # Support for index-based lookup.
        if isinstance(key, int):
            return self.values()[key]

        # Support for string-based lookup.
        usekeys = self.keys()
        if hasattr(
            usekeys, "_keys"
        ):  # sqlalchemy 2.x uses (result.RMKeyView which has wrapped _keys as list)
            usekeys = usekeys._keys
        if key in usekeys:
            i = usekeys.index(key)
            if usekeys.count(key) > 1:
                raise KeyError("Record contains multiple '{}' fields.".format(key))
            return self.values()[i]

        raise KeyError("Record contains no '{}' field.".format(key))

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError as e:
            raise AttributeError(e)

    def __dir__(self):
        standard = dir(super(Record, self))
        # Merge standard attrs with generated ones (from column names).
        return sorted(standard + [str(k) for k in self.keys()])

    def get(self, key, default=None):
        """Returns the value for a given key, or default."""
        try:
            return self[key]
        except KeyError:
            return default

    def as_dict(self, ordered=False):
        """Returns the row as a dictionary, as ordered."""
        items = zip(self.keys(), self.values())

        return OrderedDict(items) if ordered else dict(items)

    @property
    def dataset(self):
        """A Tablib Dataset containing the row."""
        data = tablib.Dataset()
        data.headers = self.keys()

        row = _reduce_datetimes(self.values())
        data.append(row)

        return data

    def export(self, format, **kwargs):
        """Exports the row to the given format."""
        return self.dataset.export(format, **kwargs)


class RecordCollection(object):
    """A set of excellent Records from a query."""

    def __init__(self, rows):
        self._rows = rows
        self._all_rows = []
        self.pending = True

    def __repr__(self):
        return "<RecordCollection size={} pending={}>".format(len(self), self.pending)

    def __iter__(self):
        """Iterate over all rows, consuming the underlying generator
        only when necessary."""
        i = 0
        while True:
            # Other code may have iterated between yields,
            # so always check the cache.
            if i < len(self):
                yield self[i]
            else:
                # Throws StopIteration when done.
                # Prevent StopIteration bubbling from generator, following https://www.python.org/dev/peps/pep-0479/
                try:
                    yield next(self)
                except StopIteration:
                    return
            i += 1

    def next(self):
        return self.__next__()

    def __next__(self):
        try:
            nextrow = next(self._rows)
            self._all_rows.append(nextrow)
            return nextrow
        except StopIteration:
            self.pending = False
            raise StopIteration("RecordCollection contains no more rows.")

    def __getitem__(self, key):
        is_int = isinstance(key, int)

        # Convert RecordCollection[1] into slice.
        if is_int:
            key = slice(key, key + 1)

        while key.stop is None or len(self) < key.stop:
            try:
                next(self)
            except StopIteration:
                break

        rows = self._all_rows[key]
        if is_int:
            return rows[0]
        else:
            return RecordCollection(iter(rows))

    def __len__(self):
        return len(self._all_rows)

    def export(self, format, **kwargs):
        """Export the RecordCollection to a given format (courtesy of Tablib)."""
        return self.dataset.export(format, **kwargs)

    @property
    def dataset(self):
        """A Tablib Dataset representation of the RecordCollection."""
        # Create a new Tablib Dataset.
        data = tablib.Dataset()

        # If the RecordCollection is empty, just return the empty set
        # Check number of rows by typecasting to list
        if len(list(self)) == 0:
            return data

        # Set the column names as headers on Tablib Dataset.
        first = self[0]

        data.headers = first.keys()
        for row in self.all():
            row = _reduce_datetimes(row.values())
            data.append(row)

        return data

    def all(self, as_dict=False, as_ordereddict=False):
        """Returns a list of all rows for the RecordCollection. If they haven't
        been fetched yet, consume the iterator and cache the results."""

        # By calling list it calls the __iter__ method
        rows = list(self)

        if as_dict:
            return [r.as_dict() for r in rows]
        elif as_ordereddict:
            return [r.as_dict(ordered=True) for r in rows]

        return rows

    def as_dict(self, ordered=False):
        return self.all(as_dict=not (ordered), as_ordereddict=ordered)

    def first(self, default=None, as_dict=False, as_ordereddict=False):
        """Returns a single record for the RecordCollection, or `default`. If
        `default` is an instance or subclass of Exception, then raise it
        instead of returning it."""

        # Try to get a record, or return/raise default.
        try:
            record = self[0]
        except IndexError:
            if isexception(default):
                raise default
            return default

        # Cast and return.
        if as_dict:
            return record.as_dict()
        elif as_ordereddict:
            return record.as_dict(ordered=True)
        else:
            return record

    def one(self, default=None, as_dict=False, as_ordereddict=False):
        """Returns a single record for the RecordCollection, ensuring that it
        is the only record, or returns `default`. If `default` is an instance
        or subclass of Exception, then raise it instead of returning it."""

        # Ensure that we don't have more than one row.
        try:
            self[1]
        except IndexError:
            return self.first(
                default=default, as_dict=as_dict, as_ordereddict=as_ordereddict
            )
        else:
            raise ValueError(
                "RecordCollection contained more than one row. "
                "Expects only one row when using "
                "RecordCollection.one"
            )

    def scalar(self, default=None):
        """Returns the first column of the first row, or `default`."""
        row = self.one()
        return row[0] if row else default


class Database(object):
    """A Database. Encapsulates a url and an SQLAlchemy engine with a pool of
    connections.
    """

    def __init__(self, db_url=None, **kwargs):
        # If no db_url was provided, fallback to $DATABASE_URL.
        self.db_url = db_url or os.environ.get("DATABASE_URL")

        if not self.db_url:
            raise ValueError("You must provide a db_url.")

        # Create an engine.
        self._engine = create_engine(self.db_url, **kwargs)
        self.open = True

    def get_engine(self):
        # Return the engine if open
        if not self.open:
            raise exc.ResourceClosedError("Database closed.")
        return self._engine

    def close(self):
        """Closes the Database."""
        self._engine.dispose()
        self.open = False

    def __enter__(self):
        return self

    def __exit__(self, exc, val, traceback):
        self.close()

    def __repr__(self):
        return "<Database open={}>".format(self.open)

    def get_table_names(self, internal=False, **kwargs):
        """Returns a list of table names for the connected database."""

        # Setup SQLAlchemy for Database inspection.
        return inspect(self._engine).get_table_names(**kwargs)

    def get_connection(self, close_with_result=False):
        """Get a connection to this Database. Connections are retrieved from a
        pool.
        """
        if not self.open:
            raise exc.ResourceClosedError("Database closed.")

        return Connection(self._engine.connect(), close_with_result=close_with_result)

    def query(self, query, fetchall=False, **params):
        """Executes the given SQL query against the Database. Parameters can,
        optionally, be provided. Returns a RecordCollection, which can be
        iterated over to get result rows as dictionaries.
        """
        with self.get_connection(True) as conn:
            return conn.query(query, fetchall, **params)

    def bulk_query(self, query, *multiparams):
        """Bulk insert or update."""

        with self.get_connection() as conn:
            conn.bulk_query(query, *multiparams)

    def query_file(self, path, fetchall=False, **params):
        """Like Database.query, but takes a filename to load a query from."""

        with self.get_connection(True) as conn:
            return conn.query_file(path, fetchall, **params)

    def bulk_query_file(self, path, *multiparams):
        """Like Database.bulk_query, but takes a filename to load a query from."""

        with self.get_connection() as conn:
            conn.bulk_query_file(path, *multiparams)

    @contextmanager
    def transaction(self):
        """A context manager for executing a transaction on this Database."""

        conn = self.get_connection()
        tx = conn.transaction()
        try:
            yield conn
            tx.commit()
        except:
            tx.rollback()
        finally:
            conn.close()


class Connection(object):
    """A Database connection."""

    def __init__(self, connection, close_with_result=False):
        self._conn = connection
        self.open = not connection.closed
        self._close_with_result = close_with_result

    def close(self):
        # No need to close if this connection is used for a single result.
        # The connection will close when the results are all consumed or GCed.
        if not self._close_with_result:
            self._conn.close()
        self.open = False

    def __enter__(self):
        return self

    def __exit__(self, exc, val, traceback):
        self.close()

    def __repr__(self):
        return "<Connection open={}>".format(self.open)

    def query(self, query, fetchall=False, **params):
        """Executes the given SQL query against the connected Database.
        Parameters can, optionally, be provided. Returns a RecordCollection,
        which can be iterated over to get result rows as dictionaries.
        """

        # Execute the given query.
        cursor = self._conn.execute(
            text(query).bindparams(**params)
        )  # TODO: PARAMS GO HERE

        # Row-by-row Record generator.
        row_gen = iter(Record([], []))

        if cursor.returns_rows:
            row_gen = (Record(cursor.keys(), row) for row in cursor)

        # Convert psycopg2 results to RecordCollection.
        results = RecordCollection(row_gen)

        # Fetch all results if desired.
        if fetchall:
            results.all()

        return results

    def bulk_query(self, query, *multiparams):
        """Bulk insert or update."""

        self._conn.execute(text(query), *multiparams)

    def query_file(self, path, fetchall=False, **params):
        """Like Connection.query, but takes a filename to load a query from."""

        # If path doesn't exists
        if not os.path.exists(path):
            raise IOError("File '{}' not found!".format(path))

        # If it's a directory
        if os.path.isdir(path):
            raise IOError("'{}' is a directory!".format(path))

        # Read the given .sql file into memory.
        with open(path) as f:
            query = f.read()

        # Defer processing to self.query method.
        return self.query(query=query, fetchall=fetchall, **params)

    def bulk_query_file(self, path, *multiparams):
        """Like Connection.bulk_query, but takes a filename to load a query
        from.
        """

        # If path doesn't exists
        if not os.path.exists(path):
            raise IOError("File '{}'' not found!".format(path))

        # If it's a directory
        if os.path.isdir(path):
            raise IOError("'{}' is a directory!".format(path))

        # Read the given .sql file into memory.
        with open(path) as f:
            query = f.read()

        self._conn.execute(text(query), *multiparams)

    def transaction(self):
        """Returns a transaction object. Call ``commit`` or ``rollback``
        on the returned object as appropriate."""

        return self._conn.begin()


def _reduce_datetimes(row):
    """Receives a row, converts datetimes to strings."""

    row = list(row)

    for i, element in enumerate(row):
        if hasattr(element, "isoformat"):
            row[i] = element.isoformat()
    return tuple(row)


def cli():
    supported_formats = "csv tsv json yaml html xls xlsx dbf latex ods".split()
    formats_lst = ", ".join(supported_formats)
    cli_docs = """Records: SQL for Humans™
A Kenneth Reitz project.

Usage:
  records <query> [<format>] [<params>...] [--url=<url>]
  records (-h | --help)

Options:
  -h --help     Show this screen.
  --url=<url>   The database URL to use. Defaults to $DATABASE_URL.

Supported Formats:
   %(formats_lst)s

   Note: xls, xlsx, dbf, and ods formats are binary, and should only be
         used with redirected output e.g. '$ records sql xls > sql.xls'.

Query Parameters:
    Query parameters can be specified in key=value format, and injected
    into your query in :key format e.g.:

    $ records 'select * from repos where language ~= :lang' lang=python

Notes:
  - While you may specify a database connection string with --url, records
    will automatically default to the value of $DATABASE_URL, if available.
  - Query is intended to be the path of a SQL file, however a query string
    can be provided instead. Use this feature discernfully; it's dangerous.
  - Records is intended for report-style exports of database queries, and
    has not yet been optimized for extremely large data dumps.
    """ % dict(
        formats_lst=formats_lst
    )

    # Parse the command-line arguments.
    arguments = docopt(cli_docs)

    query = arguments["<query>"]
    params = arguments["<params>"]
    format = arguments.get("<format>")
    if format and "=" in format:
        del arguments["<format>"]
        arguments["<params>"].append(format)
        format = None
    if format and format not in supported_formats:
        print("%s format not supported." % format)
        print("Supported formats are %s." % formats_lst)
        exit(62)

    # Can't send an empty list if params aren't expected.
    try:
        params = dict([i.split("=") for i in params])
    except ValueError:
        print("Parameters must be given in key=value format.")
        exit(64)

    # Be ready to fail on missing packages
    try:
        # Create the Database.
        db = Database(arguments["--url"])

        # Execute the query, if it is a found file.
        if os.path.isfile(query):
            rows = db.query_file(query, **params)

        # Execute the query, if it appears to be a query string.
        elif len(query.split()) > 2:
            rows = db.query(query, **params)

        # Otherwise, say the file wasn't found.
        else:
            print("The given query could not be found.")
            exit(66)

        # Print results in desired format.
        if format:
            content = rows.export(format)
            if isinstance(content, bytes):
                print_bytes(content)
            else:
                print(content)
        else:
            print(rows.dataset)
    except ImportError as impexc:
        print(impexc.msg)
        print("Used database or format require a package, which is missing.")
        print("Try to install missing packages.")
        exit(60)


def print_bytes(content):
    try:
        stdout.buffer.write(content)
    except AttributeError:
        stdout.write(content)


# Run the CLI when executed directly.
if __name__ == "__main__":
    cli()


================================================
FILE: requirements.txt
================================================
-e .[pg]
pytest


================================================
FILE: setup.py
================================================
#!/usr/bin/env python

import io
import os
import sys
from codecs import open
from shutil import rmtree

from setuptools import setup, Command

here = os.path.abspath(os.path.dirname(__file__))
with io.open(os.path.join(here, "README.rst"), encoding="utf-8") as f:
    long_description = "\n" + f.read()


class PublishCommand(Command):
    """Support setup.py publish."""

    description = "Build and publish the package."
    user_options = []

    @staticmethod
    def status(s):
        """Prints things in bold."""
        print("\033[1m{}\033[0m".format(s))

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        try:
            self.status("Removing previous builds...")
            rmtree(os.path.join(here, "dist"))
        except FileNotFoundError:
            pass

        self.status("Building Source and Wheel (universal) distribution...")
        os.system("{} setup.py sdist bdist_wheel --universal".format(sys.executable))

        self.status("Uploading the package to PyPi via Twine...")
        os.system("twine upload dist/*")

        sys.exit()


requires = [
    "SQLAlchemy>=2.0",
    "tablib>=0.11.4",
    "openpyxl>2.6.0",  # https://github.com/kennethreitz-archive/records/pull/184#issuecomment-606207851
    "docopt",
]
version = "0.6.0"


def read(f):
    return open(f, encoding="utf-8").read()


setup(
    name="records",
    version=version,
    description="SQL for Humans",
    long_description=read("README.rst") + "\n\n" + read("HISTORY.rst"),
    author="Kenneth Reitz",
    author_email="me@kennethreitz.org",
    url="https://github.com/kennethreitz/records",
    py_modules=["records"],
    package_data={"": ["LICENSE"]},
    include_package_data=True,
    entry_points={
        "console_scripts": ["records=records:cli"],
    },
    install_requires=requires,
    extras_require={
        "pandas": ["tablib[pandas]"],
        "pg": ["psycopg2-binary"],
        "redshift": ["sqlalchemy-redshift", "psycopg2"],
        # TODO: Add the rest.
    },
    license="ISC",
    zip_safe=False,
    classifiers=(
        "Development Status :: 5 - Production/Stable",
        "Intended Audience :: Developers",
        "Natural Language :: English",
        "License :: OSI Approved :: ISC License (ISCL)",
        "Programming Language :: Python",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.4",
        "Programming Language :: Python :: 3.5",
        "Programming Language :: Python :: 3.6",
        "Programming Language :: Python :: 3.7",
        "Programming Language :: Python :: 3.8",
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
        "Programming Language :: Python :: 3.12",
        "Programming Language :: Python :: Implementation :: CPython",
    ),
    cmdclass={
        "publish": PublishCommand,
    },
)


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/conftest.py
================================================
"""Shared pytest fixtures.

"""

import pytest

import records


@pytest.fixture(
    params=[
        # request: (sql_url_id, sql_url_template)
        ("sqlite_memory", "sqlite:///:memory:"),
        # ('sqlite_file', 'sqlite:///{dbfile}'),
        # ('psql', 'postgresql://records:records@localhost/records')
    ],
    ids=lambda r: r[0],
)
def db(request, tmpdir):
    """Instance of `records.Database(dburl)`

    Ensure, it gets closed after being used in a test or fixture.

    Parametrized with (sql_url_id, sql_url_template) tuple.
    If `sql_url_template` contains `{dbfile}` it is replaced with path to a
    temporary file.

    Feel free to parametrize for other databases and experiment with them.
    """
    id, url = request.param
    # replace {dbfile} in url with temporary db file path
    url = url.format(dbfile=str(tmpdir / "db.sqlite"))
    db = records.Database(url)
    yield db  # providing fixture value for a test case
    # tear_down
    db.close()


@pytest.fixture
def foo_table(db):
    """Database with table `foo` created

    tear_down drops the table.

    Typically applied by `@pytest.mark.usefixtures('foo_table')`
    """
    db.query("CREATE TABLE foo (a integer)")
    yield
    db.query("DROP TABLE foo")


================================================
FILE: tests/test_105.py
================================================
import pytest


@pytest.mark.usefixtures("foo_table")
def test_issue105(db):
    assert db.query("select count(*) as n from foo").scalar() == 0


================================================
FILE: tests/test_69.py
================================================
def test_issue69(db):
    db.query("CREATE table users (id text)")
    db.query("SELECT * FROM users WHERE id = :user", user="Te'ArnaLambert")


================================================
FILE: tests/test_records.py
================================================
from collections import namedtuple

import records

from pytest import raises


IdRecord = namedtuple('IdRecord', 'id')


def check_id(i, row):
    assert row.id == i


class TestRecordCollection:
    def test_iter(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(10))
        for i, row in enumerate(rows):
            check_id(i, row)

    def test_next(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(10))
        for i in range(10):
            check_id(i, next(rows))

    def test_iter_and_next(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(10))
        i = enumerate(iter(rows))
        check_id(*next(i))  # Cache first row.
        next(rows)  # Cache second row.
        check_id(*next(i))  # Read second row from cache.

    def test_multiple_iter(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(10))
        i = enumerate(iter(rows))
        j = enumerate(iter(rows))

        check_id(*next(i))  # Cache first row.

        check_id(*next(j))  # Read first row from cache.
        check_id(*next(j))  # Cache second row.

        check_id(*next(i))  # Read second row from cache.

    def test_slice_iter(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(10))
        for i, row in enumerate(rows[:5]):
            check_id(i, row)
        for i, row in enumerate(rows):
            check_id(i, row)
        assert len(rows) == 10


    # all

    def test_all_returns_a_list_of_records(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(3))
        assert rows.all() == [IdRecord(0), IdRecord(1), IdRecord(2)]


    # first

    def test_first_returns_a_single_record(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(1))
        assert rows.first() == IdRecord(0)

    def test_first_defaults_to_None(self):
        rows = records.RecordCollection(iter([]))
        assert rows.first() is None

    def test_first_default_is_overridable(self):
        rows = records.RecordCollection(iter([]))
        assert rows.first('Cheese') == 'Cheese'

    def test_first_raises_default_if_its_an_exception_subclass(self):
        rows = records.RecordCollection(iter([]))
        class Cheese(Exception): pass
        raises(Cheese, rows.first, Cheese)

    def test_first_raises_default_if_its_an_exception_instance(self):
        rows = records.RecordCollection(iter([]))
        class Cheese(Exception): pass
        raises(Cheese, rows.first, Cheese('cheddar'))

    # one

    def test_one_returns_a_single_record(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(1))
        assert rows.one() == IdRecord(0)

    def test_one_defaults_to_None(self):
        rows = records.RecordCollection(iter([]))
        assert rows.one() is None

    def test_one_default_is_overridable(self):
        rows = records.RecordCollection(iter([]))
        assert rows.one('Cheese') == 'Cheese'

    def test_one_raises_when_more_than_one(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(3))
        raises(ValueError, rows.one)

    def test_one_raises_default_if_its_an_exception_subclass(self):
        rows = records.RecordCollection(iter([]))
        class Cheese(Exception): pass
        raises(Cheese, rows.one, Cheese)

    def test_one_raises_default_if_its_an_exception_instance(self):
        rows = records.RecordCollection(iter([]))
        class Cheese(Exception): pass
        raises(Cheese, rows.one, Cheese('cheddar'))

    # scalar

    def test_scalar_returns_a_single_record(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(1))
        assert rows.scalar() == 0

    def test_scalar_defaults_to_None(self):
        rows = records.RecordCollection(iter([]))
        assert rows.scalar() is None

    def test_scalar_default_is_overridable(self):
        rows = records.RecordCollection(iter([]))
        assert rows.scalar('Kaffe') == 'Kaffe'

    def test_scalar_raises_when_more_than_one(self):
        rows = records.RecordCollection(IdRecord(i) for i in range(3))
        raises(ValueError, rows.scalar)


class TestRecord:

    def test_record_dir(self):
        keys, values = ['id', 'name', 'email'], [1, '', '']
        record = records.Record(keys, values)
        _dir = dir(record)
        for key in keys:
            assert key in _dir
        for key in dir(object):
            assert key in _dir

    def test_record_duplicate_column(self):
        keys, values = ['id', 'name', 'email', 'email'], [1, '', '', '']
        record = records.Record(keys, values)
        with raises(KeyError):
            record['email']


================================================
FILE: tests/test_transactions.py
================================================
"""Test manipulating database table in various transaction scenarios.

Varying conditions:

- db for different database backends (see `db` fixture)
- query run via

    - `db=records.Database(); db.query()
    - `conn=db.get_connection(); conn.query()`

- transaction
    - not used at all
    - used and created in different ways
    - transaction succeeds
    - transaction fails or raise
"""
import pytest


@pytest.mark.usefixtures('foo_table')
def test_plain_db(db):
    """Manipulate database by `db.query` without transactions.
    """
    db.query('INSERT INTO foo VALUES (42)')
    db.query('INSERT INTO foo VALUES (43)')
    assert db.query('SELECT count(*) AS n FROM foo')[0].n == 2


@pytest.mark.usefixtures('foo_table')
def test_plain_conn(db):
    """Manipulate database by `conn.query` without transactions.
    """
    conn = db.get_connection()
    conn.query('INSERT INTO foo VALUES (42)')
    conn.query('INSERT INTO foo VALUES (43)')
    assert conn.query('SELECT count(*) AS n FROM foo')[0].n == 2


@pytest.mark.usefixtures('foo_table')
def test_failing_transaction_self_managed(db):
    conn = db.get_connection()
    tx = conn.transaction()
    try:
        conn.query('INSERT INTO foo VALUES (42)')
        conn.query('INSERT INTO foo VALUES (43)')
        raise ValueError()
        tx.commit()
        conn.query('INSERT INTO foo VALUES (44)')
    except ValueError:
        tx.rollback()
    finally:
        conn.close()
        assert db.query('SELECT count(*) AS n FROM foo')[0].n == 0


@pytest.mark.usefixtures('foo_table')
def test_failing_transaction(db):
    with db.transaction() as conn:
        conn.query('INSERT INTO foo VALUES (42)')
        conn.query('INSERT INTO foo VALUES (43)')
        raise ValueError()

    assert db.query('SELECT count(*) AS n FROM foo')[0].n == 0


@pytest.mark.usefixtures('foo_table')
def test_passing_transaction_self_managed(db):
    conn = db.get_connection()
    tx = conn.transaction()
    conn.query('INSERT INTO foo VALUES (42)')
    conn.query('INSERT INTO foo VALUES (43)')
    tx.commit()
    conn.close()
    assert db.query('SELECT count(*) AS n FROM foo')[0].n == 2


@pytest.mark.usefixtures('foo_table')
def test_passing_transaction(db):
    with db.transaction() as conn:
        conn.query('INSERT INTO foo VALUES (42)')
        conn.query('INSERT INTO foo VALUES (43)')

    assert db.query('SELECT count(*) AS n FROM foo')[0].n == 2


================================================
FILE: tox.ini
================================================
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.

[tox]
envlist = py27, py34, py35, py36

[testenv]
commands =
    pytest tests
deps =
    pytest
    # psycopg2-binary
Download .txt
gitextract_bs22qwwk/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .travis.yml
├── HISTORY.rst
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── README.rst
├── examples/
│   └── randomuser-sqlite.py
├── records.py
├── requirements.txt
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_105.py
│   ├── test_69.py
│   ├── test_records.py
│   └── test_transactions.py
└── tox.ini
Download .txt
SYMBOL INDEX (98 symbols across 7 files)

FILE: records.py
  function isexception (line 14) | def isexception(obj):
  class Record (line 25) | class Record(object):
    method __init__ (line 30) | def __init__(self, keys, values):
    method keys (line 37) | def keys(self):
    method values (line 41) | def values(self):
    method __repr__ (line 45) | def __repr__(self):
    method __getitem__ (line 48) | def __getitem__(self, key):
    method __getattr__ (line 67) | def __getattr__(self, key):
    method __dir__ (line 73) | def __dir__(self):
    method get (line 78) | def get(self, key, default=None):
    method as_dict (line 85) | def as_dict(self, ordered=False):
    method dataset (line 92) | def dataset(self):
    method export (line 102) | def export(self, format, **kwargs):
  class RecordCollection (line 107) | class RecordCollection(object):
    method __init__ (line 110) | def __init__(self, rows):
    method __repr__ (line 115) | def __repr__(self):
    method __iter__ (line 118) | def __iter__(self):
    method next (line 136) | def next(self):
    method __next__ (line 139) | def __next__(self):
    method __getitem__ (line 148) | def __getitem__(self, key):
    method __len__ (line 167) | def __len__(self):
    method export (line 170) | def export(self, format, **kwargs):
    method dataset (line 175) | def dataset(self):
    method all (line 195) | def all(self, as_dict=False, as_ordereddict=False):
    method as_dict (line 209) | def as_dict(self, ordered=False):
    method first (line 212) | def first(self, default=None, as_dict=False, as_ordereddict=False):
    method one (line 233) | def one(self, default=None, as_dict=False, as_ordereddict=False):
    method scalar (line 252) | def scalar(self, default=None):
  class Database (line 258) | class Database(object):
    method __init__ (line 263) | def __init__(self, db_url=None, **kwargs):
    method get_engine (line 274) | def get_engine(self):
    method close (line 280) | def close(self):
    method __enter__ (line 285) | def __enter__(self):
    method __exit__ (line 288) | def __exit__(self, exc, val, traceback):
    method __repr__ (line 291) | def __repr__(self):
    method get_table_names (line 294) | def get_table_names(self, internal=False, **kwargs):
    method get_connection (line 300) | def get_connection(self, close_with_result=False):
    method query (line 309) | def query(self, query, fetchall=False, **params):
    method bulk_query (line 317) | def bulk_query(self, query, *multiparams):
    method query_file (line 323) | def query_file(self, path, fetchall=False, **params):
    method bulk_query_file (line 329) | def bulk_query_file(self, path, *multiparams):
    method transaction (line 336) | def transaction(self):
  class Connection (line 350) | class Connection(object):
    method __init__ (line 353) | def __init__(self, connection, close_with_result=False):
    method close (line 358) | def close(self):
    method __enter__ (line 365) | def __enter__(self):
    method __exit__ (line 368) | def __exit__(self, exc, val, traceback):
    method __repr__ (line 371) | def __repr__(self):
    method query (line 374) | def query(self, query, fetchall=False, **params):
    method bulk_query (line 400) | def bulk_query(self, query, *multiparams):
    method query_file (line 405) | def query_file(self, path, fetchall=False, **params):
    method bulk_query_file (line 423) | def bulk_query_file(self, path, *multiparams):
    method transaction (line 442) | def transaction(self):
  function _reduce_datetimes (line 449) | def _reduce_datetimes(row):
  function cli (line 460) | def cli():
  function print_bytes (line 553) | def print_bytes(content):

FILE: setup.py
  class PublishCommand (line 16) | class PublishCommand(Command):
    method status (line 23) | def status(s):
    method initialize_options (line 27) | def initialize_options(self):
    method finalize_options (line 30) | def finalize_options(self):
    method run (line 33) | def run(self):
  function read (line 58) | def read(f):

FILE: tests/conftest.py
  function db (line 19) | def db(request, tmpdir):
  function foo_table (line 40) | def foo_table(db):

FILE: tests/test_105.py
  function test_issue105 (line 5) | def test_issue105(db):

FILE: tests/test_69.py
  function test_issue69 (line 1) | def test_issue69(db):

FILE: tests/test_records.py
  function check_id (line 11) | def check_id(i, row):
  class TestRecordCollection (line 15) | class TestRecordCollection:
    method test_iter (line 16) | def test_iter(self):
    method test_next (line 21) | def test_next(self):
    method test_iter_and_next (line 26) | def test_iter_and_next(self):
    method test_multiple_iter (line 33) | def test_multiple_iter(self):
    method test_slice_iter (line 45) | def test_slice_iter(self):
    method test_all_returns_a_list_of_records (line 56) | def test_all_returns_a_list_of_records(self):
    method test_first_returns_a_single_record (line 63) | def test_first_returns_a_single_record(self):
    method test_first_defaults_to_None (line 67) | def test_first_defaults_to_None(self):
    method test_first_default_is_overridable (line 71) | def test_first_default_is_overridable(self):
    method test_first_raises_default_if_its_an_exception_subclass (line 75) | def test_first_raises_default_if_its_an_exception_subclass(self):
    method test_first_raises_default_if_its_an_exception_instance (line 80) | def test_first_raises_default_if_its_an_exception_instance(self):
    method test_one_returns_a_single_record (line 87) | def test_one_returns_a_single_record(self):
    method test_one_defaults_to_None (line 91) | def test_one_defaults_to_None(self):
    method test_one_default_is_overridable (line 95) | def test_one_default_is_overridable(self):
    method test_one_raises_when_more_than_one (line 99) | def test_one_raises_when_more_than_one(self):
    method test_one_raises_default_if_its_an_exception_subclass (line 103) | def test_one_raises_default_if_its_an_exception_subclass(self):
    method test_one_raises_default_if_its_an_exception_instance (line 108) | def test_one_raises_default_if_its_an_exception_instance(self):
    method test_scalar_returns_a_single_record (line 115) | def test_scalar_returns_a_single_record(self):
    method test_scalar_defaults_to_None (line 119) | def test_scalar_defaults_to_None(self):
    method test_scalar_default_is_overridable (line 123) | def test_scalar_default_is_overridable(self):
    method test_scalar_raises_when_more_than_one (line 127) | def test_scalar_raises_when_more_than_one(self):
  class TestRecord (line 132) | class TestRecord:
    method test_record_dir (line 134) | def test_record_dir(self):
    method test_record_duplicate_column (line 143) | def test_record_duplicate_column(self):

FILE: tests/test_transactions.py
  function test_plain_db (line 21) | def test_plain_db(db):
  function test_plain_conn (line 30) | def test_plain_conn(db):
  function test_failing_transaction_self_managed (line 40) | def test_failing_transaction_self_managed(db):
  function test_failing_transaction (line 57) | def test_failing_transaction(db):
  function test_passing_transaction_self_managed (line 67) | def test_passing_transaction_self_managed(db):
  function test_passing_transaction (line 78) | def test_passing_transaction(db):
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (46K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 21,
    "preview": "github: kennethreitz\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 820,
    "preview": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more inform"
  },
  {
    "path": ".gitignore",
    "chars": 5,
    "preview": ".env\n"
  },
  {
    "path": ".travis.yml",
    "chars": 121,
    "preview": "sudo: false\nlanguage: python\npython:\n  - \"2.7\"\n  - \"3.4\"\n  - \"3.5\"\n  - \"3.6\"\ninstall: pip install tox-travis\nscript: tox"
  },
  {
    "path": "HISTORY.rst",
    "chars": 1458,
    "preview": "v0.6.0 (04-29-2024)\n===================\n\n- Support for Python 3.6+ only.\n- Support for SQLAlchemy 2+.\n- Dropped support "
  },
  {
    "path": "LICENSE",
    "chars": 767,
    "preview": "ISC License\n\nCopyright (c) 2016, Kenneth Reitz <me@kennethreitz.org>\n\nPermission to use, copy, modify, and/or distribute"
  },
  {
    "path": "MANIFEST.in",
    "chars": 55,
    "preview": "include README.rst LICENSE HISTORY.rst requirements.txt"
  },
  {
    "path": "Makefile",
    "chars": 244,
    "preview": "testall:\n\ttox\ntest: init\n\tpipenv run pytest tests\ninit:\n\tpipenv install --skip-lock --dev\npublish:\n\tpython setup.py regi"
  },
  {
    "path": "README.md",
    "chars": 4744,
    "preview": "# Records: SQL for Humans™\n\n[![image](https://img.shields.io/pypi/v/records.svg)](https://pypi.python.org/pypi/records)\n"
  },
  {
    "path": "README.rst",
    "chars": 4829,
    "preview": "Records: SQL for Humans™\n========================\n\n\n.. image:: https://img.shields.io/pypi/v/records.svg\n    :target: ht"
  },
  {
    "path": "examples/randomuser-sqlite.py",
    "chars": 1062,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\n\nimport json\nimport requests\nimport records\n\n# Fetch random user data from random"
  },
  {
    "path": "records.py",
    "chars": 17352,
    "preview": "# -*- coding: utf-8 -*-\n\nimport os\nfrom sys import stdout\nfrom collections import OrderedDict\nfrom contextlib import con"
  },
  {
    "path": "requirements.txt",
    "chars": 16,
    "preview": "-e .[pg]\npytest\n"
  },
  {
    "path": "setup.py",
    "chars": 2984,
    "preview": "#!/usr/bin/env python\n\nimport io\nimport os\nimport sys\nfrom codecs import open\nfrom shutil import rmtree\n\nfrom setuptools"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/conftest.py",
    "chars": 1252,
    "preview": "\"\"\"Shared pytest fixtures.\n\n\"\"\"\n\nimport pytest\n\nimport records\n\n\n@pytest.fixture(\n    params=[\n        # request: (sql_u"
  },
  {
    "path": "tests/test_105.py",
    "chars": 144,
    "preview": "import pytest\n\n\n@pytest.mark.usefixtures(\"foo_table\")\ndef test_issue105(db):\n    assert db.query(\"select count(*) as n f"
  },
  {
    "path": "tests/test_69.py",
    "chars": 143,
    "preview": "def test_issue69(db):\n    db.query(\"CREATE table users (id text)\")\n    db.query(\"SELECT * FROM users WHERE id = :user\", "
  },
  {
    "path": "tests/test_records.py",
    "chars": 4699,
    "preview": "from collections import namedtuple\n\nimport records\n\nfrom pytest import raises\n\n\nIdRecord = namedtuple('IdRecord', 'id')\n"
  },
  {
    "path": "tests/test_transactions.py",
    "chars": 2425,
    "preview": "\"\"\"Test manipulating database table in various transaction scenarios.\n\nVarying conditions:\n\n- db for different database "
  },
  {
    "path": "tox.ini",
    "chars": 301,
    "preview": "# in multiple virtualenvs. This configuration file will run the\n# test suite on all supported python versions. To use it"
  }
]

About this extraction

This page contains the full source code of the kennethreitz/records GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (42.4 KB), approximately 10.8k tokens, and a symbol index with 98 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!