pax_global_header00006660000000000000000000000064131007035050014504gustar00rootroot0000000000000052 comment=8bb250185d57766fb6163da5a64f71bd6b3eb476 agate-sql-0.5.2/000077500000000000000000000000001310070350500133665ustar00rootroot00000000000000agate-sql-0.5.2/.gitignore000066400000000000000000000001361310070350500153560ustar00rootroot00000000000000.DS_Store *.pyc *.swp *.swo .tox *.egg-info docs/_build dist .coverage build .proof .test.png agate-sql-0.5.2/.travis.yml000066400000000000000000000004651310070350500155040ustar00rootroot00000000000000language: python python: - "2.7" - "3.3" - "3.4" - "3.5" - "3.6" # command to install dependencies install: - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then pip install -r requirements-py3.txt; else pip install -r requirements-py2.txt; fi # command to run tests script: nosetests tests sudo: false agate-sql-0.5.2/AUTHORS.rst000066400000000000000000000006241310070350500152470ustar00rootroot00000000000000The following individuals have contributed code to agate-sql: * `Christopher Groskopf `_ * `Adrian Klaver `_ * `James McKinney `_ * `Chris Keller `_ * `git-clueless `_ * `z2s8 `_ * `Jake Zimmerman `_ agate-sql-0.5.2/CHANGELOG.rst000066400000000000000000000027131310070350500154120ustar00rootroot000000000000000.5.2 - April 28, 2017 ---------------------- * Add ``create_if_not_exists`` flag to :meth:`.TableSQL.to_sql`. 0.5.1 - February 27, 2017 ------------------------- * Add ``prefixes`` option to :func:`.to_sql` to add expressions following the INSERT keyword, like OR IGNORE or OR REPLACE. * Use ``TIMESTAMP`` instead of ``DATETIME`` for DateTime columns. 0.5.0 - December 23, 2016 ------------------------- * ``VARCHAR`` columns are now generated with proper length constraints (unless explicilty disabled). * Tables can now be created from query results using :func:`.from_sql_query`. * Add support for running queries directly on tables with :func:`.sql_query`. * When creating tables, ``NOT NULL`` constraints will be created by default. * SQL create statements can now be generated without being executed with :func:`.to_sql_create_statement` 0.4.0 - December 19, 2016 ------------------------- * Modified ``example.py`` so it no longer depends on Postgres. * It is no longer necessary to run :code:`agatesql.patch()` after importing agatesql. * Upgrade required agate to ``1.5.0``. 0.3.0 - November 5, 2015 ------------------------ * Add ``overwrite`` flag to :meth:`.TableSQL.to_sql`. * Removed Python 2.6 support. * Updated agate dependency to version 1.1.0. * Additional SQL types are now supported. (#4, #10) 0.2.0 - October 22, 2015 ------------------------ * Add explicit patch function. 0.1.0 - September 22, 2015 -------------------------- * Initial version. agate-sql-0.5.2/COPYING000066400000000000000000000021131310070350500144160ustar00rootroot00000000000000The MIT License Copyright (c) 2017 Christopher Groskopf and contributors 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, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. agate-sql-0.5.2/MANIFEST.in000066400000000000000000000000671310070350500151270ustar00rootroot00000000000000include COPYING include AUTHORS.rst include README.rst agate-sql-0.5.2/README.rst000066400000000000000000000017531310070350500150630ustar00rootroot00000000000000.. image:: https://travis-ci.org/wireservice/agate-sql.png :target: https://travis-ci.org/wireservice/agate-sql :alt: Build status .. image:: https://img.shields.io/pypi/dw/agate-sql.svg :target: https://pypi.python.org/pypi/agate-sql :alt: PyPI downloads .. image:: https://img.shields.io/pypi/v/agate-sql.svg :target: https://pypi.python.org/pypi/agate-sql :alt: Version .. image:: https://img.shields.io/pypi/l/agate-sql.svg :target: https://pypi.python.org/pypi/agate-sql :alt: License .. image:: https://img.shields.io/pypi/pyversions/agate-sql.svg :target: https://pypi.python.org/pypi/agate-sql :alt: Support Python versions agate-sql adds SQL read/write support to `agate `_. Important links: * agate http://agate.rtfd.org * Documentation: http://agate-sql.rtfd.org * Repository: https://github.com/wireservice/agate-sql * Issues: https://github.com/wireservice/agate-sql/issues agate-sql-0.5.2/agatesql/000077500000000000000000000000001310070350500151675ustar00rootroot00000000000000agate-sql-0.5.2/agatesql/__init__.py000066400000000000000000000000551310070350500173000ustar00rootroot00000000000000#!/usr/bin/env python import agatesql.table agate-sql-0.5.2/agatesql/table.py000066400000000000000000000224561310070350500166410ustar00rootroot00000000000000#!/usr/bin/env python """ This module contains the agatesql extensions to :class:`Table `. """ import decimal import datetime import six import agate from sqlalchemy import Column, MetaData, Table, create_engine, dialects from sqlalchemy.engine import Connection from sqlalchemy.types import BOOLEAN, DECIMAL, DATE, TIMESTAMP, VARCHAR, Interval from sqlalchemy.dialects.oracle import INTERVAL as ORACLE_INTERVAL from sqlalchemy.dialects.postgresql import INTERVAL as POSTGRES_INTERVAL from sqlalchemy.schema import CreateTable from sqlalchemy.sql import select SQL_TYPE_MAP = { agate.Boolean: BOOLEAN, agate.Number: DECIMAL, agate.Date: DATE, agate.DateTime: TIMESTAMP, agate.TimeDelta: None, # See below agate.Text: VARCHAR } INTERVAL_MAP = { 'postgresql': POSTGRES_INTERVAL, 'oracle': ORACLE_INTERVAL } def get_connection(connection_or_string=None): """ Gets a connection to a specific SQL alchemy backend. If an existing connection is provided, it will be passed through. If no connection string is provided, then in in-memory SQLite database will be created. """ if connection_or_string is None: engine = create_engine('sqlite:///:memory:') connection = engine.connect() elif isinstance(connection_or_string, Connection): connection = connection_or_string else: engine = create_engine(connection_or_string) connection = engine.connect() return connection def from_sql(cls, connection_or_string, table_name): """ Create a new :class:`agate.Table` from a given SQL table. Types will be inferred from the database schema. Monkey patched as class method :meth:`Table.from_sql`. :param connection_or_string: An existing sqlalchemy connection or connection string. :param table_name: The name of a table in the referenced database. """ connection = get_connection(connection_or_string) metadata = MetaData(connection) sql_table = Table(table_name, metadata, autoload=True, autoload_with=connection) column_names = [] column_types = [] for sql_column in sql_table.columns: column_names.append(sql_column.name) if type(sql_column.type) in INTERVAL_MAP.values(): py_type = datetime.timedelta else: py_type = sql_column.type.python_type if py_type in [int, float, decimal.Decimal]: if py_type is float: sql_column.type.asdecimal = True column_types.append(agate.Number()) elif py_type is bool: column_types.append(agate.Boolean()) elif issubclass(py_type, six.string_types): column_types.append(agate.Text()) elif py_type is datetime.date: column_types.append(agate.Date()) elif py_type is datetime.datetime: column_types.append(agate.DateTime()) elif py_type is datetime.timedelta: column_types.append(agate.TimeDelta()) else: raise ValueError('Unsupported sqlalchemy column type: %s' % type(sql_column.type)) s = select([sql_table]) rows = connection.execute(s) return agate.Table(rows, column_names, column_types) def from_sql_query(self, query): """ Create an agate table from the results of a SQL query. Note that column data types will be inferred from the returned data, not the column types declared in SQL (if any). This is more flexible than :func:`.from_sql` but could result in unexpected typing issues. :param query: A SQL query to execute. """ connection = get_connection() # Must escape '%'. # @see https://github.com/wireservice/csvkit/issues/440 # @see https://bitbucket.org/zzzeek/sqlalchemy/commits/5bc1f17cb53248e7cea609693a3b2a9bb702545b rows = connection.execute(query.replace('%', '%%')) table = agate.Table(list(rows), column_names=rows._metadata.keys) return table def make_sql_column(column_name, column, sql_type_kwargs=None, sql_column_kwargs=None): """ Creates a sqlalchemy column from agate column data. :param column_name: The name of the column. :param column: The agate column. :param sql_column_kwargs: Additional kwargs to passed through to the Column constructor, such as ``nullable``. """ sql_column_type = None for agate_type, sql_type in SQL_TYPE_MAP.items(): if isinstance(column.data_type, agate_type): sql_column_type = sql_type break if sql_column_type is None: raise ValueError('Unsupported column type: %s' % column_type) sql_type_kwargs = sql_type_kwargs or {} sql_column_kwargs = sql_column_kwargs or {} return Column(column_name, sql_column_type(**sql_type_kwargs), **sql_column_kwargs) def make_sql_table(table, table_name, dialect=None, db_schema=None, constraints=True, connection=None): """ Generates a SQL alchemy table from an agate table. """ metadata = MetaData(connection) sql_table = Table(table_name, metadata, schema=db_schema) if dialect in INTERVAL_MAP.keys(): SQL_TYPE_MAP[agate.TimeDelta] = INTERVAL_MAP[dialect] else: SQL_TYPE_MAP[agate.TimeDelta] = Interval for column_name, column in table.columns.items(): sql_type_kwargs = {} sql_column_kwargs = {} if constraints: if isinstance(column.data_type, agate.Text): sql_type_kwargs['length'] = table.aggregate(agate.MaxLength(column_name)) # Avoid errors due to NO_ZERO_DATE. # @see http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_no_zero_date if not isinstance(column.data_type, agate.DateTime): sql_column_kwargs['nullable'] = table.aggregate(agate.HasNulls(column_name)) sql_table.append_column(make_sql_column(column_name, column, sql_type_kwargs, sql_column_kwargs)) return sql_table def to_sql(self, connection_or_string, table_name, overwrite=False, create=True, create_if_not_exists=False, insert=True, prefixes=[], db_schema=None, constraints=True): """ Write this table to the given SQL database. Monkey patched as instance method :meth:`Table.to_sql`. :param connection_or_string: An existing sqlalchemy connection or a connection string. :param table_name: The name of the SQL table to create. :param overwrite: Drop any existing table with the same name before creating. :param create: Create the table. :param create_if_not_exists: When creating the table, don't fail if the table already exists. :param insert: Insert table data. :param prefixes: Add prefixes to the insert query. :param db_schema: Create table in the specified database schema. :param constraints Generate constraints such as ``nullable`` for table columns. """ connection = get_connection(connection_or_string) dialect = connection.engine.dialect.name sql_table = make_sql_table(self, table_name, dialect=dialect, db_schema=db_schema, constraints=constraints, connection=connection) if create: if overwrite: sql_table.drop(checkfirst=True) sql_table.create(checkfirst=create_if_not_exists) if insert: insert = sql_table.insert() for prefix in prefixes: insert = insert.prefix_with(prefix) connection.execute(insert, [dict(zip(self.column_names, row)) for row in self.rows]) return sql_table def to_sql_create_statement(self, table_name, dialect=None, db_schema=None, constraints=True): """ Generates a CREATE TABLE statement for this SQL table, but does not execute it. :param table_name: The name of the SQL table to create. :param dialect: The dialect of SQL to use for the table statement. :param db_schema: Create table in the specified database schema. :param constraints Generate constraints such as ``nullable`` for table columns. """ sql_table = make_sql_table(self, table_name, dialect=dialect, db_schema=db_schema, constraints=constraints) if dialect: sql_dialect = dialects.registry.load(dialect)() else: sql_dialect = None return six.text_type(CreateTable(sql_table).compile(dialect=sql_dialect)).strip() + ';' def sql_query(self, query, table_name='agate'): """ Convert this agate table into an intermediate, in-memory sqlite table, run a query against it, and then return the results as a new agate table. Multiple queries may be separated with semicolons. :param query: One SQL query, or multiple queries to be run consecutively separated with semicolons. :param table_name: The name to use for the table in the queries, defaults to ``agate``. """ connection = get_connection() # Execute the specified SQL queries queries = query.split(';') rows = None sql_table = self.to_sql(connection, table_name) for q in queries: if q: rows = connection.execute(q) table = agate.Table(list(rows), column_names=rows._metadata.keys) return table agate.Table.from_sql = classmethod(from_sql) agate.Table.from_sql_query = classmethod(from_sql_query) agate.Table.to_sql = to_sql agate.Table.to_sql_create_statement = to_sql_create_statement agate.Table.sql_query = sql_query agate-sql-0.5.2/docs/000077500000000000000000000000001310070350500143165ustar00rootroot00000000000000agate-sql-0.5.2/docs/Makefile000066400000000000000000000107661310070350500157700ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/agatesql.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/agatesql.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/agatesql" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/agatesql" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." agate-sql-0.5.2/docs/conf.py000066400000000000000000000162151310070350500156220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] autodoc_member_order = 'bysource' intersphinx_mapping = { 'python': ('http://docs.python.org/3.5/', None), 'agate': ('http://agate.readthedocs.org/en/latest/', None) } # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'agate-sql' copyright = u'2017, Christopher Groskopf' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.5.2' # The full version, including alpha/beta/rc tags. release = '0.5.2 (beta)' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'agatesqldoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'agate-sql.tex', u'agate-sql Documentation', u'Christopher Groskopf', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ] agate-sql-0.5.2/docs/index.rst000066400000000000000000000036741310070350500161710ustar00rootroot00000000000000=================== agate-sql |release| =================== .. include:: ../README.rst Install ======= To install: .. code-block:: bash pip install agate-sql For details on development or supported platforms see the `agate documentation `_. .. warning:: You'll need to have the correct `sqlalchemy drivers `_ installed for whatever database you plan to access. For instance, in order to read/write tables in a Postgres database, you'll also need to ``pip install psycopg2``. Usage ===== agate-sql uses a monkey patching pattern to add SQL support to all :class:`agate.Table ` instances. .. code-block:: python import agate import agatesql Importing :mod:`.agatesql` attaches new methods to :class:`agate.Table `. For example, to import a table named :code:`doctors` from a local postgresql database named :code:`hospitals` you will use :meth:`.from_sql`: .. code-block:: python new_table = agate.Table.from_sql('postgresql:///hospitals', 'doctors') To save this table back to the database: .. code-block:: python new_table.to_sql('postgresql:///hospitals', 'doctors') The first argument to either function can be any valid `sqlalchemy connection string `_. The second argument must be a database name. (Arbitrary SQL queries are not supported.) That's all there is to it. === API === .. autofunction:: agatesql.table.from_sql .. autofunction:: agatesql.table.from_sql_query .. autofunction:: agatesql.table.to_sql .. autofunction:: agatesql.table.to_sql_create_statement .. autofunction:: agatesql.table.sql_query Authors ======= .. include:: ../AUTHORS.rst Changelog ========= .. include:: ../CHANGELOG.rst License ======= .. include:: ../COPYING Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` agate-sql-0.5.2/example.db000066400000000000000000000040001310070350500153220ustar00rootroot00000000000000SQLite format 3@ - _!tabletesttestCREATE TABLE test ( number DECIMAL NOT NULL, text VARCHAR(1) NOT NULL ) b aagate-sql-0.5.2/example.py000077500000000000000000000004261310070350500154000ustar00rootroot00000000000000#!/usr/bin/env python import agate import agatesql table = agate.Table.from_sql('sqlite:///example.db', 'test') print(table.column_names) print(table.column_types) print(len(table.columns)) print(len(table.rows)) table.to_sql('sqlite:///example.db', 'test', overwrite=True) agate-sql-0.5.2/requirements-py2.txt000066400000000000000000000002121310070350500173550ustar00rootroot00000000000000unittest2==0.5.1 nose>=1.1.2 tox>=1.3 Sphinx>=1.2.2 sphinx_rtd_theme>=0.1.6 wheel>=0.24.0 agate>=1.5.0 sqlalchemy>=1.0.8 ordereddict>=1.1 agate-sql-0.5.2/requirements-py3.txt000066400000000000000000000001501310070350500173570ustar00rootroot00000000000000nose>=1.1.2 tox>=1.3 Sphinx>=1.2.2 sphinx_rtd_theme>=0.1.6 wheel>=0.24.0 agate>=1.5.0 sqlalchemy>=1.0.8 agate-sql-0.5.2/setup.cfg000066400000000000000000000000341310070350500152040ustar00rootroot00000000000000[bdist_wheel] universal = 1 agate-sql-0.5.2/setup.py000066400000000000000000000027001310070350500150770ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup install_requires = [ 'agate>=1.5.0', 'sqlalchemy>=1.0.8' ] setup( name='agate-sql', version='0.5.2', description='agate-sql adds SQL read/write support to agate.', long_description=open('README.rst').read(), author='Christopher Groskopf', author_email='chrisgroskopf@gmail.com', url='http://agate-sql.readthedocs.org/', license='MIT', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Multimedia :: Graphics', 'Topic :: Scientific/Engineering :: Information Analysis', 'Topic :: Scientific/Engineering :: Visualization', 'Topic :: Software Development :: Libraries :: Python Modules', ], packages=[ 'agatesql' ], install_requires=install_requires ) agate-sql-0.5.2/tests/000077500000000000000000000000001310070350500145305ustar00rootroot00000000000000agate-sql-0.5.2/tests/__init__.py000066400000000000000000000000001310070350500166270ustar00rootroot00000000000000agate-sql-0.5.2/tests/test_agatesql.py000066400000000000000000000124351310070350500177470ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf8 -*- from pkg_resources import iter_entry_points try: import unittest2 as unittest except ImportError: import unittest import agate import agatesql from agatesql.table import make_sql_column, make_sql_table from sqlalchemy import create_engine class TestSQL(agate.AgateTestCase): def setUp(self): self.rows = ( (1, 'a', True, '11/4/2015', '11/4/2015 12:22 PM'), # See issue #18 # (2, u'👍', False, '11/5/2015', '11/4/2015 12:45 PM'), (2, u'c', False, '11/5/2015', '11/4/2015 12:45 PM'), (None, 'b', None, None, None) ) self.column_names = [ 'number', 'text', 'boolean', 'date', 'datetime' ] self.column_types = [ agate.Number(), agate.Text(), agate.Boolean(), agate.Date(), agate.DateTime() ] self.table = agate.Table(self.rows, self.column_names, self.column_types) self.connection_string = 'sqlite:///:memory:' def test_back_and_forth(self): engine = create_engine(self.connection_string) connection = engine.connect() self.table.to_sql(connection, 'test') table = agate.Table.from_sql(connection, 'test') self.assertSequenceEqual(table.column_names, self.column_names) self.assertIsInstance(table.column_types[0], agate.Number) self.assertIsInstance(table.column_types[1], agate.Text) self.assertIsInstance(table.column_types[2], agate.Boolean) self.assertIsInstance(table.column_types[3], agate.Date) self.assertIsInstance(table.column_types[4], agate.DateTime) self.assertEqual(len(table.rows), len(self.table.rows)) self.assertSequenceEqual(table.rows[0], self.table.rows[0]) def test_create_if_not_exists(self): column_names = ['id', 'name'] column_types = [agate.Number(), agate.Text()] rows1 = ( (1, 'Jake'), (2, 'Howard'), ) rows2 = ( (3, 'Liz'), (4, 'Tim'), ) table1 = agate.Table(rows1, column_names, column_types) table2 = agate.Table(rows2, column_names, column_types) engine = create_engine(self.connection_string) connection = engine.connect() # Write two agate tables into the same SQL table table1.to_sql(connection, 'create_if_not_exists_test', create=True, create_if_not_exists=True, insert=True) table2.to_sql(connection, 'create_if_not_exists_test', create=True, create_if_not_exists=True, insert=True) def test_to_sql_create_statement(self): statement = self.table.to_sql_create_statement('test_table') print(self.rows[1][1]) print(self.table.columns[1].values()) print(statement) self.assertIn('CREATE TABLE test_table', statement) self.assertIn('number DECIMAL', statement) self.assertIn('text VARCHAR(1) NOT NULL', statement) self.assertIn('boolean BOOLEAN', statement) self.assertIn('date DATE', statement) self.assertIn('datetime TIMESTAMP', statement) def test_make_create_table_statement_no_constraints(self): statement = self.table.to_sql_create_statement('test_table', constraints=False) self.assertIn('CREATE TABLE test_table', statement) self.assertIn('number DECIMAL', statement) self.assertIn('text VARCHAR', statement) self.assertIn('boolean BOOLEAN', statement) self.assertIn('date DATE', statement) self.assertIn('datetime TIMESTAMP', statement) def test_make_create_table_statement_with_schema(self): statement = self.table.to_sql_create_statement('test_table', db_schema='test_schema') self.assertIn('CREATE TABLE test_schema.test_table', statement) self.assertIn('number DECIMAL', statement) self.assertIn('text VARCHAR(1) NOT NULL', statement) self.assertIn('boolean BOOLEAN', statement) self.assertIn('date DATE', statement) self.assertIn('datetime TIMESTAMP', statement) def test_make_create_table_statement_with_dialects(self): for dialect in ['mysql', 'postgresql', 'sqlite']: statement = self.table.to_sql_create_statement('test_table', dialect=dialect) def test_sql_query_simple(self): results = self.table.sql_query('select * from agate') self.assertColumnNames(results, self.table.column_names) self.assertRows(results, self.table.rows) def test_sql_query_limit(self): results = self.table.sql_query('select * from agate limit 2') self.assertColumnNames(results, self.table.column_names) self.assertRows(results, self.table.rows[:2]) def test_sql_query_select(self): results = self.table.sql_query('select number, boolean from agate') self.assertColumnNames(results, ['number', 'boolean']) self.assertColumnTypes(results, [agate.Number, agate.Boolean]) self.assertRows(results, [ [1, True], [2, False], [None, None] ]) def test_sql_query_aggregate(self): results = self.table.sql_query('select sum(number) as total from agate') self.assertColumnNames(results, ['total']) self.assertColumnTypes(results, [agate.Number]) self.assertRows(results, [[3]]) agate-sql-0.5.2/tox.ini000066400000000000000000000006011310070350500146760ustar00rootroot00000000000000[tox] envlist = py27,py33,py34,py35,py36,pypy [testenv] deps= nose>=1.1.2 six>=1.6.1 commands=nosetests [testenv:py27] deps= {[testenv]deps} [testenv:py33] deps= {[testenv]deps} [testenv:py34] deps= {[testenv:py33]deps} [testenv:py35] deps= {[testenv:py33]deps} [testenv:py36] deps= {[testenv:py33]deps} [testenv:pypy] deps= {[testenv:py33]deps}