From 10980b96887b6ea40240c1a155ad6b86fa8ebf89 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Tue, 2 Apr 2013 00:44:13 +0200 Subject: [PATCH 01/30] changed background to avoid confusion with flask --- docs/_themes/kr/static/flasky.css_t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_themes/kr/static/flasky.css_t b/docs/_themes/kr/static/flasky.css_t index 3a13c35..422731e 100755 --- a/docs/_themes/kr/static/flasky.css_t +++ b/docs/_themes/kr/static/flasky.css_t @@ -16,7 +16,7 @@ body { font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; font-size: 17px; - background-color: white; + background-color: whitesmoke; color: #000; margin: 0; padding: 0; @@ -45,7 +45,7 @@ hr { } div.body { - background-color: #ffffff; + background-color: whitesmoke; color: #3E4349; padding: 0 30px 0 30px; } From 4bfd6e5d0c155f3854494e2963cc98617d01d640 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Tue, 2 Apr 2013 00:44:22 +0200 Subject: [PATCH 02/30] removed trackers --- docs/_themes/kr/layout.html | 55 ++----------------------------------- docs/index.rst | 7 +++-- 2 files changed, 6 insertions(+), 56 deletions(-) diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html index 4f6bf1d..391e037 100755 --- a/docs/_themes/kr/layout.html +++ b/docs/_themes/kr/layout.html @@ -1,6 +1,7 @@ {%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} + {% if theme_touch_icon %} {% endif %} @@ -11,61 +12,9 @@ - + Fork me on GitHub - - - - - - - - - {%- endblock %} diff --git a/docs/index.rst b/docs/index.rst index a73827f..0340555 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,8 +3,9 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to dataset's documentation! -=================================== +dataset: databases for humans +============================= + Getting the databases out of your data's way. @@ -17,7 +18,7 @@ Getting the databases out of your data's way. Indices and tables -================== +------------------ * :ref:`genindex` * :ref:`modindex` From 58c1773777ec3fc379298124bef2f8504fc595c5 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Tue, 2 Apr 2013 11:10:29 +0200 Subject: [PATCH 03/30] documentation! --- dataset/__init__.py | 3 +- dataset/persistence/database.py | 27 ++++--- dataset/persistence/table.py | 120 ++++++++++++++++++---------- docs/_themes/kr/layout.html | 2 +- docs/_themes/kr/static/flasky.css_t | 24 ++++-- docs/index.rst | 23 +++++- 6 files changed, 133 insertions(+), 66 deletions(-) diff --git a/dataset/__init__.py b/dataset/__init__.py index 99007a5..83b0b0c 100644 --- a/dataset/__init__.py +++ b/dataset/__init__.py @@ -9,8 +9,7 @@ from dataset.persistence.table import Table def connect(url): """ Opens a new connection to a database. *url* can be any valid `SQLAlchemy engine URL`_. Returns - an instance of :py:class:`dataset.Database. - + an instance of :py:class:`Database `. :: db = dataset.connect('sqlite:///factbook.db') diff --git a/dataset/persistence/database.py b/dataset/persistence/database.py index 84de871..8dbc242 100644 --- a/dataset/persistence/database.py +++ b/dataset/persistence/database.py @@ -40,7 +40,10 @@ class Database(object): an `id` column, which is set to be an auto-incrementing integer as the primary key of the table. - Returns a :py:class:`dataset.Table` instance.""" + Returns a :py:class:`Table ` instance. + :: + table = db.create_table('population') + """ with self.lock: log.debug("Creating table: %s on %r" % (table_name, self.engine)) table = SQLATable(table_name, self.metadata) @@ -53,10 +56,12 @@ class Database(object): def load_table(self, table_name): """ Loads a table. This will fail if the tables does not already exist in the database. If the table exists, its columns will be - reflected and are available on the :py:class:`dataset.Table` + reflected and are available on the :py:class:`Table ` object. - Returns a :py:class:`dataset.Table` instance.""" + Returns a :py:class:`Table ` instance. + :: + table = db.load_table('population')""" with self.lock: log.debug("Loading table: %s on %r" % (table_name, self)) table = SQLATable(table_name, self.metadata, autoload=True) @@ -64,9 +69,15 @@ class Database(object): return Table(self, table) def get_table(self, table_name): - """ Loads a table or creates it if it doesn't exist yet. - Returns a :py:class:`dataset.Table` instance. Alternatively to *get_table* - you can also get tables using the dict syntax.""" + """ Smart wrapper around *load_table* and *create_table*. Either loads a table + or creates it if it doesn't exist yet. + + Returns a :py:class:`Table ` instance. + :: + table = db.get_table('population') + # you can also use the short-hand syntax: + table = db['population'] + """ with self.lock: if table_name in self._tables: return Table(self, self._tables[table_name]) @@ -83,9 +94,7 @@ class Database(object): execution of arbitrary read/write queries. A query can either be a plain text string, or a SQLAlchemy expression. The returned iterator will yield each result sequentially. - - .. code-block:: python - + :: result = db.query('SELECT * FROM population WHERE population > 10000000') for row in result: print row diff --git a/dataset/persistence/table.py b/dataset/persistence/table.py index a83c56c..14263a9 100644 --- a/dataset/persistence/table.py +++ b/dataset/persistence/table.py @@ -18,24 +18,24 @@ class Table(object): self.table = table def drop(self): - """ Drop the table from the database, deleting both the schema + """ Drop the table from the database, deleting both the schema and all the contents within it. - + Note: the object will be in an unusable state after using this command and should not be used again. If you want to re-create the table, make sure to get a fresh instance from the - :py:class:`dataset.Database`. """ + :py:class:`Database `. """ with self.database.lock: self.database.tables.pop(self.table.name, None) self.table.drop(engine) def insert(self, row, ensure=True, types={}): - """ Add a row (type: dict) by inserting it into the database. + """ Add a row (type: dict) by inserting it into the table. If ``ensure`` is set, any of the keys of the row are not - table columns, they will be created automatically. - + table columns, they will be created automatically. + During column creation, ``types`` will be checked for a key - matching the name of a column to be created, and the given + matching the name of a column to be created, and the given SQLAlchemy column type will be used. Otherwise, the type is guessed from the row's value, defaulting to a simple unicode field. """ @@ -43,27 +43,23 @@ class Table(object): self._ensure_columns(row, types=types) self.database.engine.execute(self.table.insert(row)) - def update(self, row, unique, ensure=True, types={}): - """ Update a row in the database. The update is managed via - the set of column names stated in ``unique``: they will be + def update(self, row, keys, ensure=True, types={}): + """ Update a row in the table. The update is managed via + the set of column names stated in ``keys``: they will be used as filters for the data to be updated, using the values - in ``row``. Example: - - .. code-block:: python - + in ``row``. + :: + # update all entries with id matching 10, setting their title columns data = dict(id=10, title='I am a banana!') table.update(data, ['id']) - This will update all entries matching the given ``id``, setting - their ``title`` column. - - If keys in ``row`` update columns not present in the table, - they will be created based on the settings of ``ensure`` and - ``types``, matching the behaviour of ``insert``. + If keys in ``row`` update columns not present in the table, + they will be created based on the settings of ``ensure`` and + ``types``, matching the behaviour of :py:meth:`insert() `. """ - if not len(unique): + if not len(keys): return False - clause = [(u, row.get(u)) for u in unique] + clause = [(u, row.get(u)) for u in keys] if ensure: self._ensure_columns(row, types=types) try: @@ -74,15 +70,25 @@ class Table(object): except KeyError, ke: return False - def upsert(self, row, unique, ensure=True, types={}): + def upsert(self, row, keys, ensure=True, types={}): + """An UPSERT is a smart combination of insert and update. If rows with matching ``keys`` exist + they will be updated, otherwise a new row is inserted in the table. + :: + data = dict(id=10, title='I am a banana!') + table.upsert(data, ['id']) + """ if ensure: - self.create_index(unique) + self.create_index(keys) - if not self.update(row, unique, ensure=ensure, types=types): + if not self.update(row, keys, ensure=ensure, types=types): self.insert(row, ensure=ensure, types=types) - def delete(self, **kw): - q = self._args_to_clause(kw) + def delete(self, **filter): + """Delete rows matching the ``filter`` arguments. + :: + table.delete(year=2010) + """ + q = self._args_to_clause(filter) stmt = self.table.delete(q) self.database.engine.execute(stmt) @@ -92,8 +98,8 @@ class Table(object): _type = types[column] else: _type = guess_type(row[column]) - log.debug("Creating column: %s (%s) on %r" % (column, - _type, self.table.name)) + log.debug("Creating column: %s (%s) on %r" % (column, + _type, self.table.name)) self.create_column(column, _type) def _args_to_clause(self, args): @@ -108,7 +114,7 @@ class Table(object): if name not in self.table.columns.keys(): col = Column(name, type) col.create(self.table, - connection=self.database.engine) + connection=self.database.engine) def create_index(self, columns, name=None): with self.database.lock: @@ -126,29 +132,44 @@ class Table(object): self.indexes[name] = idx return idx - def find_one(self, **kw): - res = list(self.find(_limit=1, **kw)) + def find_one(self, **filter): + """Works just like :py:meth:`find() ` but returns only the first result. + :: + row = table.find_one(country='United States') + """ + res = list(self.find(_limit=1, **filter)) if not len(res): return None return res[0] def find(self, _limit=None, _step=5000, _offset=0, - order_by='id', **kw): + order_by='id', **filter): + """Performs a simple search on the table. + :: + results = table.find(country='France') + # combining multiple conditions (AND) + results = table.find(country='France', year=1980) + # just return the first 10 rows + results = table.find(country='France', _limit=10) + # sort results by a column + results = table.find(country='France', order_by='year') + + For more complex queries, please use :py:meth:`db.query() ` instead.""" order_by = [self.table.c[order_by].asc()] - args = self._args_to_clause(kw) + args = self._args_to_clause(filter) for i in count(): qoffset = _offset + (_step * i) qlimit = _step if _limit is not None: - qlimit = min(_limit-(_step*i), _step) + qlimit = min(_limit - (_step * i), _step) if qlimit <= 0: break q = self.table.select(whereclause=args, limit=qlimit, - offset=qoffset, order_by=order_by) + offset=qoffset, order_by=order_by) rows = list(self.database.query(q)) if not len(rows): - return + return for row in rows: yield row @@ -156,20 +177,33 @@ class Table(object): d = self.database.query(self.table.count()).next() return d.values().pop() - def distinct(self, *columns, **kw): + def distinct(self, *columns, **filter): + """Returns all rows of a table, but removes rows in with duplicate values in ``columns`. + Interally this creates a `DISTINCT statement `_. + :: + # returns only one row per year, ignoring the rest + table.distinct('year') + # works with multiple columns, too + table.distinct('year', 'country') + # you can also combine this with a filter + table.distinct('year', country='China') + """ qargs = [] try: columns = [self.table.c[c] for c in columns] - for col, val in kw.items(): - qargs.append(self.table.c[col]==val) + for col, val in filter.items(): + qargs.append(self.table.c[col] == val) except KeyError: return [] q = expression.select(columns, distinct=True, - whereclause=and_(*qargs), - order_by=[c.asc() for c in columns]) + whereclause=and_(*qargs), + order_by=[c.asc() for c in columns]) return self.database.query(q) def all(self): + """Returns all rows of the table as simple dictionaries. This is simply a shortcut + to *find()* called with no arguments. + :: + rows = table.all()""" return self.find() - diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html index 391e037..0907a52 100755 --- a/docs/_themes/kr/layout.html +++ b/docs/_themes/kr/layout.html @@ -1,7 +1,7 @@ {%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} - + {% if theme_touch_icon %} {% endif %} diff --git a/docs/_themes/kr/static/flasky.css_t b/docs/_themes/kr/static/flasky.css_t index 422731e..c5bf911 100755 --- a/docs/_themes/kr/static/flasky.css_t +++ b/docs/_themes/kr/static/flasky.css_t @@ -14,9 +14,10 @@ /* -- page layout ----------------------------------------------------------- */ body { - font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; + font-family: "Georgia", "Open Sans", OpenSansRegular, sans-serif; font-size: 17px; - background-color: whitesmoke; + background-color: white; + font-weight: 400; color: #000; margin: 0; padding: 0; @@ -45,7 +46,7 @@ hr { } div.body { - background-color: whitesmoke; + background-color: white; color: #3E4349; padding: 0 30px 0 30px; } @@ -98,7 +99,7 @@ div.sphinxsidebarwrapper p.logo { div.sphinxsidebar h3, div.sphinxsidebar h4 { - font-family: 'Garamond', 'Georgia', serif; + font-family: 'Antic Slab' ,'Garamond', 'Georgia', serif; color: #444; font-size: 24px; font-weight: normal; @@ -127,7 +128,7 @@ div.sphinxsidebar p { } div.sphinxsidebar ul { - margin: 10px 0; + margin: 10px 0 30px; padding: 0; color: #000; } @@ -156,10 +157,11 @@ div.body h3, div.body h4, div.body h5, div.body h6 { - font-family: 'Garamond', 'Georgia', serif; + font-family: 'Antic Slab', "Open Sans", OpenSansRegular, sans-serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; + text-shadow: 1px 1px 3px #ddd; } div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } @@ -244,9 +246,14 @@ p.admonition-title:after { content: ":"; } -pre, tt { +pre { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; + font-size: 0.8em; +} + +tt { + font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.95em; } img.screenshot { @@ -359,6 +366,7 @@ tt { tt.xref, a tt { background-color: #FBFBFB; + color: #2277bb; border-bottom: 1px solid white; } diff --git a/docs/index.rst b/docs/index.rst index 0340555..75b4f4c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,14 +6,31 @@ dataset: databases for humans ============================= +dataset is a ETL +Getting the databases out of your data's way:: -Getting the databases out of your data's way. + import dataset + + db = dataset.connect('sqlite:///weather.db') + db['temperature'].find() + +Features include: + +* **Automatic schema**. If a table or column is written that does not + exist in the database, it will be created automatically. +* **Upserts**. Records are either created or updated, depdending on + whether an existing version can be found. +* **Query helpers** for simple queries such as all rows in a table or + all distinct values across a set of columns. .. toctree:: :maxdepth: 2 -* `Learn how to use dataset in five minutes `_ -* `Browse the complete API docs `_ +Next steps: + +`Learn how to use dataset in five minutes `_ + +`Browse the complete API docs `_ From adc5d3201263eb88a42fe01335ae4f63958706e9 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Tue, 2 Apr 2013 11:24:35 +0200 Subject: [PATCH 04/30] docs --- docs/_themes/kr/static/flasky.css_t | 14 +++++++------- docs/index.rst | 6 ++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/_themes/kr/static/flasky.css_t b/docs/_themes/kr/static/flasky.css_t index c5bf911..dcadc82 100755 --- a/docs/_themes/kr/static/flasky.css_t +++ b/docs/_themes/kr/static/flasky.css_t @@ -15,7 +15,7 @@ body { font-family: "Georgia", "Open Sans", OpenSansRegular, sans-serif; - font-size: 17px; + font-size: 16px; background-color: white; font-weight: 400; color: #000; @@ -164,12 +164,12 @@ div.body h6 { text-shadow: 1px 1px 3px #ddd; } -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } +div.body h1 { margin-top: 0; padding-top: 0; font-size: 250%; } +div.body h2 { font-size: 190%; } +div.body h3 { font-size: 160%; } +div.body h4 { font-size: 140%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 110%; } a.headerlink { color: #ddd; diff --git a/docs/index.rst b/docs/index.rst index 75b4f4c..a24d8c4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,6 @@ dataset: databases for humans ============================= -dataset is a ETL Getting the databases out of your data's way:: import dataset @@ -28,9 +27,8 @@ Features include: Next steps: -`Learn how to use dataset in five minutes `_ - -`Browse the complete API docs `_ +* Quickstart: `Learn how to use dataset in five minutes `_ +* API: `Browse the complete API docs `_ From 4d503f42e3682bffe06c85b5b1aedf311e6db819 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Tue, 2 Apr 2013 13:17:30 +0200 Subject: [PATCH 05/30] allow sorting by multiple columns and descending order --- dataset/persistence/table.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dataset/persistence/table.py b/dataset/persistence/table.py index 14263a9..38f6e36 100644 --- a/dataset/persistence/table.py +++ b/dataset/persistence/table.py @@ -142,6 +142,12 @@ class Table(object): return None return res[0] + def _args_to_order_by(self, order_by): + if order_by[0] == '-': + return self.table.c[order_by[1:]].desc() + else: + return self.table.c[order_by].asc() + def find(self, _limit=None, _step=5000, _offset=0, order_by='id', **filter): """Performs a simple search on the table. @@ -151,11 +157,16 @@ class Table(object): results = table.find(country='France', year=1980) # just return the first 10 rows results = table.find(country='France', _limit=10) - # sort results by a column + # sort results by a column 'year' results = table.find(country='France', order_by='year') + # return all rows sorted by multiple columns (by year in descending order) + results = table.find(order_by=['country', '-year']) For more complex queries, please use :py:meth:`db.query() ` instead.""" - order_by = [self.table.c[order_by].asc()] + if isinstance(order_by, (str, unicode)): + order_by = [order_by] + order_by = [self._args_to_order_by(o) for o in order_by] + args = self._args_to_clause(filter) for i in count(): From f52c7da45b275de5449b42b343855c17231514fd Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Tue, 2 Apr 2013 13:17:41 +0200 Subject: [PATCH 06/30] docs --- docs/_static/knight_mozilla_on.jpg | Bin 0 -> 5925 bytes docs/_themes/kr/layout.html | 18 +++++++++++++++--- docs/_themes/kr/static/flasky.css_t | 16 +++++++++++----- docs/conf.py | 2 +- docs/index.rst | 25 +++++++++---------------- docs/quickstart.rst | 3 --- 6 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 docs/_static/knight_mozilla_on.jpg diff --git a/docs/_static/knight_mozilla_on.jpg b/docs/_static/knight_mozilla_on.jpg new file mode 100644 index 0000000000000000000000000000000000000000..90f256a403842b4c16184ecaafb589678308dff7 GIT binary patch literal 5925 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 NR}cSL5Ad@5e*l!)0_p$& literal 0 HcmV?d00001 diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html index 0907a52..c7b9264 100755 --- a/docs/_themes/kr/layout.html +++ b/docs/_themes/kr/layout.html @@ -7,13 +7,25 @@ {% endif %} {% endblock %} -{%- block relbar2 %}{% endblock %} + +{% block sidebarlogo %} + +{% endblock %} + +{% block sidebar2 %} + {{ sidebar() }} + +{% endblock %} + {%- block footer %} - - Fork me on GitHub +
+ +
+
+ Fork me on GitHub diff --git a/docs/_themes/kr/static/flasky.css_t b/docs/_themes/kr/static/flasky.css_t index dcadc82..363963e 100755 --- a/docs/_themes/kr/static/flasky.css_t +++ b/docs/_themes/kr/static/flasky.css_t @@ -16,7 +16,7 @@ body { font-family: "Georgia", "Open Sans", OpenSansRegular, sans-serif; font-size: 16px; - background-color: white; + background: #fff; font-weight: 400; color: #000; margin: 0; @@ -100,7 +100,7 @@ div.sphinxsidebarwrapper p.logo { div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Antic Slab' ,'Garamond', 'Georgia', serif; - color: #444; + color: #000; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; @@ -112,7 +112,7 @@ div.sphinxsidebar h4 { } div.sphinxsidebar h3 a { - color: #444; + color: #000; } div.sphinxsidebar p.logo a, @@ -157,11 +157,12 @@ div.body h3, div.body h4, div.body h5, div.body h6 { - font-family: 'Antic Slab', "Open Sans", OpenSansRegular, sans-serif; + font-family: 'Antic Slab', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; text-shadow: 1px 1px 3px #ddd; + color: #000; } div.body h1 { margin-top: 0; padding-top: 0; font-size: 250%; } @@ -248,7 +249,7 @@ p.admonition-title:after { pre { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.8em; + font-size: 0.88em; } tt { @@ -541,4 +542,9 @@ a:hover tt { .revsys-inline { display: none!important; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 140px; + padding: 4px 3px; } \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 7e17d1b..c299880 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -152,7 +152,7 @@ html_static_path = ['_static'] #html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True diff --git a/docs/index.rst b/docs/index.rst index a24d8c4..478b0d7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,8 +3,8 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -dataset: databases for humans -============================= +dataset: databases for busy nerds +================================= Getting the databases out of your data's way:: @@ -13,7 +13,8 @@ Getting the databases out of your data's way:: db = dataset.connect('sqlite:///weather.db') db['temperature'].find() -Features include: +Features +-------- * **Automatic schema**. If a table or column is written that does not exist in the database, it will be created automatically. @@ -22,20 +23,12 @@ Features include: * **Query helpers** for simple queries such as all rows in a table or all distinct values across a set of columns. +Contents +-------- + .. toctree:: :maxdepth: 2 -Next steps: - -* Quickstart: `Learn how to use dataset in five minutes `_ -* API: `Browse the complete API docs `_ - - - -Indices and tables ------------------- - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + quickstart + api diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8623841..c9aba63 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -3,9 +3,6 @@ Quickstart ========== -.. toctree:: - :maxdepth: 2 - Hi, welcome to the five-minute quick-start tutorial. At first you need to import the dataset package :) :: From f8e6d53bb5f970f3ae966bc6ed0fb65c3aa44931 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Tue, 2 Apr 2013 13:44:14 +0200 Subject: [PATCH 07/30] docs! --- dataset/persistence/table.py | 11 +++++++++-- docs/_themes/kr/static/flasky.css_t | 8 ++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/dataset/persistence/table.py b/dataset/persistence/table.py index 38f6e36..d7c0901 100644 --- a/dataset/persistence/table.py +++ b/dataset/persistence/table.py @@ -38,7 +38,11 @@ class Table(object): matching the name of a column to be created, and the given SQLAlchemy column type will be used. Otherwise, the type is guessed from the row's value, defaulting to a simple unicode - field. """ + field. + :: + data = dict(id=10, title='I am a banana!') + table.insert(data, ['id']) + """ if ensure: self._ensure_columns(row, types=types) self.database.engine.execute(self.table.insert(row)) @@ -133,7 +137,7 @@ class Table(object): return idx def find_one(self, **filter): - """Works just like :py:meth:`find() ` but returns only the first result. + """Works just like :py:meth:`find() ` but returns only one result. :: row = table.find_one(country='United States') """ @@ -157,6 +161,9 @@ class Table(object): results = table.find(country='France', year=1980) # just return the first 10 rows results = table.find(country='France', _limit=10) + + You can sort the results by single or multiple columns. For descending order + please append a minus sign to the column name:: # sort results by a column 'year' results = table.find(country='France', order_by='year') # return all rows sorted by multiple columns (by year in descending order) diff --git a/docs/_themes/kr/static/flasky.css_t b/docs/_themes/kr/static/flasky.css_t index 363963e..40b6da6 100755 --- a/docs/_themes/kr/static/flasky.css_t +++ b/docs/_themes/kr/static/flasky.css_t @@ -350,13 +350,13 @@ pre { } dl pre, blockquote pre, li pre { - margin-left: -60px; - padding-left: 60px; + margin-left: 0px; + padding-left: 15px; } dl dl pre { - margin-left: -90px; - padding-left: 90px; + margin-left: 0px; + padding-left: 15px; } tt { From b5a759087adf80dd217fe0418cefd3d7d2eb0e5f Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Tue, 2 Apr 2013 23:45:44 +0200 Subject: [PATCH 08/30] docs! --- dataset/persistence/table.py | 11 ++++++----- docs/_themes/kr/autotoc.html | 25 +++++++++++++++++++++++++ docs/_themes/kr/layout.html | 5 ----- docs/_themes/kr/sidebarlogo.html | 3 +++ docs/_themes/kr/static/flasky.css_t | 4 ++-- docs/api.rst | 11 ----------- docs/conf.py | 8 ++++++-- docs/index.rst | 29 ++++++++++++++++++++++------- docs/quickstart.rst | 18 ++++++++---------- 9 files changed, 72 insertions(+), 42 deletions(-) create mode 100644 docs/_themes/kr/autotoc.html create mode 100644 docs/_themes/kr/sidebarlogo.html diff --git a/dataset/persistence/table.py b/dataset/persistence/table.py index d7c0901..6de7de1 100644 --- a/dataset/persistence/table.py +++ b/dataset/persistence/table.py @@ -152,18 +152,19 @@ class Table(object): else: return self.table.c[order_by].asc() - def find(self, _limit=None, _step=5000, _offset=0, + def find(self, _limit=None, _offset=0, _step=5000, order_by='id', **filter): - """Performs a simple search on the table. + """Performs a simple search on the table. Simply pass keyword arguments as ``filter``. :: results = table.find(country='France') - # combining multiple conditions (AND) results = table.find(country='France', year=1980) + + Using # just return the first 10 rows results = table.find(country='France', _limit=10) - You can sort the results by single or multiple columns. For descending order - please append a minus sign to the column name:: + You can sort the results by single or multiple columns. Append a minus sign + to the column name for descending order:: # sort results by a column 'year' results = table.find(country='France', order_by='year') # return all rows sorted by multiple columns (by year in descending order) diff --git a/docs/_themes/kr/autotoc.html b/docs/_themes/kr/autotoc.html new file mode 100644 index 0000000..3e1ab00 --- /dev/null +++ b/docs/_themes/kr/autotoc.html @@ -0,0 +1,25 @@ +

{{ _('Table Of Contents') }}

+ + +
    + + + diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html index c7b9264..d0e435c 100755 --- a/docs/_themes/kr/layout.html +++ b/docs/_themes/kr/layout.html @@ -8,9 +8,6 @@ {% endblock %} -{% block sidebarlogo %} - -{% endblock %} {% block sidebar2 %} {{ sidebar() }} @@ -27,6 +24,4 @@ Fork me on GitHub - - {%- endblock %} diff --git a/docs/_themes/kr/sidebarlogo.html b/docs/_themes/kr/sidebarlogo.html new file mode 100644 index 0000000..e8f615d --- /dev/null +++ b/docs/_themes/kr/sidebarlogo.html @@ -0,0 +1,3 @@ +
    + + \ No newline at end of file diff --git a/docs/_themes/kr/static/flasky.css_t b/docs/_themes/kr/static/flasky.css_t index 40b6da6..c53de79 100755 --- a/docs/_themes/kr/static/flasky.css_t +++ b/docs/_themes/kr/static/flasky.css_t @@ -103,7 +103,7 @@ div.sphinxsidebar h4 { color: #000; font-size: 24px; font-weight: normal; - margin: 0 0 5px 0; + margin: 30px 0 5px 0; padding: 0; } @@ -128,7 +128,7 @@ div.sphinxsidebar p { } div.sphinxsidebar ul { - margin: 10px 0 30px; + margin: 10px 0px; padding: 0; color: #000; } diff --git a/docs/api.rst b/docs/api.rst index 56d428e..01dd01e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2,15 +2,8 @@ API documentation ================= - -.. toctree:: - :maxdepth: 2 - - - .. autofunction:: dataset.connect - Database -------- @@ -18,13 +11,9 @@ Database :members: get_table, create_table, load_table, query :undoc-members: - - Table ----- -Using the *Table* class you can easily store and retreive data from database tables. - .. autoclass:: dataset.Table :members: :undoc-members: diff --git a/docs/conf.py b/docs/conf.py index c299880..77e5223 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -100,7 +100,7 @@ html_theme = 'kr' # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { -# 'codebgcolor': '' +# 'stickysidebar': "true" # } # Add any paths that contain custom themes here, relative to this directory. @@ -136,7 +136,11 @@ html_static_path = ['_static'] #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +html_sidebars = { + 'index': ['sidebarlogo.html', 'sourcelink.html', 'searchbox.html'], + 'api': ['sidebarlogo.html', 'autotoc.html', 'sourcelink.html', 'searchbox.html'], + '**': ['sidebarlogo.html', 'localtoc.html', 'sourcelink.html', 'searchbox.html'] +} # Additional templates that should be rendered to pages, maps page names to # template names. diff --git a/docs/index.rst b/docs/index.rst index 478b0d7..ed29ca7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,22 +3,37 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -dataset: databases for busy nerds -================================= +dataset: databases for lazy people +================================== -Getting the databases out of your data's way:: +.. toctree:: + :hidden: + + +Although managing data in relational database has plenty of benefits, we find them rarely being used in the typical day-to-day work with small to medium scale datasets. But why is that? Why do we see an awful lot of data stored in static files in CSV or JSON format? + +Because **good programmers are lazy**, and thus they tend to prefer the easiest solution they find. And managing data in a databases simply wasn't the simplest solution to store a bunch of structured data. This is where ``dataset`` steps in! + +Dataset is here to **take the pain out of databases**. It makes reading and writing data in databases as simple as reading and writing JSON files. + +In short, :: import dataset - db = dataset.connect('sqlite:///weather.db') - db['temperature'].find() + db = dataset.connect('sqlite:///database.db') + db['sometable'].insert(dict(name='John Doe', age=37)) + db['sometable'].insert(dict(name='Jane Doe', age=34, gender='female')) + + +Now look at `similar code, without dataset `_. + Features -------- -* **Automatic schema**. If a table or column is written that does not +* **Automatic schema**: If a table or column is written that does not exist in the database, it will be created automatically. -* **Upserts**. Records are either created or updated, depdending on +* **Upserts**: Records are either created or updated, depdending on whether an existing version can be found. * **Query helpers** for simple queries such as all rows in a table or all distinct values across a set of columns. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index c9aba63..ba9879e 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -5,36 +5,34 @@ Quickstart Hi, welcome to the five-minute quick-start tutorial. +Connecting to a database +------------------------ + At first you need to import the dataset package :) :: import dataset -To connect to a database you need to identify it using what is called an engine url. Here are a few examples:: +To connect to a database you need to identify it by its `URL `_, which basically is a string of the form ``"dialect://user:password@host/dbname"``. Here are a few common examples:: # connecting to a SQLite database - db = dataset.connect('sqlite:///factbook.db') + db = dataset.connect('sqlite:///mydatabase.db') - # connecting to a MySQL database + # connecting to a MySQL database with user and password db = dataset.connect('mysql://user:password@localhost/mydatabase') # connecting to a PostgreSQL database db = dataset.connect('postgresql://scott:tiger@localhost:5432/mydatabase') -If you want to learn more about how to connect to various databases, have a look at the `SQLAlchemy documentation`_. - -.. _SQLAlchemy documentation: http://docs.sqlalchemy.org/en/latest/core/engines.html#engine-creation-api Storing data ------------ -At first you need to get a reference to the table in which you want to store your data. You don't -need to worry about whether the table already exists or not, since dataset will create it automatically:: +To store some data you need to get a reference to a table. You don't need to worry about whether the table already exists or not, since dataset will create it automatically:: # get a reference to the table 'person' table = db['person'] -Now storing data in a table is a matter of a single function call. Just pass a `dict`_ to *insert*. Note -that you don't need to create the columns *name* and *age* – dataset will do this automatically:: +Now storing data in a table is a matter of a single function call. Just pass a `dict`_ to *insert*. Note that you don't need to create the columns *name* and *age* – dataset will do this automatically:: # Insert a new record. table.insert(dict(name='John Doe', age=46)) From d240a45e77f6723ad618f5078342b69d050d6278 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 00:24:23 +0200 Subject: [PATCH 09/30] docs --- docs/index.rst | 13 +++++++------ docs/quickstart.rst | 12 ++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index ed29ca7..4acaddc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,11 +12,11 @@ dataset: databases for lazy people Although managing data in relational database has plenty of benefits, we find them rarely being used in the typical day-to-day work with small to medium scale datasets. But why is that? Why do we see an awful lot of data stored in static files in CSV or JSON format? -Because **good programmers are lazy**, and thus they tend to prefer the easiest solution they find. And managing data in a databases simply wasn't the simplest solution to store a bunch of structured data. This is where ``dataset`` steps in! +Because **programmers are lazy**, and thus they tend to prefer the easiest solution they find. And managing data in a databases simply wasn't the simplest solution to store a bunch of structured data. This is where ``dataset`` steps in! Dataset is here to **take the pain out of databases**. It makes reading and writing data in databases as simple as reading and writing JSON files. -In short, :: +:: import dataset @@ -25,7 +25,7 @@ In short, :: db['sometable'].insert(dict(name='Jane Doe', age=34, gender='female')) -Now look at `similar code, without dataset `_. +Here is `similar code, without dataset `_. Features @@ -33,10 +33,11 @@ Features * **Automatic schema**: If a table or column is written that does not exist in the database, it will be created automatically. -* **Upserts**: Records are either created or updated, depdending on +* **Upserts**: Records are either created or updated, depending on whether an existing version can be found. -* **Query helpers** for simple queries such as all rows in a table or - all distinct values across a set of columns. +* **Query helpers** for simple queries such as :py:meth:`all ` rows in a table or + all :py:meth:`distinct ` values across a set of columns. +* **Compatibility**: Being built on top of `SQLAlchemy `_, ``dataset`` works with all major databases, such as SQLite, PostgreSQL and MySQL. Contents -------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index ba9879e..c1ca514 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -69,14 +69,14 @@ Searching for specific entries:: # Returns all items table.find(country='China') -Querying data -------------- +Running custom SQL queries +-------------------------- -Querying data is easy. Dataset returns an iteratable result object:: +Of course the main reason you're using a database is that you want to use the full power of SQL queries. Here's how you run them using dataset:: - result = db.query('SELECT ...') - for row in result: - print row + result = db.query('SELECT country, COUNT(*) cnt FROM population GROUP BY year') + for row in res: + print row.country, row.cnt Freezing your data ------------------ From e0b66510aba1fe5f59af4298387b572e99c067d4 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 00:51:33 +0200 Subject: [PATCH 10/30] removed sphinx warnings --- dataset/__init__.py | 4 ++- dataset/persistence/database.py | 30 ++++++++++------ dataset/persistence/table.py | 47 ++++++++++++++++++++------ docs/_themes/{README.rst => README.md} | 6 ++-- docs/api.rst | 9 ++--- docs/quickstart.rst | 6 ++-- 6 files changed, 69 insertions(+), 33 deletions(-) rename docs/_themes/{README.rst => README.md} (83%) diff --git a/dataset/__init__.py b/dataset/__init__.py index 83b0b0c..74d6bc1 100644 --- a/dataset/__init__.py +++ b/dataset/__init__.py @@ -8,9 +8,11 @@ from dataset.persistence.table import Table def connect(url): - """ Opens a new connection to a database. *url* can be any valid `SQLAlchemy engine URL`_. Returns + """ + Opens a new connection to a database. *url* can be any valid `SQLAlchemy engine URL`_. Returns an instance of :py:class:`Database `. :: + db = dataset.connect('sqlite:///factbook.db') .. _SQLAlchemy Engine URL: http://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine diff --git a/dataset/persistence/database.py b/dataset/persistence/database.py index 8dbc242..8baa601 100644 --- a/dataset/persistence/database.py +++ b/dataset/persistence/database.py @@ -36,12 +36,13 @@ class Database(object): return set(self.metadata.tables.keys() + self._tables.keys()) def create_table(self, table_name): - """ Creates a new table. The new table will automatically have - an `id` column, which is set to be an auto-incrementing integer - as the primary key of the table. + """ + Creates a new table. The new table will automatically have an `id` column, which is + set to be an auto-incrementing integer as the primary key of the table. Returns a :py:class:`Table ` instance. :: + table = db.create_table('population') """ with self.lock: @@ -54,14 +55,17 @@ class Database(object): return Table(self, table) def load_table(self, table_name): - """ Loads a table. This will fail if the tables does not already + """ + Loads a table. This will fail if the tables does not already exist in the database. If the table exists, its columns will be reflected and are available on the :py:class:`Table ` object. Returns a :py:class:`Table ` instance. :: - table = db.load_table('population')""" + + table = db.load_table('population') + """ with self.lock: log.debug("Loading table: %s on %r" % (table_name, self)) table = SQLATable(table_name, self.metadata, autoload=True) @@ -69,11 +73,13 @@ class Database(object): return Table(self, table) def get_table(self, table_name): - """ Smart wrapper around *load_table* and *create_table*. Either loads a table + """ + Smart wrapper around *load_table* and *create_table*. Either loads a table or creates it if it doesn't exist yet. Returns a :py:class:`Table ` instance. :: + table = db.get_table('population') # you can also use the short-hand syntax: table = db['population'] @@ -90,14 +96,16 @@ class Database(object): return self.get_table(table_name) def query(self, query): - """ Run a statement on the database directly, allowing for the + """ + Run a statement on the database directly, allowing for the execution of arbitrary read/write queries. A query can either be - a plain text string, or a SQLAlchemy expression. The returned + a plain text string, or a `SQLAlchemy expression `_. The returned iterator will yield each result sequentially. :: - result = db.query('SELECT * FROM population WHERE population > 10000000') - for row in result: - print row + + res = db.query('SELECT user, COUNT(*) c FROM photos GROUP BY user') + for row in res: + print row['user'], row['c'] """ return resultiter(self.engine.execute(query)) diff --git a/dataset/persistence/table.py b/dataset/persistence/table.py index 6de7de1..5b5bb78 100644 --- a/dataset/persistence/table.py +++ b/dataset/persistence/table.py @@ -17,20 +17,28 @@ class Table(object): self.database = database self.table = table + @property + def columns(self): + """ Get a listing of all columns that exist in the table. """ + return set(self.table.columns.keys()) + def drop(self): - """ Drop the table from the database, deleting both the schema + """ + Drop the table from the database, deleting both the schema and all the contents within it. Note: the object will be in an unusable state after using this command and should not be used again. If you want to re-create the table, make sure to get a fresh instance from the - :py:class:`Database `. """ + :py:class:`Database `. + """ with self.database.lock: self.database.tables.pop(self.table.name, None) self.table.drop(engine) def insert(self, row, ensure=True, types={}): - """ Add a row (type: dict) by inserting it into the table. + """ + Add a row (type: dict) by inserting it into the table. If ``ensure`` is set, any of the keys of the row are not table columns, they will be created automatically. @@ -40,6 +48,7 @@ class Table(object): guessed from the row's value, defaulting to a simple unicode field. :: + data = dict(id=10, title='I am a banana!') table.insert(data, ['id']) """ @@ -48,11 +57,13 @@ class Table(object): self.database.engine.execute(self.table.insert(row)) def update(self, row, keys, ensure=True, types={}): - """ Update a row in the table. The update is managed via + """ + Update a row in the table. The update is managed via the set of column names stated in ``keys``: they will be used as filters for the data to be updated, using the values in ``row``. :: + # update all entries with id matching 10, setting their title columns data = dict(id=10, title='I am a banana!') table.update(data, ['id']) @@ -75,9 +86,11 @@ class Table(object): return False def upsert(self, row, keys, ensure=True, types={}): - """An UPSERT is a smart combination of insert and update. If rows with matching ``keys`` exist + """ + An UPSERT is a smart combination of insert and update. If rows with matching ``keys`` exist they will be updated, otherwise a new row is inserted in the table. :: + data = dict(id=10, title='I am a banana!') table.upsert(data, ['id']) """ @@ -88,8 +101,10 @@ class Table(object): self.insert(row, ensure=ensure, types=types) def delete(self, **filter): - """Delete rows matching the ``filter`` arguments. + """ + Delete rows matching the ``filter`` arguments. :: + table.delete(year=2010) """ q = self._args_to_clause(filter) @@ -137,8 +152,10 @@ class Table(object): return idx def find_one(self, **filter): - """Works just like :py:meth:`find() ` but returns only one result. + """ + Works just like :py:meth:`find() ` but returns only one result. :: + row = table.find_one(country='United States') """ res = list(self.find(_limit=1, **filter)) @@ -154,17 +171,21 @@ class Table(object): def find(self, _limit=None, _offset=0, _step=5000, order_by='id', **filter): - """Performs a simple search on the table. Simply pass keyword arguments as ``filter``. + """ + Performs a simple search on the table. Simply pass keyword arguments as ``filter``. :: + results = table.find(country='France') results = table.find(country='France', year=1980) - Using + Using ``_limit``:: + # just return the first 10 rows results = table.find(country='France', _limit=10) You can sort the results by single or multiple columns. Append a minus sign to the column name for descending order:: + # sort results by a column 'year' results = table.find(country='France', order_by='year') # return all rows sorted by multiple columns (by year in descending order) @@ -197,9 +218,11 @@ class Table(object): return d.values().pop() def distinct(self, *columns, **filter): - """Returns all rows of a table, but removes rows in with duplicate values in ``columns`. + """ + Returns all rows of a table, but removes rows in with duplicate values in ``columns``. Interally this creates a `DISTINCT statement `_. :: + # returns only one row per year, ignoring the rest table.distinct('year') # works with multiple columns, too @@ -221,8 +244,10 @@ class Table(object): return self.database.query(q) def all(self): - """Returns all rows of the table as simple dictionaries. This is simply a shortcut + """ + Returns all rows of the table as simple dictionaries. This is simply a shortcut to *find()* called with no arguments. :: + rows = table.all()""" return self.find() diff --git a/docs/_themes/README.rst b/docs/_themes/README.md similarity index 83% rename from docs/_themes/README.rst rename to docs/_themes/README.md index dd8d7c0..91f0d6d 100755 --- a/docs/_themes/README.rst +++ b/docs/_themes/README.md @@ -6,14 +6,14 @@ his projects. It is a derivative of Mitsuhiko's themes for Flask and Flask relat projects. To use this style in your Sphinx documentation, follow this guide: -1. put this folder as _themes into your docs folder. Alternatively +1. put this folder as _themes into your docs folder. Alternatively you can also use git submodules to check out the contents there. -2. add this to your conf.py: :: +2. add this to your conf.py: sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] - html_theme = 'flask' + html_theme = 'kr' The following themes exist: diff --git a/docs/api.rst b/docs/api.rst index 01dd01e..bf4b64b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -8,12 +8,13 @@ Database -------- .. autoclass:: dataset.Database - :members: get_table, create_table, load_table, query - :undoc-members: + :members: get_table, create_table, load_table, query, tables + :special-members: + Table ----- .. autoclass:: dataset.Table - :members: - :undoc-members: + :members: columns, drop, insert, update, upsert, find, find_one, distinct, create_column, create_index, all + :special-members: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index c1ca514..1afd111 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -74,9 +74,9 @@ Running custom SQL queries Of course the main reason you're using a database is that you want to use the full power of SQL queries. Here's how you run them using dataset:: - result = db.query('SELECT country, COUNT(*) cnt FROM population GROUP BY year') - for row in res: - print row.country, row.cnt + result = db.query('SELECT user, COUNT(*) c FROM photos GROUP BY user ORDER BY c DESC') + for row in result: + print row['user'], row['c'] Freezing your data ------------------ From 4e78c963704b456e8e010e81e16844639fb18eba Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 00:56:07 +0200 Subject: [PATCH 11/30] KeyError wasn't evaluated --- dataset/persistence/table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataset/persistence/table.py b/dataset/persistence/table.py index 5b5bb78..3f8f958 100644 --- a/dataset/persistence/table.py +++ b/dataset/persistence/table.py @@ -82,7 +82,7 @@ class Table(object): stmt = self.table.update(filters, row) rp = self.database.engine.execute(stmt) return rp.rowcount > 0 - except KeyError, ke: + except KeyError: return False def upsert(self, row, keys, ensure=True, types={}): From c284d05bd6f057f3c61b1de0cb0b706657bfec38 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 01:48:26 +0200 Subject: [PATCH 12/30] docs! --- dataset/persistence/table.py | 19 +++++++++- docs/index.rst | 6 +-- docs/quickstart.rst | 71 +++++++++++++++++++++++++++--------- 3 files changed, 74 insertions(+), 22 deletions(-) diff --git a/dataset/persistence/table.py b/dataset/persistence/table.py index 3f8f958..adaf4f2 100644 --- a/dataset/persistence/table.py +++ b/dataset/persistence/table.py @@ -19,7 +19,12 @@ class Table(object): @property def columns(self): - """ Get a listing of all columns that exist in the table. """ + """ + Get a listing of all columns that exist in the table. + + >>> print 'age' in table.columns + True + """ return set(self.table.columns.keys()) def drop(self): @@ -129,6 +134,12 @@ class Table(object): return and_(*clauses) def create_column(self, name, type): + """ + Explicitely create a new column ``name`` of a specified type. ``type`` must be a `SQLAlchemy column type `_. + :: + + table.create_column('person', sqlalchemy.String) + """ with self.database.lock: if name not in self.table.columns.keys(): col = Column(name, type) @@ -136,6 +147,12 @@ class Table(object): connection=self.database.engine) def create_index(self, columns, name=None): + """ + Create an index to speed up queries on a table. If no ``name`` is given a random name is created. + :: + + table.create_index(['name', 'country']) + """ with self.database.lock: if not name: sig = abs(hash('||'.join(columns))) diff --git a/docs/index.rst b/docs/index.rst index 4acaddc..3c3bfb8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,9 +12,9 @@ dataset: databases for lazy people Although managing data in relational database has plenty of benefits, we find them rarely being used in the typical day-to-day work with small to medium scale datasets. But why is that? Why do we see an awful lot of data stored in static files in CSV or JSON format? -Because **programmers are lazy**, and thus they tend to prefer the easiest solution they find. And managing data in a databases simply wasn't the simplest solution to store a bunch of structured data. This is where ``dataset`` steps in! +Because **programmers are lazy**, and thus they tend to prefer the easiest solution they find. And managing data in a databases simply wasn't the simplest solution to store a bunch of structured data. This is where **dataset** steps in! -Dataset is here to **take the pain out of databases**. It makes reading and writing data in databases as simple as reading and writing JSON files. +In short, **dataset** makes reading and writing data in databases as simple as reading and writing JSON files. :: @@ -37,7 +37,7 @@ Features whether an existing version can be found. * **Query helpers** for simple queries such as :py:meth:`all ` rows in a table or all :py:meth:`distinct ` values across a set of columns. -* **Compatibility**: Being built on top of `SQLAlchemy `_, ``dataset`` works with all major databases, such as SQLite, PostgreSQL and MySQL. +* **Compatibility**: Being built on top of `SQLAlchemy `_, ``dataset` works with all major databases, such as SQLite, PostgreSQL and MySQL. Contents -------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 1afd111..47810e1 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -3,7 +3,7 @@ Quickstart ========== -Hi, welcome to the five-minute quick-start tutorial. +Hi, welcome to the twelve-minute quick-start tutorial. Connecting to a database ------------------------ @@ -49,37 +49,72 @@ Updating existing entries is easy, too:: table.update(dict(name='John Doe', age=47), ['name']) +Inspecting databases and tables +------------------------------- + +When dealing with unknown databases we might want to check its structure first. To begin with, let's find out what tables are stored in the database: + + >>> print db.tables + set([u'user', u'action']) + +Now, let's list all columns available in the table ``user``: + + >>> print db['user'].columns + set([u'id', u'name', u'email', u'pwd', u'country']) + +Using ``len()`` we can get the total number of rows in a table: + + >>> print len(db['user']) + 187 + Reading data from tables ------------------------ -Checking:: +Now let's get some real data out of the table:: - table = db['population'] - - # Let's grab a list of all items/rows/entries in the table: - table.all() - - table.distinct() + users = db['user'].all() Searching for specific entries:: - # Returns the first item where the column country equals 'China' - table.find_one(country='China') + # All users from China + users = table.find(country='China') + + # Get a specific user + john = table.find_one(email='john.doe@example.org') + +Using :py:meth:`distinct() ` we can grab a set of rows with unique values in one or more columns:: + + # Get one user per country + db['user'].distinct('country') - # Returns all items - table.find(country='China') Running custom SQL queries -------------------------- -Of course the main reason you're using a database is that you want to use the full power of SQL queries. Here's how you run them using dataset:: +Of course the main reason you're using a database is that you want to use the full power of SQL queries. Here's how you run them with ``dataset``:: - result = db.query('SELECT user, COUNT(*) c FROM photos GROUP BY user ORDER BY c DESC') + result = db.query('SELECT country, COUNT(*) c FROM user GROUP BY country') for row in result: - print row['user'], row['c'] - -Freezing your data ------------------- + print row['country'], row['c'] +Exporting your data +------------------- +While playing around with your database in Python is a nice thing, sometimes we want to use our data –or parts of it– elsewhere, say in a interactive web application. Therefor ``dataset`` supports serializing rows of data into static files such as JSON using the :py:meth:`freeze() ` function:: + + # export all users into a single JSON + result = db['users'].all() + dataset.freeze(result, 'users.json') + +You can create one file per row by setting ``mode`` to "item":: + + # export one JSON file per user + dataset.freeze(result, 'users/{{ id }}.json', mode='item') + + +Since this is a common operation we made it available via command line utility ``datafreeze``. Read more about the `freezefile markup `_. + +.. code-block:: bash + + $ datafreeze freezefile.yaml From 7e7d95f850abe61f12a74a3cf00eb03d3008aa47 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 01:56:57 +0200 Subject: [PATCH 13/30] updated main readme --- README.md | 96 +++---------------------------------------------------- 1 file changed, 4 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 1022772..7150117 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,8 @@ -SQLAlchemy Loading Tools -======================== +dataset: databases for lazy people +================================== -A collection of wrappers and functions to make SQLAlchemy core easier -to use in ETL applications. SQLAlchemy is used only for database -abstraction and not as an ORM, allowing users to write extraction -scripts that can work with multiple database backends. Functions -include: +In short, **dataset** makes reading and writing data in databases as simple as reading and writing JSON files. -* **Automatic schema**. If a column is written that does not - exist on the table, it will be created automatically. -* **Upserts**. Records are either created or updated, depdending on - whether an existing version can be found. -* **Query helpers** for simple queries such as all rows in a table or - all distinct values across a set of columns. - -Examples --------- - -A typical use of ``sqlaload`` would look like this: - - from sqlaload import connect, get_table, distinct, update - - engine = connect('sqlite:///customers.db') - table = get_table(engine, 'customers') - for entry in distinct(engine, table, 'post_code', 'city') - lon, lat = geocode(entry['post_code'], entry['city']) - update(entry, {'lon': lon, 'lat': lat}) - -In this example, we selected all distinct post codes and city names from an imaginary customers database, send them through our geocoding routine and finally updated all matching rows with the returned geo information. - -Another example, updating data in a datastore, might look like this: - - from sqlaload import connect, get_table, upsert - - engine = connect('sqlite:///things.db') - table = get_table(engine, 'data') - - for item in magic_data_source_that_produces_entries(): - assert 'key1' in item - assert 'key2' in item - # this will either insert or update, depending on - # whether an entry with the matching values for - # 'key1' and 'key2' already exists: - upsert(engine, table, item, ['key1', 'key2']) - - -Here's the same example, but using the object-oriented API: - - import sqlaload - - db = sqlaload.create('sqlite:///things.db') - table = db.get_table('data') - - for item in magic_data_source_that_produces_entries(): - assert 'key1' in item - assert 'key2' in item - table.upsert(item, ['key1', 'key2']) - - -Functions ---------- - -The library currently exposes the following functions: - -**Schema management** - -* ``connect(url)``, connect to a database and return an ``engine``. See the [SQLAlchemy documentation](http://docs.sqlalchemy.org/en/rel_0_8/core/engines.html#database-urls) for information about URL schemes and formats. -* ``get_table(engine, table_name)`` will load a table configuration from the database, either reflecting the existing schema or creating a new table (with an ``id`` column). -* ``create_table(engine, table_name)`` and ``load_table(engine, table_name)`` are more explicit than ``get_table`` but allow the same functions. -* ``drop_table(engine, table_name)`` will remove an existing table, deleting all of its contents. -* ``create_column(engine, table, column_name, type)`` adds a new column to a table, ``type`` must be a SQLAlchemy type class. -* ``create_index(engine, table, columns)`` creates an index on the given table, based on a list of strings to specify the included ``columns``. - -**Queries** - -* ``find(engine, table, _limit=N, _offset=N, order_by='id', **kw)`` will retrieve database records. The query will return an iterator that only loads 5000 records at any one time, even if ``_limit`` and ``_offset`` are specified - meaning that ``find`` can be run on tables of arbitrary size. ``order_by`` is a string column name, always returned in ascending order. Finally ``**kw`` can be used to filter columns for equality, e.g. ``find(…, category=5)``. -* ``find_one(engine, table, **kw)``, like ``find`` but will only return the first matching row or ``None`` if no matches were found. -* ``distinct(engine, table, *columns, **kw)`` will return the combined distinct values for ``columns``. ``**kw`` allows filtering the same way it does in ``find``. -* ``all``, alias for ``find`` without filter options. - -**Adding and updating data** - -* ``add_row(engine, table, row, ensure=True, types={})`` add the values in the dictionary ``row`` to the given ``table``. ``ensure`` will check the schema and create the columns if necessary, their types can be specified using the ``types`` dictionary. If no ``types`` are given, the type will be guessed from the first submitted value of the column, defaulting to a text column. -* ``update_row(engine, table, row, unique, ensure=True, types={})`` will update a row or set of rows based on the data in the ``row`` dictionary and the column names specified in ``unique``. The remaining arguments are handled like those in ``add_row``. -* ``upsert(engine, table, row, unique, ensure=True, types={})`` will combine the semantics of ``update_row`` and ``add_row`` by first attempting to update existing data and otherwise (only if no record matching on the ``unique`` keys can be found) creating a new record. -* ``delete(engine, table, **kw)`` will remove records from a table. ``**kw`` is the same as in ``find`` and can be used to limit the set of records to be removed. - - - -Feedback --------- - -Please feel free create issues on the GitHub tracker at [okfn/sqlaload](https://github.com/okfn/sqlaload/issues). For other discussions, join the [okfn-labs](http://lists.okfn.org/mailman/listinfo/okfn-labs) mailing list. +[Read the docs](https://dataset.readthedocs.org/) From 37db0fa81411bc84a0e62998bf773d8d8ad8f737 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 02:05:37 +0200 Subject: [PATCH 14/30] docs! --- docs/index.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 3c3bfb8..b160da6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,9 +12,9 @@ dataset: databases for lazy people Although managing data in relational database has plenty of benefits, we find them rarely being used in the typical day-to-day work with small to medium scale datasets. But why is that? Why do we see an awful lot of data stored in static files in CSV or JSON format? -Because **programmers are lazy**, and thus they tend to prefer the easiest solution they find. And managing data in a databases simply wasn't the simplest solution to store a bunch of structured data. This is where **dataset** steps in! +Because **programmers are lazy** they tend to prefer the easiest solution they find. And in **Python**, managing data in a databases simply wasn't the simplest solution to store a bunch of structured data. This is where **dataset** steps in! -In short, **dataset** makes reading and writing data in databases as simple as reading and writing JSON files. +*In short, dataset makes reading and writing data in databases as simple as reading and writing JSON files.* :: @@ -37,7 +37,7 @@ Features whether an existing version can be found. * **Query helpers** for simple queries such as :py:meth:`all ` rows in a table or all :py:meth:`distinct ` values across a set of columns. -* **Compatibility**: Being built on top of `SQLAlchemy `_, ``dataset` works with all major databases, such as SQLite, PostgreSQL and MySQL. +* **Compatibility**: Being built on top of `SQLAlchemy `_, ``dataset`` works with all major databases, such as SQLite, PostgreSQL and MySQL. Contents -------- From 7638e898fdbb0cb1218bfc976136e64cf103d71e Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 12:28:11 +0200 Subject: [PATCH 15/30] added code example --- dataset/persistence/database.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dataset/persistence/database.py b/dataset/persistence/database.py index 8baa601..8241f05 100644 --- a/dataset/persistence/database.py +++ b/dataset/persistence/database.py @@ -32,7 +32,11 @@ class Database(object): @property def tables(self): - """ Get a listing of all tables that exist in the database. """ + """ Get a listing of all tables that exist in the database. + + >>> print db.tables + set([u'user', u'action']) + """ return set(self.metadata.tables.keys() + self._tables.keys()) def create_table(self, table_name): From 258e3f305975e51723238f27558b3bd96373b1e7 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 12:28:32 +0200 Subject: [PATCH 16/30] Allowing for direct iteration over rows in a table --- dataset/persistence/table.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dataset/persistence/table.py b/dataset/persistence/table.py index adaf4f2..e6ca217 100644 --- a/dataset/persistence/table.py +++ b/dataset/persistence/table.py @@ -231,6 +231,9 @@ class Table(object): yield row def __len__(self): + """ + Returns the number of rows in the table. + """ d = self.database.query(self.table.count()).next() return d.values().pop() @@ -268,3 +271,14 @@ class Table(object): rows = table.all()""" return self.find() + + def __iter__(self): + """ + Allows for iterating over all rows in the table without explicelty calling :py:meth:`all() `. + :: + + for row in table: + print row + """ + for row in self.all(): + yield row From 5e5b902116d332614e387488a3f64a580dd85aab Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 12:28:42 +0200 Subject: [PATCH 17/30] updated documentation --- docs/api.rst | 2 +- docs/index.rst | 2 +- docs/quickstart.rst | 9 +++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index bf4b64b..5df5ce9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -17,4 +17,4 @@ Table .. autoclass:: dataset.Table :members: columns, drop, insert, update, upsert, find, find_one, distinct, create_column, create_index, all - :special-members: + :special-members: __len__, __iter__ diff --git a/docs/index.rst b/docs/index.rst index b160da6..a3d366f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,7 +12,7 @@ dataset: databases for lazy people Although managing data in relational database has plenty of benefits, we find them rarely being used in the typical day-to-day work with small to medium scale datasets. But why is that? Why do we see an awful lot of data stored in static files in CSV or JSON format? -Because **programmers are lazy** they tend to prefer the easiest solution they find. And in **Python**, managing data in a databases simply wasn't the simplest solution to store a bunch of structured data. This is where **dataset** steps in! +Because **programmers are lazy** they tend to prefer the easiest solution they find. And in **Python**, databases weren't the simplest solution to store a bunch of structured data. This is what **dataset** is going to change! *In short, dataset makes reading and writing data in databases as simple as reading and writing JSON files.* diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 47810e1..2e65d73 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -74,13 +74,18 @@ Now let's get some real data out of the table:: users = db['user'].all() -Searching for specific entries:: +If we simply want to iterate over all rows in a table, we can ommit :py:meth:`all() `:: + + for user in db['user']: + print user['email'] + +We can search for specific entries using :py:meth:`find() ` and :py:meth:`find_one() `:: # All users from China users = table.find(country='China') # Get a specific user - john = table.find_one(email='john.doe@example.org') + john = table.find_one(name='John Doe') Using :py:meth:`distinct() ` we can grab a set of rows with unique values in one or more columns:: From b39e4b193a2247dcaca8e43a32bd7a8efead4601 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 12:46:10 +0200 Subject: [PATCH 18/30] fine-tuning the documentation --- dataset/persistence/table.py | 3 ++- docs/index.rst | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dataset/persistence/table.py b/dataset/persistence/table.py index e6ca217..a039abb 100644 --- a/dataset/persistence/table.py +++ b/dataset/persistence/table.py @@ -274,7 +274,8 @@ class Table(object): def __iter__(self): """ - Allows for iterating over all rows in the table without explicelty calling :py:meth:`all() `. + Allows for iterating over all rows in the table without explicetly + calling :py:meth:`all() `. :: for row in table: diff --git a/docs/index.rst b/docs/index.rst index a3d366f..e779b3e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,9 +12,9 @@ dataset: databases for lazy people Although managing data in relational database has plenty of benefits, we find them rarely being used in the typical day-to-day work with small to medium scale datasets. But why is that? Why do we see an awful lot of data stored in static files in CSV or JSON format? -Because **programmers are lazy** they tend to prefer the easiest solution they find. And in **Python**, databases weren't the simplest solution to store a bunch of structured data. This is what **dataset** is going to change! +Because **programmers are lazy** they tend to prefer the easiest solution they find. And in **Python**, a database wasn't the simplest solution for storing a bunch of structured data. This is what **dataset** is going to change! -*In short, dataset makes reading and writing data in databases as simple as reading and writing JSON files.* +In short, dataset combines the straightforwardness of No-SQL interfaces with the full power and flexibility of relational databases. :: From cca2dfeb8bf2daa072ed35d7f24f38291b61c9ef Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 13:00:44 +0200 Subject: [PATCH 19/30] added sitemap --- docs/_themes/kr/sidebarlogo.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/_themes/kr/sidebarlogo.html b/docs/_themes/kr/sidebarlogo.html index e8f615d..c9e238b 100644 --- a/docs/_themes/kr/sidebarlogo.html +++ b/docs/_themes/kr/sidebarlogo.html @@ -1,3 +1,11 @@
    - \ No newline at end of file + + +

    Documention

    + + \ No newline at end of file From 8d366b385263e916a8cd4f597121004c39e705cf Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 13:01:02 +0200 Subject: [PATCH 20/30] including toc in doc index --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 77e5223..e21220e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -137,7 +137,7 @@ html_static_path = ['_static'] # Custom sidebar templates, maps document names to template names. html_sidebars = { - 'index': ['sidebarlogo.html', 'sourcelink.html', 'searchbox.html'], + 'index': ['sidebarlogo.html', 'localtoc.html', 'sourcelink.html', 'searchbox.html'], 'api': ['sidebarlogo.html', 'autotoc.html', 'sourcelink.html', 'searchbox.html'], '**': ['sidebarlogo.html', 'localtoc.html', 'sourcelink.html', 'searchbox.html'] } From 8b12c9f95a955241d1de1020bbe6814ac7c7621b Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 13:01:19 +0200 Subject: [PATCH 21/30] added contributors section to index page --- docs/index.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index e779b3e..0759872 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,9 +12,9 @@ dataset: databases for lazy people Although managing data in relational database has plenty of benefits, we find them rarely being used in the typical day-to-day work with small to medium scale datasets. But why is that? Why do we see an awful lot of data stored in static files in CSV or JSON format? -Because **programmers are lazy** they tend to prefer the easiest solution they find. And in **Python**, a database wasn't the simplest solution for storing a bunch of structured data. This is what **dataset** is going to change! +Because **programmers are lazy**, and thus they tend to prefer the easiest solution they find. And in **Python**, a database wasn't the simplest solution for storing a bunch of structured data. This is what **dataset** is going to change! -In short, dataset combines the straightforwardness of No-SQL interfaces with the full power and flexibility of relational databases. +In short, dataset combines the straightforwardness of NoSQL interfaces with the full power and flexibility of relational databases. :: @@ -48,3 +48,7 @@ Contents quickstart api +Contributors +------------ + +``dataset`` is written and maintained by `Friedrich Lindenberg `_ and `Gregor Aisch `_. Its code is largely based on the preceding libraries `sqlaload `_ and `datafreeze `_. And of course, we're standing on the `shoulders of giants `_. \ No newline at end of file From 5c6c9c67d452282462f45b98f2e0377a3e386843 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 13:18:45 +0200 Subject: [PATCH 22/30] added piwik tracking code --- docs/_themes/kr/layout.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html index d0e435c..8693628 100755 --- a/docs/_themes/kr/layout.html +++ b/docs/_themes/kr/layout.html @@ -24,4 +24,21 @@ Fork me on GitHub + + + + + {%- endblock %} From f0b34469b1392661dc28ee7001a3fa15adfdb48d Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 14:04:52 +0200 Subject: [PATCH 23/30] added the naked mole rat! --- docs/_static/dataset-logo.png | Bin 0 -> 46267 bytes docs/_themes/kr/sidebarlogo.html | 12 ++++++++---- docs/conf.py | 2 +- docs/index.rst | 6 ++++-- 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 docs/_static/dataset-logo.png diff --git a/docs/_static/dataset-logo.png b/docs/_static/dataset-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7e3b71338d781efa3a9d786602b0ec90ed435d GIT binary patch literal 46267 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 +dataset - +

    Makes managing databases as simple as reading and writing JSON files.

    -

    Documention

    + + +

    Overview

    \ No newline at end of file + + + diff --git a/docs/conf.py b/docs/conf.py index e21220e..77e5223 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -137,7 +137,7 @@ html_static_path = ['_static'] # Custom sidebar templates, maps document names to template names. html_sidebars = { - 'index': ['sidebarlogo.html', 'localtoc.html', 'sourcelink.html', 'searchbox.html'], + 'index': ['sidebarlogo.html', 'sourcelink.html', 'searchbox.html'], 'api': ['sidebarlogo.html', 'autotoc.html', 'sourcelink.html', 'searchbox.html'], '**': ['sidebarlogo.html', 'localtoc.html', 'sourcelink.html', 'searchbox.html'] } diff --git a/docs/index.rst b/docs/index.rst index 0759872..c515fa0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,7 +14,7 @@ Although managing data in relational database has plenty of benefits, we find th Because **programmers are lazy**, and thus they tend to prefer the easiest solution they find. And in **Python**, a database wasn't the simplest solution for storing a bunch of structured data. This is what **dataset** is going to change! -In short, dataset combines the straightforwardness of NoSQL interfaces with the full power and flexibility of relational databases. +In short, dataset combines the straightforwardness of NoSQL interfaces with the full power and flexibility of relational databases. It makes database management as simple as reading and writing JSON files. :: @@ -51,4 +51,6 @@ Contents Contributors ------------ -``dataset`` is written and maintained by `Friedrich Lindenberg `_ and `Gregor Aisch `_. Its code is largely based on the preceding libraries `sqlaload `_ and `datafreeze `_. And of course, we're standing on the `shoulders of giants `_. \ No newline at end of file +``dataset`` is written and maintained by `Friedrich Lindenberg `_ and `Gregor Aisch `_. Its code is largely based on the preceding libraries `sqlaload `_ and `datafreeze `_. And of course, we're standing on the `shoulders of giants `_. + +Our cute little `naked mole rat `_ was drawn by `Johannes Koch `_. \ No newline at end of file From e6844e50bda5a32d67f4954bbcd66eb43ee4daf1 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 14:08:10 +0200 Subject: [PATCH 24/30] claim modification --- docs/_themes/kr/sidebarlogo.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_themes/kr/sidebarlogo.html b/docs/_themes/kr/sidebarlogo.html index 86c7358..e55e227 100644 --- a/docs/_themes/kr/sidebarlogo.html +++ b/docs/_themes/kr/sidebarlogo.html @@ -1,6 +1,6 @@ dataset -

    Makes managing databases as simple as reading and writing JSON files.

    +

    Because managing databases in Python should be as simple as reading and writing JSON files.

    From b002f454ca2bd7e52a6af8b4c352e6a58baefcc9 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 14:28:42 +0200 Subject: [PATCH 25/30] extended intro example --- docs/index.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index c515fa0..cf620b9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,9 +20,13 @@ In short, dataset combines the straightforwardness of NoSQL interfaces with the import dataset - db = dataset.connect('sqlite:///database.db') - db['sometable'].insert(dict(name='John Doe', age=37)) - db['sometable'].insert(dict(name='Jane Doe', age=34, gender='female')) + db = dataset.connect('sqlite:///:memory:') + + table = db['sometable'] + table.insert(dict(name='John Doe', age=37)) + table.insert(dict(name='Jane Doe', age=34, gender='female')) + + john = table.find_one(name='John Doe') Here is `similar code, without dataset `_. From fb9d3d2b3802991111fa64696613c5e024d45646 Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 14:38:50 +0200 Subject: [PATCH 26/30] fixed links, added brief install guide --- docs/_themes/kr/sidebarlogo.html | 6 +- docs/_themes/kr_small/layout.html | 22 -- docs/_themes/kr_small/static/flasky.css_t | 287 ---------------------- docs/_themes/kr_small/theme.conf | 10 - docs/install.rst | 10 + 5 files changed, 13 insertions(+), 322 deletions(-) delete mode 100755 docs/_themes/kr_small/layout.html delete mode 100755 docs/_themes/kr_small/static/flasky.css_t delete mode 100755 docs/_themes/kr_small/theme.conf create mode 100644 docs/install.rst diff --git a/docs/_themes/kr/sidebarlogo.html b/docs/_themes/kr/sidebarlogo.html index e55e227..579ec42 100644 --- a/docs/_themes/kr/sidebarlogo.html +++ b/docs/_themes/kr/sidebarlogo.html @@ -7,9 +7,9 @@

    Overview

    diff --git a/docs/_themes/kr_small/layout.html b/docs/_themes/kr_small/layout.html deleted file mode 100755 index aa1716a..0000000 --- a/docs/_themes/kr_small/layout.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "basic/layout.html" %} -{% block header %} - {{ super() }} - {% if pagename == 'index' %} -
    - {% endif %} -{% endblock %} -{% block footer %} - {% if pagename == 'index' %} -
    - {% endif %} -{% endblock %} -{# do not display relbars #} -{% block relbar1 %}{% endblock %} -{% block relbar2 %} - {% if theme_github_fork %} - Fork me on GitHub - {% endif %} -{% endblock %} -{% block sidebar1 %}{% endblock %} -{% block sidebar2 %}{% endblock %} diff --git a/docs/_themes/kr_small/static/flasky.css_t b/docs/_themes/kr_small/static/flasky.css_t deleted file mode 100755 index 71961a2..0000000 --- a/docs/_themes/kr_small/static/flasky.css_t +++ /dev/null @@ -1,287 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- flasky theme based on nature theme. - * - * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'Georgia', serif; - font-size: 17px; - color: #000; - background: white; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 40px auto 0 auto; - width: 700px; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - text-align: right; - color: #888; - padding: 10px; - font-size: 14px; - width: 650px; - margin: 0 auto 40px auto; -} - -div.footer a { - color: #888; - text-decoration: underline; -} - -div.related { - line-height: 32px; - color: #888; -} - -div.related ul { - padding: 0 0 0 10px; -} - -div.related a { - color: #444; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body { - padding-bottom: 40px; /* saved for footer */ -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} - -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: white; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight{ - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.85em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td { - padding: 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -pre { - padding: 0; - margin: 15px -30px; - padding: 8px; - line-height: 1.3em; - padding: 7px 30px; - background: #eee; - border-radius: 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; -} - -dl pre { - margin-left: -60px; - padding-left: 60px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; -} - -a:hover tt { - background: #EEE; -} diff --git a/docs/_themes/kr_small/theme.conf b/docs/_themes/kr_small/theme.conf deleted file mode 100755 index 542b462..0000000 --- a/docs/_themes/kr_small/theme.conf +++ /dev/null @@ -1,10 +0,0 @@ -[theme] -inherit = basic -stylesheet = flasky.css -nosidebar = true -pygments_style = flask_theme_support.FlaskyStyle - -[options] -index_logo = '' -index_logo_height = 120px -github_fork = '' diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..ca49b85 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,10 @@ + +Installation Guide +================== + +The easiest way is to installing ``dataset`` from the `Python Package Index `_ using ``pip`` or ``easy_install``: + +.. code-block:: bash + + $ easy_install dataset + From 0d051313923c3bbd3b162bfca06f9abd45d67f4c Mon Sep 17 00:00:00 2001 From: Gregor Aisch Date: Wed, 3 Apr 2013 14:47:16 +0200 Subject: [PATCH 27/30] fixed docs, logo in footer --- docs/_themes/kr/layout.html | 5 +++-- docs/_themes/kr/sidebarlogo.html | 2 +- docs/_themes/kr/static/flasky.css_t | 4 ++++ docs/install.rst | 12 +++++++++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html index 8693628..121e296 100755 --- a/docs/_themes/kr/layout.html +++ b/docs/_themes/kr/layout.html @@ -11,11 +11,12 @@ {% block sidebar2 %} {{ sidebar() }} - {% endblock %} {%- block footer %} -