├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── README.rst ├── TODO.rst ├── docker-compose.yml ├── docs ├── Makefile ├── _static │ └── peewee-white.png ├── _themes │ └── flask │ │ ├── layout.html │ │ ├── relations.html │ │ ├── static │ │ ├── flasky.css_t │ │ └── small_flask.css │ │ └── theme.conf ├── conf.py ├── index.rst ├── make.bat ├── mysql.png ├── peewee-logo.png ├── peewee-white.png ├── peewee │ ├── api.rst │ ├── contributing.rst │ ├── database.rst │ ├── example.rst │ ├── hacks.rst │ ├── installation.rst │ ├── models.rst │ ├── more-resources.rst │ ├── playhouse.rst │ ├── querying.rst │ ├── quickstart.rst │ ├── schema.jpg │ ├── transactions.rst │ └── tweepee.jpg ├── postgresql.png └── sqlite.png ├── examples ├── analytics │ ├── app.py │ ├── reports.py │ ├── requirements.txt │ └── run_example.py ├── blog │ ├── app.py │ ├── requirements.txt │ ├── static │ │ ├── css │ │ │ ├── blog.min.css │ │ │ └── hilite.css │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ ├── js │ │ │ ├── bootstrap.min.js │ │ │ └── jquery-1.11.0.min.js │ │ └── robots.txt │ └── templates │ │ ├── base.html │ │ ├── create.html │ │ ├── detail.html │ │ ├── edit.html │ │ ├── includes │ │ └── pagination.html │ │ ├── index.html │ │ ├── login.html │ │ └── logout.html ├── diary.py ├── redis_vtable │ ├── README.md │ └── redis_vtab.py └── twitter │ ├── app.py │ ├── requirements.txt │ ├── run_example.py │ ├── static │ └── style.css │ └── templates │ ├── create.html │ ├── homepage.html │ ├── includes │ ├── message.html │ └── pagination.html │ ├── join.html │ ├── layout.html │ ├── login.html │ ├── private_messages.html │ ├── public_messages.html │ ├── user_detail.html │ ├── user_followers.html │ ├── user_following.html │ └── user_list.html ├── peewee.py ├── playhouse ├── README.md ├── __init__.py ├── _speedups.pyx ├── _sqlite_ext.pyx ├── _sqlite_udf.pyx ├── apsw_ext.py ├── berkeleydb.py ├── csv_loader.py ├── csv_utils.py ├── dataset.py ├── db_url.py ├── djpeewee.py ├── fields.py ├── flask_utils.py ├── gfk.py ├── hybrid.py ├── kv.py ├── migrate.py ├── pool.py ├── postgres_ext.py ├── pskel ├── read_slave.py ├── reflection.py ├── shortcuts.py ├── signals.py ├── sqlcipher_ext.py ├── sqlite_ext.py ├── sqlite_udf.py ├── sqliteq.py ├── test_utils.py └── tests │ ├── README │ ├── __init__.py │ ├── base.py │ ├── libs │ ├── __init__.py │ └── mock.py │ ├── models.py │ ├── test_apis.py │ ├── test_apsw.py │ ├── test_berkeleydb.py │ ├── test_compound_queries.py │ ├── test_csv_utils.py │ ├── test_database.py │ ├── test_dataset.py │ ├── test_db_url.py │ ├── test_djpeewee.py │ ├── test_extra_fields.py │ ├── test_fields.py │ ├── test_flask_utils.py │ ├── test_gfk.py │ ├── test_helpers.py │ ├── test_hybrid.py │ ├── test_introspection.py │ ├── test_keys.py │ ├── test_kv.py │ ├── test_manytomany.py │ ├── test_migrate.py │ ├── test_models.py │ ├── test_pool.py │ ├── test_postgres.py │ ├── test_pwiz.py │ ├── test_queries.py │ ├── test_query_results.py │ ├── test_read_slave.py │ ├── test_reflection.py │ ├── test_shortcuts.py │ ├── test_signals.py │ ├── test_speedups.py │ ├── test_sqlcipher_ext.py │ ├── test_sqlite_c_ext.py │ ├── test_sqlite_ext.py │ ├── test_sqlite_udf.py │ ├── test_sqliteq.py │ ├── test_test_utils.py │ └── test_transactions.py ├── pwiz.py ├── requirements.txt ├── runtests.py ├── setup.py ├── tests.py └── tests └── test_async.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build 3 | prof/ 4 | playhouse/*.c 5 | playhouse/*.h 6 | playhouse/*.so 7 | playhouse/tests/peewee_test.db 8 | .idea/ 9 | MANIFEST 10 | peewee_test.db 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.4" 6 | - "3.5" 7 | env: 8 | - PEEWEE_TEST_BACKEND=sqlite 9 | - PEEWEE_TEST_BACKEND=postgresql 10 | - PEEWEE_TEST_BACKEND=mysql 11 | addons: 12 | postgresql: "9.4" 13 | services: 14 | - postgresql 15 | - mysql 16 | install: "pip install psycopg2 Cython pymysql" 17 | before_script: 18 | - sh -c "python setup.py build_ext -i" 19 | - sh -c "psql -c 'drop database if exists peewee_test;' -U postgres" 20 | - sh -c "psql -c 'create database peewee_test;' -U postgres" 21 | - sh -c "psql peewee_test -c 'create extension hstore;' -U postgres" 22 | - sh -c "mysql -e 'drop database if exists peewee_test;'" 23 | - sh -c "mysql -e 'create database peewee_test;'" 24 | script: "python runtests.py -a" 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | 3 | ADD . /code 4 | RUN pip install -r /code/requirements.txt 5 | 6 | WORKDIR /code 7 | CMD python main.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Charles Leifer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.md 2 | include LICENSE 3 | include README.rst 4 | include TODO.rst 5 | include runtests.py 6 | include tests.py 7 | include playhouse/_sqlite_ext.pyx 8 | include playhouse/_sqlite_udf.pyx 9 | include playhouse/_speedups.pyx 10 | include playhouse/pskel 11 | include playhouse/README.md 12 | include playhouse/tests/README 13 | recursive-include examples * 14 | recursive-include docs * 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | aiopeewee 2 | ====== 3 | 4 | Peewee is a simple and small ORM. It has few (but expressive) concepts, making it easy to learn and intuitive to use. 5 | 6 | This is a port of peewee and is currently in development. 7 | -------------------------------------------------------------------------------- /TODO.rst: -------------------------------------------------------------------------------- 1 | todo 2 | ==== 3 | 4 | * Database column defaults? 5 | * Pre-compute foreign keys, attributes and join types (forward-ref or backref) in the `AggregateQueryResultWrapper.iterate` method. 6 | * Improve the performance of the `QueryCompiler`. 7 | 8 | version 3? 9 | ========== 10 | 11 | * Follow foreign keys through fields, e.g. Tweet.user.username, or Comment.blog.user.username. 12 | * Simplify the node types: 13 | * Node (base class) 14 | * Expression 15 | * Quoted 16 | * Clause 17 | * Parsing should be context-aware, which would reduce some of the hacks, particularly around `IN` + lists. 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | test: 4 | build: . 5 | volumes: 6 | - .:/code 7 | ports: 8 | - 8001:80 9 | #command: python -m pytest tests 10 | command: python tests/test_async.py 11 | depends_on: 12 | - database 13 | database: 14 | image: postgres 15 | environment: 16 | - POSTGRES_PASSWORD=testpw 17 | - POSTGRES_USER=admin 18 | - POSTGRES_DB=testing 19 | ports: 20 | - 5433:5432 -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/peewee.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/peewee.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/peewee" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/peewee" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/_static/peewee-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/docs/_static/peewee-white.png -------------------------------------------------------------------------------- /docs/_themes/flask/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | {% endblock %} 10 | {%- block relbar2 %}{% endblock %} 11 | {% block header %} 12 | {{ super() }} 13 | {% if pagename == 'index' %} 14 |
15 | {% endif %} 16 | {% endblock %} 17 | {%- block footer %} 18 | 22 | {% if pagename == 'index' %} 23 |
24 | {% endif %} 25 | {%- endblock %} 26 | -------------------------------------------------------------------------------- /docs/_themes/flask/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /docs/_themes/flask/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | -------------------------------------------------------------------------------- /docs/_themes/flask/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | index_logo = '' 8 | index_logo_height = 120px 9 | touch_icon = 10 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. peewee documentation master file, created by 2 | sphinx-quickstart on Thu Nov 25 21:20:29 2010. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | peewee 7 | ====== 8 | 9 | .. image:: peewee-logo.png 10 | 11 | Peewee is a simple and small ORM. It has few (but expressive) concepts, making it easy to learn and intuitive to use. 12 | 13 | * A small, expressive ORM 14 | * Written in python with support for versions 2.6+ and 3.2+. 15 | * built-in support for sqlite, mysql and postgresql 16 | * :ref:`numerous extensions available ` (:ref:`postgres hstore/json/arrays `, :ref:`sqlite full-text-search `, :ref:`schema migrations `, and much more). 17 | 18 | .. image:: postgresql.png 19 | :target: peewee/database.html#using-postgresql 20 | :alt: postgresql 21 | 22 | .. image:: mysql.png 23 | :target: peewee/database.html#using-mysql 24 | :alt: mysql 25 | 26 | .. image:: sqlite.png 27 | :target: peewee/database.html#using-sqlite 28 | :alt: sqlite 29 | 30 | Peewee's source code hosted on `GitHub `_. 31 | 32 | New to peewee? Here is a list of documents you might find most helpful when getting 33 | started: 34 | 35 | * :ref:`Quickstart guide ` -- this guide covers all the bare essentials. It will take you between 5 and 10 minutes to go through it. 36 | * :ref:`Guide to the various query operators ` describes how to construct queries and combine expressions. 37 | * :ref:`Field types table ` lists the various field types peewee supports and the parameters they accept. There is also an :ref:`extension module ` that contains :ref:`special/custom field types `. 38 | 39 | Contents: 40 | --------- 41 | 42 | .. toctree:: 43 | :maxdepth: 2 44 | :glob: 45 | 46 | peewee/installation 47 | peewee/quickstart 48 | peewee/example 49 | peewee/more-resources 50 | peewee/contributing 51 | peewee/database 52 | peewee/models 53 | peewee/querying 54 | peewee/transactions 55 | peewee/playhouse 56 | peewee/api 57 | peewee/hacks 58 | 59 | Note 60 | ---- 61 | 62 | If you find any bugs, odd behavior, or have an idea for a new feature please don't hesitate to `open an issue `_ on GitHub or `contact me `_. 63 | 64 | Indices and tables 65 | ================== 66 | 67 | * :ref:`genindex` 68 | * :ref:`modindex` 69 | * :ref:`search` 70 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | echo. 46 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 47 | goto end 48 | ) 49 | 50 | if "%1" == "dirhtml" ( 51 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 52 | echo. 53 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 54 | goto end 55 | ) 56 | 57 | if "%1" == "singlehtml" ( 58 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 59 | echo. 60 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 61 | goto end 62 | ) 63 | 64 | if "%1" == "pickle" ( 65 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 66 | echo. 67 | echo.Build finished; now you can process the pickle files. 68 | goto end 69 | ) 70 | 71 | if "%1" == "json" ( 72 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 73 | echo. 74 | echo.Build finished; now you can process the JSON files. 75 | goto end 76 | ) 77 | 78 | if "%1" == "htmlhelp" ( 79 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 80 | echo. 81 | echo.Build finished; now you can run HTML Help Workshop with the ^ 82 | .hhp project file in %BUILDDIR%/htmlhelp. 83 | goto end 84 | ) 85 | 86 | if "%1" == "qthelp" ( 87 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 88 | echo. 89 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 90 | .qhcp project file in %BUILDDIR%/qthelp, like this: 91 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\peewee.qhcp 92 | echo.To view the help file: 93 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\peewee.ghc 94 | goto end 95 | ) 96 | 97 | if "%1" == "devhelp" ( 98 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 99 | echo. 100 | echo.Build finished. 101 | goto end 102 | ) 103 | 104 | if "%1" == "epub" ( 105 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 106 | echo. 107 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 108 | goto end 109 | ) 110 | 111 | if "%1" == "latex" ( 112 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 113 | echo. 114 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 115 | goto end 116 | ) 117 | 118 | if "%1" == "text" ( 119 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 120 | echo. 121 | echo.Build finished. The text files are in %BUILDDIR%/text. 122 | goto end 123 | ) 124 | 125 | if "%1" == "man" ( 126 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 127 | echo. 128 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 129 | goto end 130 | ) 131 | 132 | if "%1" == "changes" ( 133 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 134 | echo. 135 | echo.The overview file is in %BUILDDIR%/changes. 136 | goto end 137 | ) 138 | 139 | if "%1" == "linkcheck" ( 140 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 141 | echo. 142 | echo.Link check complete; look for any errors in the above output ^ 143 | or in %BUILDDIR%/linkcheck/output.txt. 144 | goto end 145 | ) 146 | 147 | if "%1" == "doctest" ( 148 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 149 | echo. 150 | echo.Testing of doctests in the sources finished, look at the ^ 151 | results in %BUILDDIR%/doctest/output.txt. 152 | goto end 153 | ) 154 | 155 | :end 156 | -------------------------------------------------------------------------------- /docs/mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/docs/mysql.png -------------------------------------------------------------------------------- /docs/peewee-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/docs/peewee-logo.png -------------------------------------------------------------------------------- /docs/peewee-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/docs/peewee-white.png -------------------------------------------------------------------------------- /docs/peewee/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _contributing: 2 | 3 | Contributing 4 | ============ 5 | 6 | In order to continually improve, Peewee needs the help of developers like you. Whether it's contributing patches, submitting bug reports, or just asking and answering questions, you are helping to make Peewee a better library. 7 | 8 | In this document I'll describe some of the ways you can help. 9 | 10 | Patches 11 | ------- 12 | 13 | Do you have an idea for a new feature, or is there a clunky API you'd like to improve? Before coding it up and submitting a pull-request, `open a new issue `_ on GitHub describing your proposed changes. This doesn't have to be anything formal, just a description of what you'd like to do and why. 14 | 15 | When you're ready, you can submit a pull-request with your changes. Successful patches will have the following: 16 | 17 | * Unit tests. 18 | * Documentation, both prose form and general :ref:`API documentation `. 19 | * Code that conforms stylistically with the rest of the Peewee codebase. 20 | 21 | Bugs 22 | ---- 23 | 24 | If you've found a bug, please check to see if it has `already been reported `_, and if not `create an issue on GitHub `_. The more information you include, the more quickly the bug will get fixed, so please try to include the following: 25 | 26 | * Traceback and the error message (please `format your code `_!) 27 | * Relevant portions of your code or code to reproduce the error 28 | * Peewee version: ``python -c "from peewee import __version__; print(__version__)"`` 29 | * Which database you're using 30 | 31 | If you have found a bug in the code and submit a failing test-case, then hats-off to you, you are a hero! 32 | 33 | Questions 34 | --------- 35 | 36 | If you have questions about how to do something with peewee, then I recommend either: 37 | 38 | * Ask on StackOverflow. I check SO just about every day for new peewee questions and try to answer them. This has the benefit also of preserving the question and answer for other people to find. 39 | * Ask in IRC, ``#peewee`` on freenode. I always answer questions, but it may take a bit to get to them. 40 | * Ask on the mailing list, https://groups.google.com/group/peewee-orm 41 | -------------------------------------------------------------------------------- /docs/peewee/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | Installing and Testing 4 | ====================== 5 | 6 | Most users will want to simply install the latest version, hosted on PyPI: 7 | 8 | .. code-block:: console 9 | 10 | pip install peewee 11 | 12 | Peewee comes with two C extensions that can optionally be compiled: 13 | 14 | * Speedups, which includes miscellaneous functions re-implemented with Cython. This module will be built automatically if Cython is installed. 15 | * Sqlite extensions, which includes Cython implementations of the SQLite date manipulation functions, the REGEXP operator, and full-text search result ranking algorithms. This module should be built using the ``build_sqlite_ext`` command. 16 | 17 | .. note:: 18 | If you have Cython installed, then the ``speedups`` module will automatically be built. If you wish to also build the SQLite Cython extension, you must manually run: 19 | 20 | .. code-block:: console 21 | 22 | python setup.py build_sqlite_ext 23 | python setup.py install 24 | 25 | 26 | Installing with git 27 | ------------------- 28 | 29 | The project is hosted at https://github.com/coleifer/peewee and can be installed 30 | using git: 31 | 32 | .. code-block:: console 33 | 34 | git clone https://github.com/coleifer/peewee.git 35 | cd peewee 36 | python setup.py install 37 | 38 | If you would like to build the SQLite extension in a git checkout, you can run: 39 | 40 | .. code-block:: console 41 | 42 | # Build the sqlite extension and place the shared library alongside the other modules. 43 | python setup.py build_sqlite_ext -i 44 | 45 | .. note:: 46 | On some systems you may need to use ``sudo python setup.py install`` to install peewee system-wide. 47 | 48 | Running tests 49 | ------------- 50 | 51 | You can test your installation by running the test suite. 52 | 53 | .. code-block:: console 54 | 55 | python setup.py test 56 | 57 | # Or use the test runner: 58 | python runtests.py 59 | 60 | You can test specific features or specific database drivers using the ``runtests.py`` 61 | script. By default the test suite is run using SQLite and the ``playhouse`` 62 | extension tests are not run. To view the available test runner options, use: 63 | 64 | .. code-block:: console 65 | 66 | python runtests.py --help 67 | 68 | Optional dependencies 69 | --------------------- 70 | 71 | .. note:: 72 | To use Peewee, you typically won't need anything outside the standard 73 | library, since most Python distributions are compiled with SQLite support. 74 | You can test by running ``import sqlite3`` in the Python console. If you 75 | wish to use another database, there are many DB-API 2.0-compatible drivers 76 | out there, such as ``pymysql`` or ``psycopg2`` for MySQL and Postgres 77 | respectively. 78 | 79 | * `Cython `_: used for various speedups. Can give a big 80 | boost to certain operations, particularly if you use SQLite. 81 | * `apsw `_: an optional 3rd-party SQLite 82 | binding offering greater performance and much, much saner semantics than the 83 | standard library ``pysqlite``. Use with :py:class:`APSWDatabase`. 84 | * `pycrypto `_ is used for the 85 | :py:class:`AESEncryptedField`. 86 | * ``bcrypt`` module is used for the :py:class:`PasswordField`. 87 | * `vtfunc ` is used to provide some 88 | table-valued functions for Sqlite as part of the ``sqlite_udf`` extensions 89 | module. 90 | * `gevent `_ is an optional dependency for 91 | :py:class:`SqliteQueueDatabase` (though it works with ``threading`` just 92 | fine). 93 | * `BerkeleyDB `_ can 94 | be compiled with a SQLite frontend, which works with Peewee. Compiling can be 95 | tricky so `here are instructions `_. 96 | * Lastly, if you use the *Flask* or *Django* frameworks, there are helper 97 | extension modules available. 98 | -------------------------------------------------------------------------------- /docs/peewee/more-resources.rst: -------------------------------------------------------------------------------- 1 | .. _more-resources: 2 | 3 | Additional Resources 4 | ==================== 5 | 6 | I've written a number of blog posts about building applications and web-services with peewee (and usually Flask). If you'd like to see some "real-life" applications that use peewee, the following resources may be useful: 7 | 8 | * `How to make a Flask blog in one hour or less `_. 9 | * `Building a note-taking app with Flask and Peewee `_ as well as `Part 2 `_ and `Part 3 `_. 10 | * `Analytics web service built with Flask and Peewee `_. 11 | * `Personalized news digest (with a boolean query parser!) `_. 12 | * `Using peewee to explore CSV files `_. 13 | * `Structuring Flask apps with Peewee `_. 14 | * `Creating a lastpass clone with Flask and Peewee `_. 15 | * `Building a web-based encrypted file manager with Flask, peewee and S3 `_. 16 | * `Creating a bookmarking web-service that takes screenshots of your bookmarks `_. 17 | * `Building a pastebin, wiki and a bookmarking service using Flask and Peewee `_. 18 | * `Encrypted databases with Python and SQLCipher `_. 19 | * `Dear Diary, an Encrypted Command-Line Diary `_. 20 | -------------------------------------------------------------------------------- /docs/peewee/schema.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/docs/peewee/schema.jpg -------------------------------------------------------------------------------- /docs/peewee/tweepee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/docs/peewee/tweepee.jpg -------------------------------------------------------------------------------- /docs/postgresql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/docs/postgresql.png -------------------------------------------------------------------------------- /docs/sqlite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/docs/sqlite.png -------------------------------------------------------------------------------- /examples/analytics/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example "Analytics" app. To start using this on your site, do the following: 3 | 4 | * Create a postgresql database with HStore support: 5 | 6 | createdb analytics 7 | psql analytics -c "create extension hstore;" 8 | 9 | * Create an account for each domain you intend to collect analytics for, e.g. 10 | 11 | Account.create(domain='charlesleifer.com') 12 | 13 | * Update configuration values marked "TODO", e.g. DOMAIN. 14 | 15 | * Run this app using the WSGI server of your choice. 16 | 17 | * Using the appropriate account id, add a ` 21 | 22 | Take a look at `reports.py` for some interesting queries you can perform 23 | on your pageview data. 24 | """ 25 | import datetime 26 | import os 27 | from urlparse import parse_qsl, urlparse 28 | 29 | from flask import Flask, Response, abort, request 30 | from peewee import create_model_tables 31 | from peewee import * 32 | from playhouse.postgres_ext import HStoreField 33 | from playhouse.postgres_ext import PostgresqlExtDatabase 34 | 35 | 36 | # Analytics settings. 37 | BEACON = '47494638396101000100800000dbdfef00000021f90401000000002c00000000010001000002024401003b'.decode('hex') # 1px gif. 38 | DATABASE_NAME = 'analytics' 39 | DOMAIN = 'http://analytics.yourdomain.com' # TODO: change me. 40 | JAVASCRIPT = """(function(id){ 41 | var d=document,i=new Image,e=encodeURIComponent; 42 | i.src='%s/a.gif?id='+id+'&url='+e(d.location.href)+'&ref='+e(d.referrer)+'&t='+e(d.title); 43 | })(%s)""".replace('\n', '') 44 | 45 | # Flask settings. 46 | DEBUG = bool(os.environ.get('DEBUG')) 47 | SECRET_KEY = 'secret - change me' # TODO: change me. 48 | 49 | app = Flask(__name__) 50 | app.config.from_object(__name__) 51 | 52 | database = PostgresqlExtDatabase( 53 | DATABASE_NAME, 54 | user='postgres') 55 | 56 | class BaseModel(Model): 57 | class Meta: 58 | database = database 59 | 60 | class Account(BaseModel): 61 | domain = CharField() 62 | 63 | def verify_url(self, url): 64 | netloc = urlparse(url).netloc 65 | url_domain = '.'.join(netloc.split('.')[-2:]) # Ignore subdomains. 66 | return self.domain == url_domain 67 | 68 | class PageView(BaseModel): 69 | account = ForeignKeyField(Account, related_name='pageviews') 70 | url = TextField() 71 | timestamp = DateTimeField(default=datetime.datetime.now) 72 | title = TextField(default='') 73 | ip = CharField(default='') 74 | referrer = TextField(default='') 75 | headers = HStoreField() 76 | params = HStoreField() 77 | 78 | @classmethod 79 | def create_from_request(cls, account, request): 80 | parsed = urlparse(request.args['url']) 81 | params = dict(parse_qsl(parsed.query)) 82 | 83 | return PageView.create( 84 | account=account, 85 | url=parsed.path, 86 | title=request.args.get('t') or '', 87 | ip=request.headers.get('x-forwarded-for', request.remote_addr), 88 | referrer=request.args.get('ref') or '', 89 | headers=dict(request.headers), 90 | params=params) 91 | 92 | @app.route('/a.gif') 93 | def analyze(): 94 | # Make sure an account id and url were specified. 95 | if not request.args.get('id') or not request.args.get('url'): 96 | abort(404) 97 | 98 | # Ensure the account id is valid. 99 | try: 100 | account = Account.get(Account.id == request.args['id']) 101 | except Account.DoesNotExist: 102 | abort(404) 103 | 104 | # Ensure the account id matches the domain of the URL we wish to record. 105 | if not account.verify_url(request.args['url']): 106 | abort(403) 107 | 108 | # Store the page-view data in the database. 109 | PageView.create_from_request(account, request) 110 | 111 | # Return a 1px gif. 112 | response = Response(app.config['BEACON'], mimetype='image/gif') 113 | response.headers['Cache-Control'] = 'private, no-cache' 114 | return response 115 | 116 | @app.route('/a.js') 117 | def script(): 118 | account_id = request.args.get('id') 119 | if account_id: 120 | return Response( 121 | app.config['JAVASCRIPT'] % (app.config['DOMAIN'], account_id), 122 | mimetype='text/javascript') 123 | return Response('', mimetype='text/javascript') 124 | 125 | @app.errorhandler(404) 126 | def not_found(e): 127 | return Response('

Not found.

') 128 | 129 | # Request handlers -- these two hooks are provided by flask and we will use them 130 | # to create and tear down a database connection on each request. 131 | @app.before_request 132 | def before_request(): 133 | g.db = database 134 | g.db.connect() 135 | 136 | @app.after_request 137 | def after_request(response): 138 | g.db.close() 139 | return response 140 | 141 | 142 | if __name__ == '__main__': 143 | create_model_tables([Account, PageView], fail_silently=True) 144 | app.run(debug=True) 145 | -------------------------------------------------------------------------------- /examples/analytics/reports.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | 3 | from app import Account, PageView 4 | 5 | 6 | DEFAULT_ACCOUNT_ID = 1 7 | 8 | class Report(object): 9 | def __init__(self, account_id=DEFAULT_ACCOUNT_ID): 10 | self.account = Account.get(Account.id == account_id) 11 | self.date_range = None 12 | 13 | def get_query(self): 14 | query = PageView.select().where(PageView.account == self.account) 15 | if self.date_range: 16 | query = query.where(PageView.timestamp.between(*self.date_range)) 17 | return query 18 | 19 | def top_pages_by_time_period(self, interval='day'): 20 | """ 21 | Get a breakdown of top pages per interval, i.e. 22 | 23 | day url count 24 | 2014-01-01 /blog/ 11 25 | 2014-01-02 /blog/ 14 26 | 2014-01-03 /blog/ 9 27 | """ 28 | date_trunc = fn.date_trunc(interval, PageView.timestamp) 29 | return (self.get_query() 30 | .select( 31 | PageView.url, 32 | date_trunc.alias(interval), 33 | fn.Count(PageView.id).alias('count')) 34 | .group_by(PageView.url, date_trunc) 35 | .order_by( 36 | SQL(interval), 37 | SQL('count').desc(), 38 | PageView.url)) 39 | 40 | def cookies(self): 41 | """ 42 | Retrieve the cookies header from all the users who visited. 43 | """ 44 | return (self.get_query() 45 | .select(PageView.ip, PageView.headers['Cookie']) 46 | .where(~(PageView.headers['Cookie'] >> None)) 47 | .tuples()) 48 | 49 | def user_agents(self): 50 | """ 51 | Retrieve user-agents, sorted by most common to least common. 52 | """ 53 | return (self.get_query() 54 | .select( 55 | PageView.headers['User-Agent'], 56 | fn.Count(PageView.id)) 57 | .group_by(PageView.headers['User-Agent']) 58 | .order_by(fn.Count(PageView.id).desc()) 59 | .tuples()) 60 | 61 | def languages(self): 62 | """ 63 | Retrieve languages, sorted by most common to least common. The 64 | Accept-Languages header sometimes looks weird, i.e. 65 | "en-US,en;q=0.8,is;q=0.6,da;q=0.4" We will split on the first semi- 66 | colon. 67 | """ 68 | language = PageView.headers['Accept-Language'] 69 | first_language = fn.SubStr( 70 | language, # String to slice. 71 | 1, # Left index. 72 | fn.StrPos(language, ';')) 73 | return (self.get_query() 74 | .select(first_language, fn.Count(PageView.id)) 75 | .group_by(first_language) 76 | .order_by(fn.Count(PageView.id).desc()) 77 | .tuples()) 78 | 79 | def trail(self): 80 | """ 81 | Get all visitors by IP and then list the pages they visited in order. 82 | """ 83 | inner = (self.get_query() 84 | .select(PageView.ip, PageView.url) 85 | .order_by(PageView.timestamp)) 86 | return (PageView 87 | .select( 88 | PageView.ip, 89 | fn.array_agg(PageView.url).coerce(False).alias('urls')) 90 | .from_(inner.alias('t1')) 91 | .group_by(PageView.ip)) 92 | 93 | def _referrer_clause(self, domain_only=True): 94 | if domain_only: 95 | return fn.SubString(Clause( 96 | PageView.referrer, SQL('FROM'), '.*://([^/]*)')) 97 | return PageView.referrer 98 | 99 | def top_referrers(self, domain_only=True): 100 | """ 101 | What domains send us the most traffic? 102 | """ 103 | referrer = self._referrer_clause(domain_only) 104 | return (self.get_query() 105 | .select(referrer, fn.Count(PageView.id)) 106 | .group_by(referrer) 107 | .order_by(fn.Count(PageView.id).desc()) 108 | .tuples()) 109 | 110 | def referrers_for_url(self, domain_only=True): 111 | referrer = self._referrer_clause(domain_only) 112 | return (self.get_query() 113 | .select(PageView.url, referrer, fn.Count(PageView.id)) 114 | .group_by(PageView.url, referrer) 115 | .order_by(PageView.url, fn.Count(PageView.id).desc()) 116 | .tuples()) 117 | 118 | def referrers_to_url(self, domain_only=True): 119 | referrer = self._referrer_clause(domain_only) 120 | return (self.get_query() 121 | .select(referrer, PageView.url, fn.Count(PageView.id)) 122 | .group_by(referrer, PageView.url) 123 | .order_by(referrer, fn.Count(PageView.id).desc()) 124 | .tuples()) 125 | -------------------------------------------------------------------------------- /examples/analytics/requirements.txt: -------------------------------------------------------------------------------- 1 | peewee 2 | flask 3 | -------------------------------------------------------------------------------- /examples/analytics/run_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | sys.path.insert(0, '../..') 5 | 6 | from app import app 7 | app.run(debug=True) 8 | -------------------------------------------------------------------------------- /examples/blog/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | BeautifulSoup 3 | micawber 4 | pygments 5 | markdown 6 | peewee 7 | -------------------------------------------------------------------------------- /examples/blog/static/css/hilite.css: -------------------------------------------------------------------------------- 1 | .highlight { 2 | background: #040400; 3 | color: #FFFFFF; 4 | } 5 | 6 | .highlight span.selection { color: #323232; } 7 | .highlight span.gp { color: #9595FF; } 8 | .highlight span.vi { color: #9595FF; } 9 | .highlight span.kn { color: #00C0D1; } 10 | .highlight span.cp { color: #AEE674; } 11 | .highlight span.caret { color: #FFFFFF; } 12 | .highlight span.no { color: #AEE674; } 13 | .highlight span.s2 { color: #BBFB8D; } 14 | .highlight span.nb { color: #A7FDB2; } 15 | .highlight span.nc { color: #C2ABFF; } 16 | .highlight span.nd { color: #AEE674; } 17 | .highlight span.s { color: #BBFB8D; } 18 | .highlight span.nf { color: #AEE674; } 19 | .highlight span.nx { color: #AEE674; } 20 | .highlight span.kp { color: #00C0D1; } 21 | .highlight span.nt { color: #C2ABFF; } 22 | .highlight span.s1 { color: #BBFB8D; } 23 | .highlight span.bg { color: #040400; } 24 | .highlight span.kt { color: #00C0D1; } 25 | .highlight span.support_function { color: #81B864; } 26 | .highlight span.ow { color: #EBE1B4; } 27 | .highlight span.mf { color: #A1FF24; } 28 | .highlight span.bp { color: #9595FF; } 29 | .highlight span.fg { color: #FFFFFF; } 30 | .highlight span.c1 { color: #3379FF; } 31 | .highlight span.kc { color: #9595FF; } 32 | .highlight span.c { color: #3379FF; } 33 | .highlight span.sx { color: #BBFB8D; } 34 | .highlight span.kd { color: #00C0D1; } 35 | .highlight span.ss { color: #A1FF24; } 36 | .highlight span.sr { color: #BBFB8D; } 37 | .highlight span.mo { color: #A1FF24; } 38 | .highlight span.mi { color: #A1FF24; } 39 | .highlight span.mh { color: #A1FF24; } 40 | .highlight span.o { color: #EBE1B4; } 41 | .highlight span.si { color: #DA96A3; } 42 | .highlight span.sh { color: #BBFB8D; } 43 | .highlight span.na { color: #AEE674; } 44 | .highlight span.sc { color: #BBFB8D; } 45 | .highlight span.k { color: #00C0D1; } 46 | .highlight span.se { color: #DA96A3; } 47 | .highlight span.sd { color: #54F79C; } 48 | -------------------------------------------------------------------------------- /examples/blog/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/examples/blog/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /examples/blog/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/examples/blog/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /examples/blog/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/examples/blog/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /examples/blog/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /examples/blog/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Blog 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% block extra_head %}{% endblock %} 12 | 13 | 14 | {% block extra_scripts %}{% endblock %} 15 | 16 | 17 | 18 | 48 | 49 |
50 | {% for category, message in get_flashed_messages(with_categories=true) %} 51 |
52 | 53 |

{{ message }}

54 |
55 | {% endfor %} 56 | 57 | {% block page_header %} 58 | 61 | {% endblock %} 62 | 63 | {% block content %}{% endblock %} 64 | 65 |
66 |
67 |

Blog, © 2015

68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /examples/blog/templates/create.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Create entry{% endblock %} 4 | 5 | {% block content_title %}Create entry{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 | 27 |
28 |
29 |
30 |
31 |
32 | 33 | Cancel 34 |
35 |
36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /examples/blog/templates/detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}{{ entry.title }}{% endblock %} 4 | 5 | {% block content_title %}{{ entry.title }}{% endblock %} 6 | 7 | {% block extra_header %} 8 | {% if session.logged_in %} 9 |
  • Edit entry
  • 10 | {% endif %} 11 | {% endblock %} 12 | 13 | {% block content %} 14 |

    Created {{ entry.timestamp.strftime('%m/%d/%Y at %G:%I%p') }}

    15 | {{ entry.html_content }} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /examples/blog/templates/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "create.html" %} 2 | 3 | {% block title %}Edit entry{% endblock %} 4 | 5 | {% block content_title %}Edit entry{% endblock %} 6 | 7 | {% block form_action %}{{ url_for('edit', slug=entry.slug) }}{% endblock %} 8 | 9 | {% block save_button %}Save changes{% endblock %} 10 | -------------------------------------------------------------------------------- /examples/blog/templates/includes/pagination.html: -------------------------------------------------------------------------------- 1 | {% if pagination.get_page_count() > 1 %} 2 | 14 | {% endif %} 15 | -------------------------------------------------------------------------------- /examples/blog/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Blog entries{% endblock %} 4 | 5 | {% block content_title %}{% if search %}Search "{{ search }}"{% else %}Blog entries{% endif %}{% endblock %} 6 | 7 | {% block content %} 8 | {% for entry in object_list %} 9 | {% if search %} 10 | {% set entry = entry.entry %} 11 | {% endif %} 12 |

    13 | 14 | {{ entry.title }} 15 | 16 |

    17 |

    Created {{ entry.timestamp.strftime('%m/%d/%Y at %G:%I%p') }}

    18 | {% else %} 19 |

    No entries have been created yet.

    20 | {% endfor %} 21 | {% include "includes/pagination.html" %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /examples/blog/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Log in{% endblock %} 4 | 5 | {% block content_title %}Log in{% endblock %} 6 | 7 | {% block content %} 8 |
    9 |
    10 | 11 |
    12 | 13 |
    14 |
    15 |
    16 |
    17 | 18 |
    19 |
    20 |
    21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /examples/blog/templates/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Log out{% endblock %} 4 | 5 | {% block content_title %}Log out{% endblock %} 6 | 7 | {% block content %} 8 |

    Click the button below to log out of the site.

    9 |
    10 | 11 |
    12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /examples/diary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from collections import OrderedDict 4 | import datetime 5 | from getpass import getpass 6 | import sys 7 | 8 | from peewee import * 9 | from playhouse.sqlcipher_ext import SqlCipherDatabase 10 | 11 | # Defer initialization of the database until the script is executed from the 12 | # command-line. 13 | db = SqlCipherDatabase(None) 14 | 15 | class Entry(Model): 16 | content = TextField() 17 | timestamp = DateTimeField(default=datetime.datetime.now) 18 | 19 | class Meta: 20 | database = db 21 | 22 | def initialize(passphrase): 23 | db.init('diary.db', passphrase=passphrase, kdf_iter=64000) 24 | Entry.create_table(fail_silently=True) 25 | 26 | def menu_loop(): 27 | choice = None 28 | while choice != 'q': 29 | for key, value in menu.items(): 30 | print('%s) %s' % (key, value.__doc__)) 31 | choice = raw_input('Action: ').lower().strip() 32 | if choice in menu: 33 | menu[choice]() 34 | 35 | def add_entry(): 36 | """Add entry""" 37 | print('Enter your entry. Press ctrl+d when finished.') 38 | data = sys.stdin.read().strip() 39 | if data and raw_input('Save entry? [Yn] ') != 'n': 40 | Entry.create(content=data) 41 | print('Saved successfully.') 42 | 43 | def view_entries(search_query=None): 44 | """View previous entries""" 45 | query = Entry.select().order_by(Entry.timestamp.desc()) 46 | if search_query: 47 | query = query.where(Entry.content.contains(search_query)) 48 | 49 | for entry in query: 50 | timestamp = entry.timestamp.strftime('%A %B %d, %Y %I:%M%p') 51 | print(timestamp) 52 | print('=' * len(timestamp)) 53 | print(entry.content) 54 | print('n) next entry') 55 | print('d) delete entry') 56 | print('q) return to main menu') 57 | action = raw_input('Choice? (Ndq) ').lower().strip() 58 | if action == 'q': 59 | break 60 | elif action == 'd': 61 | entry.delete_instance() 62 | break 63 | 64 | def search_entries(): 65 | """Search entries""" 66 | view_entries(raw_input('Search query: ')) 67 | 68 | menu = OrderedDict([ 69 | ('a', add_entry), 70 | ('v', view_entries), 71 | ('s', search_entries), 72 | ]) 73 | 74 | if __name__ == '__main__': 75 | # Collect the passphrase using a secure method. 76 | passphrase = getpass('Enter password: ') 77 | 78 | if not passphrase: 79 | sys.stderr.write('Passphrase required to access diary.\n') 80 | sys.stderr.flush() 81 | sys.exit(1) 82 | 83 | # Initialize the database. 84 | initialize(passphrase) 85 | menu_loop() 86 | -------------------------------------------------------------------------------- /examples/redis_vtable/README.md: -------------------------------------------------------------------------------- 1 | ### SQLite Virtual Table Example 2 | 3 | SQLite's virtual table mechanism allows applications to define external data-sources that are mapped to SQLite tables and queried using SQL. The stdlib `sqlite3` module does not support this functionality, but [apsw](https://github.com/rogerbinns/apsw), an alternative driver, does. 4 | 5 | This example shows a very basic implementation of a virtual table that exposes a Redis database as a SQL table. 6 | -------------------------------------------------------------------------------- /examples/twitter/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | peewee 3 | -------------------------------------------------------------------------------- /examples/twitter/run_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | sys.path.insert(0, '../..') 5 | 6 | from app import app 7 | app.run() 8 | -------------------------------------------------------------------------------- /examples/twitter/static/style.css: -------------------------------------------------------------------------------- 1 | body { font-family: sans-serif; background: #eee; } 2 | a, h1, h2 { color: #377BA8; } 3 | h1, h2 { font-family: 'Georgia', serif; margin: 0; } 4 | h1 { border-bottom: 2px solid #eee; } 5 | h2 { font-size: 1.2em; } 6 | 7 | .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; 8 | padding: 0.8em; background: white; } 9 | .page ul { list-style-type: none; } 10 | .page li { clear: both; } 11 | .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; 12 | margin-bottom: 1em; background: #fafafa; } 13 | .flash { background: #CEE5F5; padding: 0.5em; 14 | border: 1px solid #AACBE2; } 15 | .avatar { display: block; float: left; margin: 0 10px 0 0; } 16 | .message-content { min-height: 80px; } 17 | -------------------------------------------------------------------------------- /examples/twitter/templates/create.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Create

    4 |
    5 |
    6 |
    Message:
    7 |
    8 |
    9 |
    10 |
    11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /examples/twitter/templates/homepage.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Home

    4 |

    Welcome to the site!

    5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /examples/twitter/templates/includes/message.html: -------------------------------------------------------------------------------- 1 | 2 |

    {{ message.content|urlize }}

    3 | -------------------------------------------------------------------------------- /examples/twitter/templates/includes/pagination.html: -------------------------------------------------------------------------------- 1 | {% if page > 1 %} 2 | 3 | {% endif %} 4 | {% if page < pages %} 5 | 6 | {% endif %} 7 | -------------------------------------------------------------------------------- /examples/twitter/templates/join.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Join

    4 |
    5 |
    6 |
    Username:
    7 |
    8 |
    Password:
    9 |
    10 |
    Email:
    11 |
    12 |

    (used for gravatar)

    13 |
    14 |
    15 |
    16 |
    17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /examples/twitter/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | Tweepee 3 | 4 |
    5 |

    Tweepee

    6 |
    7 | {% if not session.logged_in %} 8 | log in 9 | join 10 | {% else %} 11 | public timeline 12 | create 13 | log out 14 | {% endif %} 15 |
    16 | {% for message in get_flashed_messages() %} 17 |
    {{ message }}
    18 | {% endfor %} 19 | {% block body %}{% endblock %} 20 |
    21 | -------------------------------------------------------------------------------- /examples/twitter/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Login

    4 | {% if error %}

    Error: {{ error }}{% endif %} 5 |

    6 |
    7 |
    Username: 8 |
    9 |
    Password: 10 |
    11 |
    12 |
    13 |
    14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /examples/twitter/templates/private_messages.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Private Timeline

    4 |
      5 | {% for message in message_list %} 6 |
    • {% include "includes/message.html" %}
    • 7 | {% endfor %} 8 |
    9 | {% include "includes/pagination.html" %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /examples/twitter/templates/public_messages.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Public Timeline

    4 |
      5 | {% for message in message_list %} 6 |
    • {% include "includes/message.html" %}
    • 7 | {% endfor %} 8 |
    9 | {% include "includes/pagination.html" %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /examples/twitter/templates/user_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Messages from {{ user.username }}

    4 | {% if current_user %} 5 | {% if user.username != current_user.username %} 6 | {% if current_user|is_following(user) %} 7 |
    8 | 9 |
    10 | {% else %} 11 |
    12 | 13 |
    14 | {% endif %} 15 | {% endif %} 16 | {% endif %} 17 |
      18 | {% for message in message_list %} 19 |
    • {% include "includes/message.html" %}
    • 20 | {% endfor %} 21 |
    22 | {% include "includes/pagination.html" %} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /examples/twitter/templates/user_followers.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Followers

    4 | 9 | {% include "includes/pagination.html" %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /examples/twitter/templates/user_following.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Following

    4 | 9 | {% include "includes/pagination.html" %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /examples/twitter/templates/user_list.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Users

    4 | 9 | {% include "includes/pagination.html" %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /playhouse/README.md: -------------------------------------------------------------------------------- 1 | ## Playhouse 2 | 3 | The `playhouse` namespace contains numerous extensions to Peewee. These include vendor-specific database extensions, high-level abstractions to simplify working with databases, and tools for low-level database operations and introspection. 4 | 5 | ### Vendor extensions 6 | 7 | * [SQLite extensions](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#sqlite-ext) 8 | * User-defined aggregates, collations, and functions 9 | * Full-text search (FTS3/4/5) 10 | * BM25 ranking algorithm implemented as SQLite C extension, backported to FTS4 11 | * Virtual tables and C extensions 12 | * Closure tables 13 | * [APSW extensions](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#apsw-an-advanced-sqlite-driver): use Peewee with the powerful [APSW](https://github.com/rogerbinns/apsw) SQLite driver. 14 | * [BerkeleyDB](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#berkeleydb-backend): compile BerkeleyDB with SQLite compatibility API, then use with Peewee. 15 | * [SQLCipher](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#sqlcipher-backend): encrypted SQLite databases. 16 | * [Postgresql extensions](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#postgresql-extensions) 17 | * JSON and JSONB 18 | * HStore 19 | * Arrays 20 | * Server-side cursors 21 | * Full-text search 22 | 23 | ### High-level libraries 24 | 25 | * [Extra fields](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#extra-fields) 26 | * Many-to-many field 27 | * Compressed field 28 | * Password field 29 | * AES encrypted field 30 | * [Shortcuts / helpers](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#shortcuts) 31 | * `CASE` statement constructor 32 | * `CAST` 33 | * Model to dict serializer 34 | * Dict to model deserializer 35 | * Retry query with backoff 36 | * [Hybrid attributes](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#hybrid) 37 | * [Signals](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#signals): pre/post-save, pre/post-delete, pre/post-init. 38 | * [Dataset](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#dataset): high-level API for working with databases popuarlized by the [project of the same name](https://dataset.readthedocs.io/). 39 | * [Key/Value Store](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#kv): key/value store using SQLite. Supports *smart indexing*, for *Pandas*-style queries. 40 | * [Generic foreign key](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#gfk): made popular by Django. 41 | * [CSV utilities](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#csv-utils): load CSV directly into database, generate models from CSV, and more. 42 | 43 | ### Database management and framework support 44 | 45 | * [pwiz](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#pwiz): generate model code from a pre-existing database. 46 | * [Schema migrations](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#migrate): modify your schema using high-level APIs. Even supports dropping or renaming columns in SQLite. 47 | * [Connection pool](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#pool): simple connection pooling. 48 | * [Reflection](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#reflection): low-level, cross-platform database introspection 49 | * [Database URLs](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#db-url): use URLs to connect to database 50 | * [Read slave](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#read-slaves) 51 | * [Flask utils](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#flask-utils): paginated object lists, database connection management, and more. 52 | * [Django integration](http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#djpeewee): generate peewee models from Django models, use Peewee alongside your Django ORM code. 53 | -------------------------------------------------------------------------------- /playhouse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/playhouse/__init__.py -------------------------------------------------------------------------------- /playhouse/_sqlite_udf.pyx: -------------------------------------------------------------------------------- 1 | import sys 2 | from difflib import SequenceMatcher 3 | from random import randint 4 | 5 | 6 | IS_PY3K = sys.version_info[0] == 3 7 | 8 | # String UDF. 9 | def damerau_levenshtein_dist(s1, s2): 10 | cdef: 11 | int i, j, del_cost, add_cost, sub_cost 12 | int s1_len = len(s1), s2_len = len(s2) 13 | list one_ago, two_ago, current_row 14 | list zeroes = [0] * (s2_len + 1) 15 | 16 | if IS_PY3K: 17 | current_row = list(range(1, s2_len + 2)) 18 | else: 19 | current_row = range(1, s2_len + 2) 20 | 21 | current_row[-1] = 0 22 | one_ago = None 23 | 24 | for i in range(s1_len): 25 | two_ago = one_ago 26 | one_ago = current_row 27 | current_row = list(zeroes) 28 | current_row[-1] = i + 1 29 | for j in range(s2_len): 30 | del_cost = one_ago[j] + 1 31 | add_cost = current_row[j - 1] + 1 32 | sub_cost = one_ago[j - 1] + (s1[i] != s2[j]) 33 | current_row[j] = min(del_cost, add_cost, sub_cost) 34 | 35 | # Handle transpositions. 36 | if (i > 0 and j > 0 and s1[i] == s2[j - 1] 37 | and s1[i-1] == s2[j] and s1[i] != s2[j]): 38 | current_row[j] = min(current_row[j], two_ago[j - 2] + 1) 39 | 40 | return current_row[s2_len - 1] 41 | 42 | # String UDF. 43 | def levenshtein_dist(a, b): 44 | cdef: 45 | int add, delete, change 46 | int i, j 47 | int n = len(a), m = len(b) 48 | list current, previous 49 | list zeroes 50 | 51 | if n > m: 52 | a, b = b, a 53 | n, m = m, n 54 | 55 | zeroes = [0] * (m + 1) 56 | 57 | if IS_PY3K: 58 | current = list(range(n + 1)) 59 | else: 60 | current = range(n + 1) 61 | 62 | for i in range(1, m + 1): 63 | previous = current 64 | current = list(zeroes) 65 | current[0] = i 66 | 67 | for j in range(1, n + 1): 68 | add = previous[j] + 1 69 | delete = current[j - 1] + 1 70 | change = previous[j - 1] 71 | if a[j - 1] != b[i - 1]: 72 | change +=1 73 | current[j] = min(add, delete, change) 74 | 75 | return current[n] 76 | 77 | # String UDF. 78 | def str_dist(a, b): 79 | cdef: 80 | int t = 0 81 | 82 | for i in SequenceMatcher(None, a, b).get_opcodes(): 83 | if i[0] == 'equal': 84 | continue 85 | t = t + max(i[4] - i[3], i[2] - i[1]) 86 | return t 87 | 88 | # Math Aggregate. 89 | cdef class median(object): 90 | cdef: 91 | int ct 92 | list items 93 | 94 | def __init__(self): 95 | self.ct = 0 96 | self.items = [] 97 | 98 | cdef selectKth(self, int k, int s=0, int e=-1): 99 | cdef: 100 | int idx 101 | if e < 0: 102 | e = len(self.items) 103 | idx = randint(s, e-1) 104 | idx = self.partition_k(idx, s, e) 105 | if idx > k: 106 | return self.selectKth(k, s, idx) 107 | elif idx < k: 108 | return self.selectKth(k, idx + 1, e) 109 | else: 110 | return self.items[idx] 111 | 112 | cdef int partition_k(self, int pi, int s, int e): 113 | cdef: 114 | int i, x 115 | 116 | val = self.items[pi] 117 | # Swap pivot w/last item. 118 | self.items[e - 1], self.items[pi] = self.items[pi], self.items[e - 1] 119 | x = s 120 | for i in range(s, e): 121 | if self.items[i] < val: 122 | self.items[i], self.items[x] = self.items[x], self.items[i] 123 | x += 1 124 | self.items[x], self.items[e-1] = self.items[e-1], self.items[x] 125 | return x 126 | 127 | def step(self, item): 128 | self.items.append(item) 129 | self.ct += 1 130 | 131 | def finalize(self): 132 | if self.ct == 0: 133 | return None 134 | elif self.ct < 3: 135 | return self.items[0] 136 | else: 137 | return self.selectKth(self.ct / 2) 138 | -------------------------------------------------------------------------------- /playhouse/apsw_ext.py: -------------------------------------------------------------------------------- 1 | """ 2 | Peewee integration with APSW, "another python sqlite wrapper". 3 | 4 | Project page: https://rogerbinns.github.io/apsw/ 5 | 6 | APSW is a really neat library that provides a thin wrapper on top of SQLite's 7 | C interface. 8 | 9 | Here are just a few reasons to use APSW, taken from the documentation: 10 | 11 | * APSW gives all functionality of SQLite, including virtual tables, virtual 12 | file system, blob i/o, backups and file control. 13 | * Connections can be shared across threads without any additional locking. 14 | * Transactions are managed explicitly by your code. 15 | * APSW can handle nested transactions. 16 | * Unicode is handled correctly. 17 | * APSW is faster. 18 | """ 19 | import apsw 20 | from peewee import * 21 | from peewee import _sqlite_date_part 22 | from peewee import _sqlite_date_trunc 23 | from peewee import _sqlite_regexp 24 | from peewee import BooleanField as _BooleanField 25 | from peewee import DateField as _DateField 26 | from peewee import DateTimeField as _DateTimeField 27 | from peewee import DecimalField as _DecimalField 28 | from peewee import logger 29 | from peewee import savepoint 30 | from peewee import TimeField as _TimeField 31 | from peewee import transaction as _transaction 32 | from playhouse.sqlite_ext import SqliteExtDatabase 33 | from playhouse.sqlite_ext import VirtualCharField 34 | from playhouse.sqlite_ext import VirtualField 35 | from playhouse.sqlite_ext import VirtualFloatField 36 | from playhouse.sqlite_ext import VirtualIntegerField 37 | from playhouse.sqlite_ext import VirtualModel 38 | 39 | 40 | class transaction(_transaction): 41 | def __init__(self, db, lock_type='deferred'): 42 | self.db = db 43 | self.lock_type = lock_type 44 | 45 | def _begin(self): 46 | self.db.begin(self.lock_type) 47 | 48 | 49 | class APSWDatabase(SqliteExtDatabase): 50 | def __init__(self, database, timeout=None, **kwargs): 51 | self.timeout = timeout 52 | self._modules = {} 53 | super(APSWDatabase, self).__init__(database, **kwargs) 54 | 55 | def register_module(self, mod_name, mod_inst): 56 | self._modules[mod_name] = mod_inst 57 | 58 | def unregister_module(self, mod_name): 59 | del(self._modules[mod_name]) 60 | 61 | def _connect(self, database, **kwargs): 62 | conn = apsw.Connection(database, **kwargs) 63 | if self.timeout is not None: 64 | conn.setbusytimeout(self.timeout) 65 | try: 66 | self._add_conn_hooks(conn) 67 | except: 68 | conn.close() 69 | raise 70 | return conn 71 | 72 | def _add_conn_hooks(self, conn): 73 | super(APSWDatabase, self)._add_conn_hooks(conn) 74 | self._load_modules(conn) # APSW-only. 75 | 76 | def _load_modules(self, conn): 77 | for mod_name, mod_inst in self._modules.items(): 78 | conn.createmodule(mod_name, mod_inst) 79 | return conn 80 | 81 | def _load_aggregates(self, conn): 82 | for name, (klass, num_params) in self._aggregates.items(): 83 | def make_aggregate(): 84 | instance = klass() 85 | return (instance, instance.step, instance.finalize) 86 | conn.createaggregatefunction(name, make_aggregate) 87 | 88 | def _load_collations(self, conn): 89 | for name, fn in self._collations.items(): 90 | conn.createcollation(name, fn) 91 | 92 | def _load_functions(self, conn): 93 | for name, (fn, num_params) in self._functions.items(): 94 | conn.createscalarfunction(name, fn, num_params) 95 | 96 | def _load_extensions(self, conn): 97 | conn.enableloadextension(True) 98 | for extension in self._extensions: 99 | conn.loadextension(extension) 100 | 101 | def load_extension(self, extension): 102 | self._extensions.add(extension) 103 | if not self.is_closed(): 104 | conn = self.get_conn() 105 | conn.enableloadextension(True) 106 | conn.loadextension(extension) 107 | 108 | def _execute_sql(self, cursor, sql, params): 109 | cursor.execute(sql, params or ()) 110 | return cursor 111 | 112 | def execute_sql(self, sql, params=None, require_commit=True): 113 | logger.debug((sql, params)) 114 | with self.exception_wrapper: 115 | cursor = self.get_cursor() 116 | self._execute_sql(cursor, sql, params) 117 | return cursor 118 | 119 | def last_insert_id(self, cursor, model): 120 | if model._meta.auto_increment: 121 | return cursor.getconnection().last_insert_rowid() 122 | 123 | def rows_affected(self, cursor): 124 | return cursor.getconnection().changes() 125 | 126 | def begin(self, lock_type='deferred'): 127 | self.get_cursor().execute('begin %s;' % lock_type) 128 | 129 | def commit(self): 130 | self.get_cursor().execute('commit;') 131 | 132 | def rollback(self): 133 | self.get_cursor().execute('rollback;') 134 | 135 | def transaction(self, lock_type='deferred'): 136 | return transaction(self, lock_type) 137 | 138 | def savepoint(self, sid=None): 139 | return savepoint(self, sid) 140 | 141 | 142 | def nh(s, v): 143 | if v is not None: 144 | return str(v) 145 | 146 | class BooleanField(_BooleanField): 147 | def db_value(self, v): 148 | v = super(BooleanField, self).db_value(v) 149 | if v is not None: 150 | return v and 1 or 0 151 | 152 | class DateField(_DateField): 153 | db_value = nh 154 | 155 | class TimeField(_TimeField): 156 | db_value = nh 157 | 158 | class DateTimeField(_DateTimeField): 159 | db_value = nh 160 | 161 | class DecimalField(_DecimalField): 162 | db_value = nh 163 | -------------------------------------------------------------------------------- /playhouse/berkeleydb.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import datetime 3 | import decimal 4 | import sys 5 | 6 | from peewee import ImproperlyConfigured 7 | from peewee import sqlite3 8 | from playhouse.sqlite_ext import * 9 | 10 | sqlite3_lib_version = sqlite3.sqlite_version_info 11 | 12 | # Peewee assumes that the `pysqlite2` module was compiled against the 13 | # BerkeleyDB SQLite libraries. 14 | try: 15 | from pysqlite2 import dbapi2 as berkeleydb 16 | except ImportError: 17 | import sqlite3 as berkeleydb 18 | 19 | berkeleydb.register_adapter(decimal.Decimal, str) 20 | berkeleydb.register_adapter(datetime.date, str) 21 | berkeleydb.register_adapter(datetime.time, str) 22 | 23 | 24 | class BerkeleyDatabase(SqliteExtDatabase): 25 | def __init__(self, database, pragmas=None, cache_size=None, page_size=None, 26 | multiversion=None, *args, **kwargs): 27 | super(BerkeleyDatabase, self).__init__( 28 | database, pragmas=pragmas, *args, **kwargs) 29 | if multiversion: 30 | self._pragmas.append(('multiversion', 'on')) 31 | if page_size: 32 | self._pragmas.append(('page_size', page_size)) 33 | if cache_size: 34 | self._pragmas.append(('cache_size', cache_size)) 35 | 36 | def _connect(self, database, **kwargs): 37 | if not PYSQLITE_BERKELEYDB: 38 | message = ('Your Python SQLite driver (%s) does not appear to ' 39 | 'have been compiled against the BerkeleyDB SQLite ' 40 | 'library.' % berkeleydb) 41 | if LIBSQLITE_BERKELEYDB: 42 | message += (' However, the libsqlite on your system is the ' 43 | 'BerkeleyDB implementation. Try recompiling ' 44 | 'pysqlite.') 45 | else: 46 | message += (' Additionally, the libsqlite on your system ' 47 | 'does not appear to be the BerkeleyDB ' 48 | 'implementation.') 49 | raise ImproperlyConfigured(message) 50 | 51 | conn = berkeleydb.connect(database, **kwargs) 52 | conn.isolation_level = None 53 | self._add_conn_hooks(conn) 54 | return conn 55 | 56 | def _set_pragmas(self, conn): 57 | # `multiversion` is weird. It checks first whether another connection 58 | # from the BTree cache is available, and then switches to that, which 59 | # may have the handle of the DB_Env. If that happens, then we get 60 | # an error stating that you cannot set `multiversion` despite the 61 | # fact we have not done any operations and it's a brand new conn. 62 | if self._pragmas: 63 | cursor = conn.cursor() 64 | for pragma, value in self._pragmas: 65 | if pragma == 'multiversion': 66 | try: 67 | cursor.execute('PRAGMA %s = %s;' % (pragma, value)) 68 | except berkeleydb.OperationalError: 69 | pass 70 | else: 71 | cursor.execute('PRAGMA %s = %s;' % (pragma, value)) 72 | cursor.close() 73 | 74 | @classmethod 75 | def check_pysqlite(cls): 76 | try: 77 | from pysqlite2 import dbapi2 as sqlite3 78 | except ImportError: 79 | import sqlite3 80 | conn = sqlite3.connect(':memory:') 81 | try: 82 | results = conn.execute('PRAGMA compile_options;').fetchall() 83 | finally: 84 | conn.close() 85 | for option, in results: 86 | if option == 'BERKELEY_DB': 87 | return True 88 | return False 89 | 90 | @classmethod 91 | def check_libsqlite(cls): 92 | # Checking compile options is not supported. 93 | if sys.platform.startswith('win'): 94 | library = 'libsqlite3.dll' 95 | elif sys.platform == 'darwin': 96 | library = 'libsqlite3.dylib' 97 | else: 98 | library = 'libsqlite3.so' 99 | 100 | try: 101 | libsqlite = ctypes.CDLL(library) 102 | except OSError: 103 | return False 104 | 105 | return libsqlite.sqlite3_compileoption_used('BERKELEY_DB') == 1 106 | 107 | 108 | if sqlite3_lib_version < (3, 6, 23): 109 | # Checking compile flags is not supported in older SQLite versions. 110 | PYSQLITE_BERKELEYDB = False 111 | LIBSQLITE_BERKELEYDB = False 112 | else: 113 | PYSQLITE_BERKELEYDB = BerkeleyDatabase.check_pysqlite() 114 | LIBSQLITE_BERKELEYDB = BerkeleyDatabase.check_libsqlite() 115 | -------------------------------------------------------------------------------- /playhouse/csv_loader.py: -------------------------------------------------------------------------------- 1 | from playhouse.csv_utils import * # Provided for backwards-compatibility. 2 | -------------------------------------------------------------------------------- /playhouse/db_url.py: -------------------------------------------------------------------------------- 1 | try: 2 | from urlparse import urlparse, parse_qsl 3 | except ImportError: 4 | from urllib.parse import urlparse, parse_qsl 5 | 6 | from peewee import * 7 | from playhouse.pool import PooledMySQLDatabase 8 | from playhouse.pool import PooledPostgresqlDatabase 9 | from playhouse.pool import PooledSqliteDatabase 10 | from playhouse.pool import PooledSqliteExtDatabase 11 | from playhouse.sqlite_ext import SqliteExtDatabase 12 | 13 | 14 | schemes = { 15 | 'mysql': MySQLDatabase, 16 | 'mysql+pool': PooledMySQLDatabase, 17 | 'postgres': PostgresqlDatabase, 18 | 'postgresql': PostgresqlDatabase, 19 | 'postgres+pool': PooledPostgresqlDatabase, 20 | 'postgresql+pool': PooledPostgresqlDatabase, 21 | 'sqlite': SqliteDatabase, 22 | 'sqliteext': SqliteExtDatabase, 23 | 'sqlite+pool': PooledSqliteDatabase, 24 | 'sqliteext+pool': PooledSqliteExtDatabase, 25 | } 26 | 27 | def register_database(db_class, *names): 28 | global schemes 29 | for name in names: 30 | schemes[name] = db_class 31 | 32 | def parseresult_to_dict(parsed): 33 | 34 | # urlparse in python 2.6 is broken so query will be empty and instead 35 | # appended to path complete with '?' 36 | path_parts = parsed.path[1:].split('?') 37 | try: 38 | query = path_parts[1] 39 | except IndexError: 40 | query = parsed.query 41 | 42 | connect_kwargs = {'database': path_parts[0]} 43 | if parsed.username: 44 | connect_kwargs['user'] = parsed.username 45 | if parsed.password: 46 | connect_kwargs['password'] = parsed.password 47 | if parsed.hostname: 48 | connect_kwargs['host'] = parsed.hostname 49 | if parsed.port: 50 | connect_kwargs['port'] = parsed.port 51 | 52 | # Adjust parameters for MySQL. 53 | if parsed.scheme == 'mysql' and 'password' in connect_kwargs: 54 | connect_kwargs['passwd'] = connect_kwargs.pop('password') 55 | elif 'sqlite' in parsed.scheme and not connect_kwargs['database']: 56 | connect_kwargs['database'] = ':memory:' 57 | 58 | # Get additional connection args from the query string 59 | qs_args = parse_qsl(query, keep_blank_values=True) 60 | for key, value in qs_args: 61 | if value.lower() == 'false': 62 | value = False 63 | elif value.lower() == 'true': 64 | value = True 65 | elif value.isdigit(): 66 | value = int(value) 67 | elif '.' in value and all(p.isdigit() for p in value.split('.', 1)): 68 | try: 69 | value = float(value) 70 | except ValueError: 71 | pass 72 | elif value.lower() in ('null', 'none'): 73 | value = None 74 | 75 | connect_kwargs[key] = value 76 | 77 | return connect_kwargs 78 | 79 | def parse(url): 80 | parsed = urlparse(url) 81 | return parseresult_to_dict(parsed) 82 | 83 | def connect(url, **connect_params): 84 | parsed = urlparse(url) 85 | connect_kwargs = parseresult_to_dict(parsed) 86 | connect_kwargs.update(connect_params) 87 | database_class = schemes.get(parsed.scheme) 88 | 89 | if database_class is None: 90 | if database_class in schemes: 91 | raise RuntimeError('Attempted to use "%s" but a required library ' 92 | 'could not be imported.' % parsed.scheme) 93 | else: 94 | raise RuntimeError('Unrecognized or unsupported scheme: "%s".' % 95 | parsed.scheme) 96 | 97 | return database_class(**connect_kwargs) 98 | 99 | # Conditionally register additional databases. 100 | try: 101 | from playhouse.pool import PooledPostgresqlExtDatabase 102 | except ImportError: 103 | pass 104 | else: 105 | register_database( 106 | PooledPostgresqlExtDatabase, 107 | 'postgresext+pool', 108 | 'postgresqlext+pool') 109 | 110 | try: 111 | from playhouse.apsw_ext import APSWDatabase 112 | except ImportError: 113 | pass 114 | else: 115 | register_database(APSWDatabase, 'apsw') 116 | 117 | try: 118 | from playhouse.berkeleydb import BerkeleyDatabase 119 | except ImportError: 120 | pass 121 | else: 122 | register_database(BerkeleyDatabase, 'berkeleydb') 123 | 124 | try: 125 | from playhouse.postgres_ext import PostgresqlExtDatabase 126 | except ImportError: 127 | pass 128 | else: 129 | register_database(PostgresqlExtDatabase, 'postgresext', 'postgresqlext') 130 | -------------------------------------------------------------------------------- /playhouse/flask_utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import sys 3 | 4 | from flask import abort 5 | from flask import render_template 6 | from flask import request 7 | from peewee import Database 8 | from peewee import DoesNotExist 9 | from peewee import Model 10 | from peewee import Proxy 11 | from peewee import SelectQuery 12 | from playhouse.db_url import connect as db_url_connect 13 | 14 | 15 | class PaginatedQuery(object): 16 | def __init__(self, query_or_model, paginate_by, page_var='page', 17 | check_bounds=False): 18 | self.paginate_by = paginate_by 19 | self.page_var = page_var 20 | self.check_bounds = check_bounds 21 | 22 | if isinstance(query_or_model, SelectQuery): 23 | self.query = query_or_model 24 | self.model = self.query.model_class 25 | else: 26 | self.model = query_or_model 27 | self.query = self.model.select() 28 | 29 | def get_page(self): 30 | curr_page = request.args.get(self.page_var) 31 | if curr_page and curr_page.isdigit(): 32 | return max(1, int(curr_page)) 33 | return 1 34 | 35 | def get_page_count(self): 36 | return int(math.ceil(float(self.query.count()) / self.paginate_by)) 37 | 38 | def get_object_list(self): 39 | if self.check_bounds and self.get_page() > self.get_page_count(): 40 | abort(404) 41 | return self.query.paginate(self.get_page(), self.paginate_by) 42 | 43 | 44 | def get_object_or_404(query_or_model, *query): 45 | if not isinstance(query_or_model, SelectQuery): 46 | query_or_model = query_or_model.select() 47 | try: 48 | return query_or_model.where(*query).get() 49 | except DoesNotExist: 50 | abort(404) 51 | 52 | def object_list(template_name, query, context_variable='object_list', 53 | paginate_by=20, page_var='page', check_bounds=True, **kwargs): 54 | paginated_query = PaginatedQuery( 55 | query, 56 | paginate_by, 57 | page_var, 58 | check_bounds) 59 | kwargs[context_variable] = paginated_query.get_object_list() 60 | return render_template( 61 | template_name, 62 | pagination=paginated_query, 63 | page=paginated_query.get_page(), 64 | **kwargs) 65 | 66 | def get_current_url(): 67 | if not request.query_string: 68 | return request.path 69 | return '%s?%s' % (request.path, request.query_string) 70 | 71 | def get_next_url(default='/'): 72 | if request.args.get('next'): 73 | return request.args['next'] 74 | elif request.form.get('next'): 75 | return request.form['next'] 76 | return default 77 | 78 | class FlaskDB(object): 79 | def __init__(self, app=None, database=None): 80 | self.database = None # Reference to actual Peewee database instance. 81 | self._app = app 82 | self._db = database # dict, url, Database, or None (default). 83 | if app is not None: 84 | self.init_app(app) 85 | 86 | def init_app(self, app): 87 | self._app = app 88 | 89 | if self._db is None: 90 | if 'DATABASE' in app.config: 91 | initial_db = app.config['DATABASE'] 92 | elif 'DATABASE_URL' in app.config: 93 | initial_db = app.config['DATABASE_URL'] 94 | else: 95 | raise ValueError('Missing required configuration data for ' 96 | 'database: DATABASE or DATABASE_URL.') 97 | else: 98 | initial_db = self._db 99 | 100 | self._load_database(app, initial_db) 101 | self._register_handlers(app) 102 | 103 | def _load_database(self, app, config_value): 104 | if isinstance(config_value, Database): 105 | database = config_value 106 | elif isinstance(config_value, dict): 107 | database = self._load_from_config_dict(dict(config_value)) 108 | else: 109 | # Assume a database connection URL. 110 | database = db_url_connect(config_value) 111 | 112 | if isinstance(self.database, Proxy): 113 | self.database.initialize(database) 114 | else: 115 | self.database = database 116 | 117 | def _load_from_config_dict(self, config_dict): 118 | try: 119 | name = config_dict.pop('name') 120 | engine = config_dict.pop('engine') 121 | except KeyError: 122 | raise RuntimeError('DATABASE configuration must specify a ' 123 | '`name` and `engine`.') 124 | 125 | if '.' in engine: 126 | path, class_name = engine.rsplit('.', 1) 127 | else: 128 | path, class_name = 'peewee', engine 129 | 130 | try: 131 | __import__(path) 132 | module = sys.modules[path] 133 | database_class = getattr(module, class_name) 134 | assert issubclass(database_class, Database) 135 | except ImportError: 136 | raise RuntimeError('Unable to import %s' % engine) 137 | except AttributeError: 138 | raise RuntimeError('Database engine not found %s' % engine) 139 | except AssertionError: 140 | raise RuntimeError('Database engine not a subclass of ' 141 | 'peewee.Database: %s' % engine) 142 | 143 | return database_class(name, **config_dict) 144 | 145 | def _register_handlers(self, app): 146 | app.before_request(self.connect_db) 147 | app.teardown_request(self.close_db) 148 | 149 | def get_model_class(self): 150 | if self.database is None: 151 | raise RuntimeError('Database must be initialized.') 152 | 153 | class BaseModel(Model): 154 | class Meta: 155 | database = self.database 156 | 157 | return BaseModel 158 | 159 | @property 160 | def Model(self): 161 | if self._app is None: 162 | database = getattr(self, 'database', None) 163 | if database is None: 164 | self.database = Proxy() 165 | 166 | if not hasattr(self, '_model_class'): 167 | self._model_class = self.get_model_class() 168 | return self._model_class 169 | 170 | def connect_db(self): 171 | self.database.connect() 172 | 173 | def close_db(self, exc): 174 | if not self.database.is_closed(): 175 | self.database.close() 176 | -------------------------------------------------------------------------------- /playhouse/gfk.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provide a "Generic ForeignKey", similar to Django. A "GFK" is composed of two 3 | columns: an object ID and an object type identifier. The object types are 4 | collected in a global registry (all_models), so all you need to do is subclass 5 | ``gfk.Model`` and your model will be added to the registry. 6 | 7 | Example: 8 | 9 | class Tag(Model): 10 | tag = CharField() 11 | object_type = CharField(null=True) 12 | object_id = IntegerField(null=True) 13 | object = GFKField('object_type', 'object_id') 14 | 15 | class Blog(Model): 16 | tags = ReverseGFK(Tag, 'object_type', 'object_id') 17 | 18 | class Photo(Model): 19 | tags = ReverseGFK(Tag, 'object_type', 'object_id') 20 | 21 | tag.object -> a blog or photo 22 | blog.tags -> select query of tags for ``blog`` instance 23 | Blog.tags -> select query of all tags for Blog instances 24 | """ 25 | 26 | from peewee import * 27 | from peewee import BaseModel as _BaseModel 28 | from peewee import Model as _Model 29 | from peewee import SelectQuery 30 | from peewee import UpdateQuery 31 | from peewee import with_metaclass 32 | 33 | 34 | all_models = set() 35 | table_cache = {} 36 | 37 | 38 | class BaseModel(_BaseModel): 39 | def __new__(cls, name, bases, attrs): 40 | cls = super(BaseModel, cls).__new__(cls, name, bases, attrs) 41 | if name not in ('_metaclass_helper_', 'Model'): 42 | all_models.add(cls) 43 | return cls 44 | 45 | class Model(with_metaclass(BaseModel, _Model)): 46 | pass 47 | 48 | def get_model(tbl_name): 49 | if tbl_name not in table_cache: 50 | for model in all_models: 51 | if model._meta.db_table == tbl_name: 52 | table_cache[tbl_name] = model 53 | break 54 | return table_cache.get(tbl_name) 55 | 56 | class BoundGFKField(object): 57 | __slots__ = ('model_class', 'gfk_field') 58 | 59 | def __init__(self, model_class, gfk_field): 60 | self.model_class = model_class 61 | self.gfk_field = gfk_field 62 | 63 | @property 64 | def unique(self): 65 | indexes = self.model_class._meta.indexes 66 | fields = set((self.gfk_field.model_type_field, 67 | self.gfk_field.model_id_field)) 68 | for (indexed_columns, is_unique) in indexes: 69 | if not fields - set(indexed_columns): 70 | return True 71 | return False 72 | 73 | @property 74 | def primary_key(self): 75 | pk = self.model_class._meta.primary_key 76 | if isinstance(pk, CompositeKey): 77 | fields = set((self.gfk_field.model_type_field, 78 | self.gfk_field.model_id_field)) 79 | if not fields - set(pk.field_names): 80 | return True 81 | return False 82 | 83 | def __eq__(self, other): 84 | meta = self.model_class._meta 85 | type_field = meta.fields[self.gfk_field.model_type_field] 86 | id_field = meta.fields[self.gfk_field.model_id_field] 87 | return ( 88 | (type_field == other._meta.db_table) & 89 | (id_field == other._get_pk_value())) 90 | 91 | def __ne__(self, other): 92 | other_cls = type(other) 93 | type_field = other._meta.fields[self.gfk_field.model_type_field] 94 | id_field = other._meta.fields[self.gfk_field.model_id_field] 95 | return ( 96 | (type_field == other._meta.db_table) & 97 | (id_field != other._get_pk_value())) 98 | 99 | 100 | class GFKField(object): 101 | def __init__(self, model_type_field='object_type', 102 | model_id_field='object_id'): 103 | self.model_type_field = model_type_field 104 | self.model_id_field = model_id_field 105 | self.att_name = '.'.join((self.model_type_field, self.model_id_field)) 106 | 107 | def get_obj(self, instance): 108 | data = instance._data 109 | if data.get(self.model_type_field) and data.get(self.model_id_field): 110 | tbl_name = data[self.model_type_field] 111 | model_class = get_model(tbl_name) 112 | if not model_class: 113 | raise AttributeError('Model for table "%s" not found in GFK ' 114 | 'lookup.' % tbl_name) 115 | query = model_class.select().where( 116 | model_class._meta.primary_key == data[self.model_id_field]) 117 | return query.get() 118 | 119 | def __get__(self, instance, instance_type=None): 120 | if instance: 121 | if self.att_name not in instance._obj_cache: 122 | rel_obj = self.get_obj(instance) 123 | if rel_obj: 124 | instance._obj_cache[self.att_name] = rel_obj 125 | return instance._obj_cache.get(self.att_name) 126 | return BoundGFKField(instance_type, self) 127 | 128 | def __set__(self, instance, value): 129 | instance._obj_cache[self.att_name] = value 130 | instance._data[self.model_type_field] = value._meta.db_table 131 | instance._data[self.model_id_field] = value._get_pk_value() 132 | 133 | 134 | class ReverseGFK(object): 135 | def __init__(self, model, model_type_field='object_type', 136 | model_id_field='object_id'): 137 | self.model_class = model 138 | self.model_type_field = model._meta.fields[model_type_field] 139 | self.model_id_field = model._meta.fields[model_id_field] 140 | 141 | def __get__(self, instance, instance_type=None): 142 | if instance: 143 | return self.model_class.select().where( 144 | (self.model_type_field == instance._meta.db_table) & 145 | (self.model_id_field == instance._get_pk_value()) 146 | ) 147 | else: 148 | return self.model_class.select().where( 149 | self.model_type_field == instance_type._meta.db_table 150 | ) 151 | 152 | def __set__(self, instance, value): 153 | mtv = instance._meta.db_table 154 | miv = instance._get_pk_value() 155 | if (isinstance(value, SelectQuery) and 156 | value.model_class == self.model_class): 157 | UpdateQuery(self.model_class, { 158 | self.model_type_field: mtv, 159 | self.model_id_field: miv, 160 | }).where(value._where).execute() 161 | elif all(map(lambda i: isinstance(i, self.model_class), value)): 162 | for obj in value: 163 | setattr(obj, self.model_type_field.name, mtv) 164 | setattr(obj, self.model_id_field.name, miv) 165 | obj.save() 166 | else: 167 | raise ValueError('ReverseGFK field unable to handle "%s"' % value) 168 | -------------------------------------------------------------------------------- /playhouse/hybrid.py: -------------------------------------------------------------------------------- 1 | # Hybrid methods/attributes, based on similar functionality in SQLAlchemy: 2 | # http://docs.sqlalchemy.org/en/improve_toc/orm/extensions/hybrid.html 3 | class hybrid_method(object): 4 | def __init__(self, func, expr=None): 5 | self.func = func 6 | self.expr = expr or func 7 | 8 | def __get__(self, instance, instance_type): 9 | if instance is None: 10 | return self.expr.__get__(instance_type, instance_type.__class__) 11 | return self.func.__get__(instance, instance_type) 12 | 13 | def expression(self, expr): 14 | self.expr = expr 15 | return self 16 | 17 | 18 | class hybrid_property(object): 19 | def __init__(self, fget, fset=None, fdel=None, expr=None): 20 | self.fget = fget 21 | self.fset = fset 22 | self.fdel = fdel 23 | self.expr = expr or fget 24 | 25 | def __get__(self, instance, instance_type): 26 | if instance is None: 27 | return self.expr(instance_type) 28 | return self.fget(instance) 29 | 30 | def __set__(self, instance, value): 31 | if self.fset is None: 32 | raise AttributeError('Cannot set attribute.') 33 | self.fset(instance, value) 34 | 35 | def __delete__(self, instance): 36 | if self.fdel is None: 37 | raise AttributeError('Cannot delete attribute.') 38 | self.fdel(instance) 39 | 40 | def setter(self, fset): 41 | self.fset = fset 42 | return self 43 | 44 | def deleter(self, fdel): 45 | self.fdel = fdel 46 | return self 47 | 48 | def expression(self, expr): 49 | self.expr = expr 50 | return self 51 | -------------------------------------------------------------------------------- /playhouse/kv.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import pickle 3 | try: 4 | import simplejson as json 5 | except ImportError: 6 | import json 7 | 8 | from peewee import * 9 | from peewee import Node 10 | from playhouse.fields import PickledField 11 | 12 | try: 13 | from playhouse.apsw_ext import APSWDatabase 14 | def KeyValueDatabase(db_name, **kwargs): 15 | return APSWDatabase(db_name, **kwargs) 16 | except ImportError: 17 | def KeyValueDatabase(db_name, **kwargs): 18 | return SqliteDatabase(db_name, check_same_thread=False, **kwargs) 19 | 20 | Sentinel = type('Sentinel', (object,), {}) 21 | 22 | key_value_db = KeyValueDatabase(':memory:', threadlocals=False) 23 | 24 | class JSONField(TextField): 25 | def db_value(self, value): 26 | return json.dumps(value) 27 | 28 | def python_value(self, value): 29 | if value is not None: 30 | return json.loads(value) 31 | 32 | class KeyStore(object): 33 | """ 34 | Rich dictionary with support for storing a wide variety of data types. 35 | 36 | :param peewee.Field value_type: Field type to use for values. 37 | :param boolean ordered: Whether keys should be returned in sorted order. 38 | :param peewee.Model model: Model class to use for Keys/Values. 39 | """ 40 | def __init__(self, value_field, ordered=False, database=None): 41 | self._value_field = value_field 42 | self._ordered = ordered 43 | 44 | self._database = database or key_value_db 45 | self._compiler = self._database.compiler() 46 | 47 | self.model = self.create_model() 48 | self.key = self.model.key 49 | self.value = self.model.value 50 | 51 | self._database.create_table(self.model, True) 52 | self._native_upsert = isinstance(self._database, SqliteDatabase) 53 | 54 | def create_model(self): 55 | class KVModel(Model): 56 | key = CharField(max_length=255, primary_key=True) 57 | value = self._value_field 58 | 59 | class Meta: 60 | database = self._database 61 | 62 | return KVModel 63 | 64 | def query(self, *select): 65 | query = self.model.select(*select).tuples() 66 | if self._ordered: 67 | query = query.order_by(self.key) 68 | return query 69 | 70 | def convert_node(self, node): 71 | if not isinstance(node, Node): 72 | return (self.key == node), True 73 | return node, False 74 | 75 | def __contains__(self, key): 76 | node, _ = self.convert_node(key) 77 | return self.model.select().where(node).exists() 78 | 79 | def __len__(self): 80 | return self.model.select().count() 81 | 82 | def __getitem__(self, node): 83 | converted, is_single = self.convert_node(node) 84 | result = self.query(self.value).where(converted) 85 | item_getter = operator.itemgetter(0) 86 | result = [item_getter(val) for val in result] 87 | if len(result) == 0 and is_single: 88 | raise KeyError(node) 89 | elif is_single: 90 | return result[0] 91 | return result 92 | 93 | def _upsert(self, key, value): 94 | self.model.insert(**{ 95 | self.key.name: key, 96 | self.value.name: value}).upsert().execute() 97 | 98 | def __setitem__(self, node, value): 99 | if isinstance(node, Node): 100 | update = {self.value.name: value} 101 | self.model.update(**update).where(node).execute() 102 | elif self._native_upsert: 103 | self._upsert(node, value) 104 | else: 105 | try: 106 | self.model.create(key=node, value=value) 107 | except: 108 | self._database.rollback() 109 | (self.model 110 | .update(**{self.value.name: value}) 111 | .where(self.key == node) 112 | .execute()) 113 | 114 | def __delitem__(self, node): 115 | converted, _ = self.convert_node(node) 116 | self.model.delete().where(converted).execute() 117 | 118 | def __iter__(self): 119 | return self.query().execute() 120 | 121 | def keys(self): 122 | return map(operator.itemgetter(0), self.query(self.key)) 123 | 124 | def values(self): 125 | return map(operator.itemgetter(0), self.query(self.value)) 126 | 127 | def items(self): 128 | return iter(self) 129 | 130 | def get(self, k, default=None): 131 | try: 132 | return self[k] 133 | except KeyError: 134 | return default 135 | 136 | def pop(self, k, default=Sentinel): 137 | with self._database.transaction(): 138 | node, is_single = self.convert_node(k) 139 | try: 140 | res = self[k] 141 | except KeyError: 142 | if default is Sentinel: 143 | raise 144 | return default 145 | del(self[node]) 146 | return res 147 | 148 | def clear(self): 149 | self.model.delete().execute() 150 | 151 | 152 | class PickledKeyStore(KeyStore): 153 | def __init__(self, ordered=False, database=None): 154 | super(PickledKeyStore, self).__init__( 155 | PickledField(), ordered, database) 156 | 157 | 158 | class JSONKeyStore(KeyStore): 159 | def __init__(self, ordered=False, database=None): 160 | field = JSONField(null=True) 161 | super(JSONKeyStore, self).__init__(field, ordered, database) 162 | -------------------------------------------------------------------------------- /playhouse/pskel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from collections import namedtuple 4 | import optparse 5 | 6 | template = """ 7 | #!/usr/bin/env python 8 | 9 | import logging 10 | 11 | from peewee import * 12 | from peewee import create_model_tables 13 | %(extra_import)s 14 | 15 | db = %(engine)s('%(database)s') 16 | 17 | class BaseModel(Model): 18 | class Meta: 19 | database = db 20 | 21 | %(models)s 22 | def main(): 23 | db.create_tables([%(model_names)s], True) 24 | %(logging)s 25 | 26 | if __name__ == '__main__': 27 | main() 28 | """.strip() 29 | 30 | model_template = """ 31 | class %(model_name)s(BaseModel): 32 | pass 33 | """ 34 | 35 | logging_template = """ 36 | logger = logging.getLogger('peewee') 37 | logger.setLevel(logging.DEBUG) 38 | logger.addHandler(logging.StreamHandler()) 39 | """ 40 | 41 | class Engine(namedtuple('_Engine', ('engine', 'imports'))): 42 | def __new__(cls, engine, imports=None): 43 | return super(Engine, cls).__new__(cls, engine, imports) 44 | 45 | def get_import(self): 46 | if self.imports: 47 | return 'from %s import *' % self.imports 48 | return '' 49 | 50 | engine_mapping = { 51 | 'postgres': Engine('PostgresqlDatabase'), 52 | 'postgres_ext': Engine('PostgresqlExtDatabase', 'playhouse.postgres_ext'), 53 | 'sqlite': Engine('SqliteDatabase'), 54 | 'sqlite_ext': Engine('SqliteExtDatabase', 'playhouse.sqlite_ext'), 55 | 'mysql': Engine('MySQLDatabase'), 56 | 'apsw': Engine('APSWDatabase', 'playhouse.apsw_ext'), 57 | 'bdb': Engine('BerkeleyDatabase', 'playhouse.berkeleydb'), 58 | } 59 | 60 | def render_models(models, engine, database, logging=False): 61 | rendered_models = [] 62 | class_names = [] 63 | 64 | for model in models: 65 | class_name = model.strip().title() 66 | class_names.append(class_name) 67 | rendered_models.append(model_template % {'model_name': class_name}) 68 | 69 | parameters = { 70 | 'database': database, 71 | 'engine': engine.engine, 72 | 'extra_import': engine.get_import(), 73 | 'logging': logging_template if logging else '', 74 | 'models': '\n'.join(rendered_models + ['']), 75 | 'model_names': ', '.join(class_names), 76 | } 77 | 78 | return template % parameters 79 | 80 | 81 | if __name__ == '__main__': 82 | parser = optparse.OptionParser( 83 | usage='Usage: %prog [options] model1 model2...') 84 | ao = parser.add_option 85 | ao('-l', '--logging', dest='logging', action='store_true') 86 | ao('-e', '--engine', dest='engine', choices=sorted(engine_mapping.keys()), 87 | default='sqlite') 88 | ao('-d', '--database', dest='database', default=':memory:') 89 | 90 | options, models = parser.parse_args() 91 | print(render_models( 92 | models, 93 | engine=engine_mapping[options.engine], 94 | database=options.database, 95 | logging=options.logging)) 96 | -------------------------------------------------------------------------------- /playhouse/read_slave.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for using a dedicated read-slave. The read database is specified as a 3 | Model.Meta option, and will be used for SELECT statements: 4 | 5 | 6 | master = PostgresqlDatabase('master') 7 | read_slave = PostgresqlDatabase('read_slave') 8 | 9 | class BaseModel(ReadSlaveModel): 10 | class Meta: 11 | database = master 12 | read_slaves = [read_slave] # This database will be used for SELECTs. 13 | 14 | 15 | # Now define your models as you would normally. 16 | class User(BaseModel): 17 | username = CharField() 18 | 19 | # To force a SELECT on the master database, you can instantiate the SelectQuery 20 | # by hand: 21 | master_select = SelectQuery(User).where(...) 22 | """ 23 | from peewee import * 24 | 25 | 26 | class ReadSlaveModel(Model): 27 | @classmethod 28 | def _get_read_database(cls): 29 | if not getattr(cls._meta, 'read_slaves', None): 30 | return cls._meta.database 31 | current_idx = getattr(cls, '_read_slave_idx', -1) 32 | cls._read_slave_idx = (current_idx + 1) % len(cls._meta.read_slaves) 33 | return cls._meta.read_slaves[cls._read_slave_idx] 34 | 35 | @classmethod 36 | def select(cls, *args, **kwargs): 37 | query = super(ReadSlaveModel, cls).select(*args, **kwargs) 38 | query.database = cls._get_read_database() 39 | return query 40 | 41 | @classmethod 42 | def raw(cls, *args, **kwargs): 43 | query = super(ReadSlaveModel, cls).raw(*args, **kwargs) 44 | if query._sql.lower().startswith('select'): 45 | query.database = cls._get_read_database() 46 | return query 47 | -------------------------------------------------------------------------------- /playhouse/signals.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provide django-style hooks for model events. 3 | """ 4 | from peewee import Model as _Model 5 | 6 | 7 | class Signal(object): 8 | def __init__(self): 9 | self._flush() 10 | 11 | def connect(self, receiver, name=None, sender=None): 12 | name = name or receiver.__name__ 13 | if name not in self._receivers: 14 | self._receivers[name] = (receiver, sender) 15 | self._receiver_list.append(name) 16 | else: 17 | raise ValueError('receiver named %s already connected' % name) 18 | 19 | def disconnect(self, receiver=None, name=None): 20 | if receiver: 21 | name = receiver.__name__ 22 | if name: 23 | del self._receivers[name] 24 | self._receiver_list.remove(name) 25 | else: 26 | raise ValueError('a receiver or a name must be provided') 27 | 28 | def __call__(self, name=None, sender=None): 29 | def decorator(fn): 30 | self.connect(fn, name, sender) 31 | return fn 32 | return decorator 33 | 34 | def send(self, instance, *args, **kwargs): 35 | sender = type(instance) 36 | responses = [] 37 | for name in self._receiver_list: 38 | r, s = self._receivers[name] 39 | if s is None or isinstance(instance, s): 40 | responses.append((r, r(sender, instance, *args, **kwargs))) 41 | return responses 42 | 43 | def _flush(self): 44 | self._receivers = {} 45 | self._receiver_list = [] 46 | 47 | 48 | pre_save = Signal() 49 | post_save = Signal() 50 | pre_delete = Signal() 51 | post_delete = Signal() 52 | pre_init = Signal() 53 | post_init = Signal() 54 | 55 | 56 | class Model(_Model): 57 | def __init__(self, *args, **kwargs): 58 | super(Model, self).__init__(*args, **kwargs) 59 | pre_init.send(self) 60 | 61 | def prepared(self): 62 | super(Model, self).prepared() 63 | post_init.send(self) 64 | 65 | def save(self, *args, **kwargs): 66 | pk_value = self._get_pk_value() 67 | created = kwargs.get('force_insert', False) or not bool(pk_value) 68 | pre_save.send(self, created=created) 69 | ret = super(Model, self).save(*args, **kwargs) 70 | post_save.send(self, created=created) 71 | return ret 72 | 73 | def delete_instance(self, *args, **kwargs): 74 | pre_delete.send(self) 75 | ret = super(Model, self).delete_instance(*args, **kwargs) 76 | post_delete.send(self) 77 | return ret 78 | -------------------------------------------------------------------------------- /playhouse/sqlcipher_ext.py: -------------------------------------------------------------------------------- 1 | """ 2 | Peewee integration with pysqlcipher. 3 | 4 | Project page: https://github.com/leapcode/pysqlcipher/ 5 | 6 | **WARNING!!! EXPERIMENTAL!!!** 7 | 8 | * Although this extention's code is short, it has not been propery 9 | peer-reviewed yet and may have introduced vulnerabilities. 10 | * The code contains minimum values for `passphrase` length and 11 | `kdf_iter`, as well as a default value for the later. 12 | **Do not** regard these numbers as advice. Consult the docs at 13 | http://sqlcipher.net/sqlcipher-api/ and security experts. 14 | 15 | Also note that this code relies on pysqlcipher and sqlcipher, and 16 | the code there might have vulnerabilities as well, but since these 17 | are widely used crypto modules, we can expect "short zero days" there. 18 | 19 | Example usage: 20 | 21 | from peewee.playground.ciphersql_ext import SqlCipherDatabase 22 | db = SqlCipherDatabase('/path/to/my.db', passphrase="don'tuseme4real", 23 | kdf_iter=1000000) 24 | 25 | * `passphrase`: should be "long enough". 26 | Note that *length beats vocabulary* (much exponential), and even 27 | a lowercase-only passphrase like easytorememberyethardforotherstoguess 28 | packs more noise than 8 random printable chatacters and *can* be memorized. 29 | * `kdf_iter`: Should be "as much as the weakest target machine can afford". 30 | 31 | When opening an existing database, passphrase and kdf_iter should be identical 32 | to the ones used when creating it. If they're wrong, an exception will only be 33 | raised **when you access the database**. 34 | 35 | If you need to ask for an interactive passphrase, here's example code you can 36 | put after the `db = ...` line: 37 | 38 | try: # Just access the database so that it checks the encryption. 39 | db.get_tables() 40 | # We're looking for a DatabaseError with a specific error message. 41 | except peewee.DatabaseError as e: 42 | # Check whether the message *means* "passphrase is wrong" 43 | if e.args[0] == 'file is encrypted or is not a database': 44 | raise Exception('Developer should Prompt user for passphrase ' 45 | 'again.') 46 | else: 47 | # A different DatabaseError. Raise it. 48 | raise e 49 | 50 | See a more elaborate example with this code at 51 | https://gist.github.com/thedod/11048875 52 | """ 53 | import datetime 54 | import decimal 55 | 56 | from peewee import * 57 | from playhouse.sqlite_ext import SqliteExtDatabase 58 | try: 59 | from pysqlcipher import dbapi2 as sqlcipher 60 | except ImportError: 61 | try: 62 | from pysqlcipher3 import dbapi2 as sqlcipher 63 | except ImportError: 64 | raise ImportError('Sqlcipher python bindings not found.') 65 | 66 | sqlcipher.register_adapter(decimal.Decimal, str) 67 | sqlcipher.register_adapter(datetime.date, str) 68 | sqlcipher.register_adapter(datetime.time, str) 69 | 70 | 71 | class _SqlCipherDatabase(object): 72 | def _connect(self, database, **kwargs): 73 | passphrase = kwargs.pop('passphrase', '') 74 | kdf_iter = kwargs.pop('kdf_iter', 64000) 75 | 76 | if len(passphrase) < 8: 77 | raise ImproperlyConfigured( 78 | 'SqlCipherDatabase passphrase should be at least eight ' 79 | 'character long.') 80 | 81 | if kdf_iter and kdf_iter < 10000: 82 | raise ImproperlyConfigured( 83 | 'SqlCipherDatabase kdf_iter should be at least 10000.') 84 | 85 | conn = sqlcipher.connect(database, **kwargs) 86 | self._add_conn_hooks(conn) 87 | conn.execute( 88 | 'PRAGMA key=\'{0}\''.format(passphrase.replace("'", "''"))) 89 | conn.execute('PRAGMA kdf_iter={0:d}'.format(kdf_iter)) 90 | return conn 91 | 92 | 93 | class SqlCipherDatabase(_SqlCipherDatabase, SqliteDatabase): 94 | pass 95 | 96 | 97 | class SqlCipherExtDatabase(_SqlCipherDatabase, SqliteExtDatabase): 98 | def __init__(self, *args, **kwargs): 99 | kwargs['c_extensions'] = False 100 | super(SqlCipherExtDatabase, self).__init__(*args, **kwargs) 101 | 102 | def _connect(self, *args, **kwargs): 103 | conn = super(SqlCipherExtDatabase, self)._connect(*args, **kwargs) 104 | 105 | self._load_aggregates(conn) 106 | self._load_collations(conn) 107 | self._load_functions(conn) 108 | if self._row_factory: 109 | conn.row_factory = self._row_factory 110 | if self._extensions: 111 | conn.enable_load_extension(True) 112 | for extension in self._extensions: 113 | conn.load_extension(extension) 114 | return conn 115 | -------------------------------------------------------------------------------- /playhouse/test_utils.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import logging 3 | 4 | from peewee import create_model_tables 5 | from peewee import drop_model_tables 6 | 7 | 8 | logger = logging.getLogger('peewee') 9 | 10 | class test_database(object): 11 | def __init__(self, db, models, create_tables=True, drop_tables=True, 12 | fail_silently=False): 13 | if not isinstance(models, (list, tuple, set)): 14 | raise ValueError('%r must be a list or tuple.' % models) 15 | self.db = db 16 | self.models = models 17 | self.create_tables = create_tables 18 | self.drop_tables = drop_tables 19 | self.fail_silently = fail_silently 20 | 21 | def __enter__(self): 22 | self.orig = [] 23 | for m in self.models: 24 | self.orig.append(m._meta.database) 25 | m._meta.database = self.db 26 | if self.create_tables: 27 | create_model_tables(self.models, fail_silently=self.fail_silently) 28 | 29 | def __exit__(self, exc_type, exc_val, exc_tb): 30 | if self.create_tables and self.drop_tables: 31 | drop_model_tables(self.models, fail_silently=self.fail_silently) 32 | for i, m in enumerate(self.models): 33 | m._meta.database = self.orig[i] 34 | 35 | 36 | class _QueryLogHandler(logging.Handler): 37 | def __init__(self, *args, **kwargs): 38 | self.queries = [] 39 | logging.Handler.__init__(self, *args, **kwargs) 40 | 41 | def emit(self, record): 42 | self.queries.append(record) 43 | 44 | 45 | class count_queries(object): 46 | def __init__(self, only_select=False): 47 | self.only_select = only_select 48 | self.count = 0 49 | 50 | def get_queries(self): 51 | return self._handler.queries 52 | 53 | def __enter__(self): 54 | self._handler = _QueryLogHandler() 55 | logger.setLevel(logging.DEBUG) 56 | logger.addHandler(self._handler) 57 | return self 58 | 59 | def __exit__(self, exc_type, exc_val, exc_tb): 60 | logger.removeHandler(self._handler) 61 | if self.only_select: 62 | self.count = len([q for q in self._handler.queries 63 | if q.msg[0].startswith('SELECT ')]) 64 | else: 65 | self.count = len(self._handler.queries) 66 | 67 | 68 | class assert_query_count(count_queries): 69 | def __init__(self, expected, only_select=False): 70 | super(assert_query_count, self).__init__(only_select=only_select) 71 | self.expected = expected 72 | 73 | def __call__(self, f): 74 | @wraps(f) 75 | def decorated(*args, **kwds): 76 | with self: 77 | ret = f(*args, **kwds) 78 | 79 | self._assert_count() 80 | return ret 81 | 82 | return decorated 83 | 84 | def _assert_count(self): 85 | error_msg = '%s != %s' % (self.count, self.expected) 86 | assert self.count == self.expected, error_msg 87 | 88 | def __exit__(self, exc_type, exc_val, exc_tb): 89 | super(assert_query_count, self).__exit__(exc_type, exc_val, exc_tb) 90 | self._assert_count() 91 | -------------------------------------------------------------------------------- /playhouse/tests/README: -------------------------------------------------------------------------------- 1 | Run peewee tests: 2 | 3 | $ python runtests.py (-e sqlite, -e postgres, -e mysql) 4 | 5 | Run playhouse tests: 6 | 7 | $ python runtests.py -x (-e sqlite, -e postgres, -e mysql) 8 | 9 | Run the entire suite, peewee and playhouse: 10 | 11 | $ python runtests.py -a (-e sqlite, -e postgres, -e mysql) 12 | 13 | Run a specific TestCase: 14 | 15 | $ python tests.py TestCompoundSelectSQL 16 | 17 | Run a specific test method: 18 | 19 | $ python tests.py TestCompoundSelectSQL.test_simple_same_model 20 | -------------------------------------------------------------------------------- /playhouse/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/playhouse/tests/__init__.py -------------------------------------------------------------------------------- /playhouse/tests/libs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/channelcat/aiopeewee/74d039ac4c32f9a346c5596541cf53c3ec6e389e/playhouse/tests/libs/__init__.py -------------------------------------------------------------------------------- /playhouse/tests/test_apis.py: -------------------------------------------------------------------------------- 1 | from peewee import Node 2 | from peewee import * 3 | from playhouse.tests.base import PeeweeTestCase 4 | 5 | 6 | class TestNodeAPI(PeeweeTestCase): 7 | def test_extend(self): 8 | @Node.extend() 9 | def add(self, lhs, rhs): 10 | return lhs + rhs 11 | 12 | n = Node() 13 | self.assertEqual(n.add(4, 2), 6) 14 | delattr(Node, 'add') 15 | self.assertRaises(AttributeError, lambda: n.add(2, 4)) 16 | 17 | def test_clone(self): 18 | @Node.extend(clone=True) 19 | def hack(self, alias): 20 | self._negated = True 21 | self._alias = alias 22 | 23 | n = Node() 24 | c = n.hack('magic!') 25 | self.assertFalse(n._negated) 26 | self.assertEqual(n._alias, None) 27 | self.assertTrue(c._negated) 28 | self.assertEqual(c._alias, 'magic!') 29 | 30 | class TestModel(Model): 31 | data = CharField() 32 | 33 | hacked = TestModel.data.hack('nugget') 34 | self.assertFalse(TestModel.data._negated) 35 | self.assertEqual(TestModel.data._alias, None) 36 | self.assertTrue(hacked._negated) 37 | self.assertEqual(hacked._alias, 'nugget') 38 | 39 | delattr(Node, 'hack') 40 | self.assertRaises(AttributeError, lambda: TestModel.data.hack()) 41 | -------------------------------------------------------------------------------- /playhouse/tests/test_apsw.py: -------------------------------------------------------------------------------- 1 | import apsw 2 | import datetime 3 | 4 | from playhouse.apsw_ext import * 5 | from playhouse.tests.base import ModelTestCase 6 | 7 | 8 | db = APSWDatabase(':memory:') 9 | 10 | class BaseModel(Model): 11 | class Meta: 12 | database = db 13 | 14 | class User(BaseModel): 15 | username = CharField() 16 | 17 | class Message(BaseModel): 18 | user = ForeignKeyField(User) 19 | message = TextField() 20 | pub_date = DateTimeField() 21 | published = BooleanField() 22 | 23 | 24 | class APSWTestCase(ModelTestCase): 25 | requires = [Message, User] 26 | 27 | def test_db_register_functions(self): 28 | result = db.execute_sql('SELECT date_part(?, ?)', ( 29 | 'day', '2015-01-02 03:04:05')).fetchone()[0] 30 | self.assertEqual(result, 2) 31 | 32 | result = db.execute_sql('SELECT date_trunc(?, ?)', ( 33 | 'day', '2015-01-02 03:04:05')).fetchone()[0] 34 | self.assertEqual(result, '2015-01-02') 35 | 36 | def test_db_pragmas(self): 37 | test_db = APSWDatabase(':memory:', pragmas=( 38 | ('cache_size', '1337'), 39 | )) 40 | test_db.connect() 41 | 42 | cs = test_db.execute_sql('PRAGMA cache_size;').fetchone()[0] 43 | self.assertEqual(cs, 1337) 44 | 45 | def test_select_insert(self): 46 | users = ('u1', 'u2', 'u3') 47 | for user in users: 48 | User.create(username=user) 49 | 50 | self.assertEqual([x.username for x in User.select()], ['u1', 'u2', 'u3']) 51 | self.assertEqual([x.username for x in User.select().filter(username='x')], []) 52 | self.assertEqual([x.username for x in User.select().filter(username__in=['u1', 'u3'])], ['u1', 'u3']) 53 | 54 | dt = datetime.datetime(2012, 1, 1, 11, 11, 11) 55 | Message.create(user=User.get(username='u1'), message='herps', pub_date=dt, published=True) 56 | Message.create(user=User.get(username='u2'), message='derps', pub_date=dt, published=False) 57 | 58 | m1 = Message.get(message='herps') 59 | self.assertEqual(m1.user.username, 'u1') 60 | self.assertEqual(m1.pub_date, dt) 61 | self.assertEqual(m1.published, True) 62 | 63 | m2 = Message.get(message='derps') 64 | self.assertEqual(m2.user.username, 'u2') 65 | self.assertEqual(m2.pub_date, dt) 66 | self.assertEqual(m2.published, False) 67 | 68 | def test_update_delete(self): 69 | u1 = User.create(username='u1') 70 | u2 = User.create(username='u2') 71 | 72 | u1.username = 'u1-modified' 73 | u1.save() 74 | 75 | self.assertEqual(User.select().count(), 2) 76 | self.assertEqual(User.get(username='u1-modified').id, u1.id) 77 | 78 | u1.delete_instance() 79 | self.assertEqual(User.select().count(), 1) 80 | 81 | def test_transaction_handling(self): 82 | dt = datetime.datetime(2012, 1, 1, 11, 11, 11) 83 | 84 | def do_ctx_mgr_error(): 85 | with db.transaction(): 86 | User.create(username='u1') 87 | raise ValueError 88 | 89 | self.assertRaises(ValueError, do_ctx_mgr_error) 90 | self.assertEqual(User.select().count(), 0) 91 | 92 | def do_ctx_mgr_success(): 93 | with db.transaction(): 94 | u = User.create(username='test') 95 | Message.create(message='testing', user=u, pub_date=dt, published=1) 96 | 97 | do_ctx_mgr_success() 98 | self.assertEqual(User.select().count(), 1) 99 | self.assertEqual(Message.select().count(), 1) 100 | 101 | @db.commit_on_success 102 | def create_error(): 103 | u = User.create(username='test') 104 | Message.create(message='testing', user=u, pub_date=dt, published=1) 105 | raise ValueError 106 | 107 | self.assertRaises(ValueError, create_error) 108 | self.assertEqual(User.select().count(), 1) 109 | 110 | @db.commit_on_success 111 | def create_success(): 112 | u = User.create(username='test') 113 | Message.create(message='testing', user=u, pub_date=dt, published=1) 114 | 115 | create_success() 116 | self.assertEqual(User.select().count(), 2) 117 | self.assertEqual(Message.select().count(), 2) 118 | 119 | def test_exists_regression(self): 120 | User.create(username='u1') 121 | self.assertTrue(User.select().where(User.username == 'u1').exists()) 122 | self.assertFalse(User.select().where(User.username == 'ux').exists()) 123 | -------------------------------------------------------------------------------- /playhouse/tests/test_berkeleydb.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from peewee import IntegrityError 5 | from playhouse.berkeleydb import * 6 | from playhouse.tests.base import database_initializer 7 | from playhouse.tests.base import ModelTestCase 8 | from playhouse.tests.base import skip_unless 9 | 10 | database = database_initializer.get_database('berkeleydb') 11 | 12 | class BaseModel(Model): 13 | class Meta: 14 | database = database 15 | 16 | class Person(BaseModel): 17 | name = CharField(unique=True) 18 | 19 | class Message(BaseModel): 20 | person = ForeignKeyField(Person, related_name='messages') 21 | body = TextField() 22 | 23 | 24 | @skip_unless(BerkeleyDatabase.check_pysqlite) 25 | class TestBerkeleyDatabase(ModelTestCase): 26 | requires = [Person, Message] 27 | 28 | def setUp(self): 29 | self.remove_db_files() 30 | super(TestBerkeleyDatabase, self).setUp() 31 | 32 | def tearDown(self): 33 | super(TestBerkeleyDatabase, self).tearDown() 34 | if not database.is_closed(): 35 | database.close() 36 | 37 | def remove_db_files(self): 38 | filename = database.database 39 | if os.path.exists(filename): 40 | os.unlink(filename) 41 | if os.path.exists(filename + '-journal'): 42 | shutil.rmtree(filename + '-journal') 43 | 44 | def test_storage_retrieval(self): 45 | pc = Person.create(name='charlie') 46 | ph = Person.create(name='huey') 47 | 48 | for i in range(3): 49 | Message.create(person=pc, body='message-%s' % i) 50 | 51 | self.assertEqual(Message.select().count(), 3) 52 | self.assertEqual(Person.select().count(), 2) 53 | self.assertEqual( 54 | [msg.body for msg in pc.messages.order_by(Message.body)], 55 | ['message-0', 'message-1', 'message-2']) 56 | self.assertEqual(list(ph.messages), []) 57 | 58 | def test_transaction(self): 59 | with database.transaction(): 60 | Person.create(name='charlie') 61 | 62 | self.assertEqual(Person.select().count(), 1) 63 | 64 | @database.commit_on_success 65 | def rollback(): 66 | Person.create(name='charlie') 67 | 68 | self.assertRaises(IntegrityError, rollback) 69 | self.assertEqual(Person.select().count(), 1) 70 | 71 | def _test_pragmas(self, db): 72 | class PragmaTest(Model): 73 | data = TextField() 74 | class Meta: 75 | database = db 76 | 77 | sql = lambda q: db.execute_sql(q).fetchone()[0] 78 | 79 | with db.execution_context() as ctx: 80 | PragmaTest.create_table() 81 | 82 | # Use another connection to check the pragma values. 83 | with db.execution_context() as ctx: 84 | conn = db.get_conn() 85 | cache = sql('PRAGMA cache_size;') 86 | page = sql('PRAGMA page_size;') 87 | mvcc = sql('PRAGMA multiversion;') 88 | self.assertEqual(cache, 1000) 89 | self.assertEqual(page, 2048) 90 | self.assertEqual(mvcc, 1) 91 | 92 | # Now, use two connections. This tests the weird behavior of the 93 | # BTree cache. 94 | conn = db.get_conn() 95 | self.assertEqual(sql('PRAGMA multiversion;'), 1) 96 | 97 | with db.execution_context(): 98 | conn2 = db.get_conn() 99 | self.assertTrue(id(conn) != id(conn2)) 100 | self.assertEqual(sql('PRAGMA cache_size;'), 1000) 101 | self.assertEqual(sql('PRAGMA multiversion;'), 1) 102 | self.assertEqual(sql('PRAGMA page_size;'), 2048) 103 | 104 | def test_pragmas(self): 105 | database.close() 106 | self.remove_db_files() 107 | 108 | db = BerkeleyDatabase( 109 | database.database, 110 | cache_size=1000, 111 | page_size=2048, 112 | multiversion=True) 113 | 114 | try: 115 | self._test_pragmas(db) 116 | finally: 117 | if not db.is_closed(): 118 | db.close() 119 | 120 | def test_udf(self): 121 | @database.func() 122 | def title(s): 123 | return s.title() 124 | 125 | with database.execution_context(): 126 | res = database.execute_sql('select title(?)', ('whats up',)) 127 | self.assertEqual(res.fetchone(), ('Whats Up',)) 128 | -------------------------------------------------------------------------------- /playhouse/tests/test_db_url.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | from playhouse.db_url import connect, parse 3 | from playhouse.sqlite_ext import SqliteExtDatabase 4 | from playhouse.tests.base import PeeweeTestCase 5 | 6 | 7 | class TestDBURL(PeeweeTestCase): 8 | def test_db_url_parse(self): 9 | cfg = parse('mysql://usr:pwd@hst:123/db') 10 | self.assertEqual(cfg['user'], 'usr') 11 | self.assertEqual(cfg['passwd'], 'pwd') 12 | self.assertEqual(cfg['host'], 'hst') 13 | self.assertEqual(cfg['database'], 'db') 14 | self.assertEqual(cfg['port'], 123) 15 | cfg = parse('postgresql://usr:pwd@hst/db') 16 | self.assertEqual(cfg['password'], 'pwd') 17 | cfg = parse('mysql+pool://usr:pwd@hst:123/db' 18 | '?max_connections=42&stale_timeout=8001.2&zai=&baz=3.4.5' 19 | '&boolz=false') 20 | self.assertEqual(cfg['user'], 'usr') 21 | self.assertEqual(cfg['password'], 'pwd') 22 | self.assertEqual(cfg['host'], 'hst') 23 | self.assertEqual(cfg['database'], 'db') 24 | self.assertEqual(cfg['port'], 123) 25 | self.assertEqual(cfg['max_connections'], 42) 26 | self.assertEqual(cfg['stale_timeout'], 8001.2) 27 | self.assertEqual(cfg['zai'], '') 28 | self.assertEqual(cfg['baz'], '3.4.5') 29 | self.assertEqual(cfg['boolz'], False) 30 | 31 | def test_db_url(self): 32 | db = connect('sqlite:///:memory:') 33 | self.assertTrue(isinstance(db, SqliteDatabase)) 34 | self.assertEqual(db.database, ':memory:') 35 | 36 | db = connect('sqlite:///:memory:', journal_mode='MEMORY') 37 | self.assertTrue(('journal_mode', 'MEMORY') in db._pragmas) 38 | 39 | db = connect('sqliteext:///foo/bar.db') 40 | self.assertTrue(isinstance(db, SqliteExtDatabase)) 41 | self.assertEqual(db.database, 'foo/bar.db') 42 | 43 | db = connect('sqlite:////this/is/absolute.path') 44 | self.assertEqual(db.database, '/this/is/absolute.path') 45 | 46 | db = connect('sqlite://') 47 | self.assertTrue(isinstance(db, SqliteDatabase)) 48 | self.assertEqual(db.database, ':memory:') 49 | 50 | def test_bad_scheme(self): 51 | def _test_scheme(): 52 | connect('missing:///') 53 | 54 | self.assertRaises(RuntimeError, _test_scheme) 55 | -------------------------------------------------------------------------------- /playhouse/tests/test_extra_fields.py: -------------------------------------------------------------------------------- 1 | import random 2 | import sys 3 | 4 | from peewee import * 5 | from playhouse.fields import * 6 | try: 7 | from playhouse.fields import AESEncryptedField 8 | except ImportError: 9 | AESEncryptedField = None 10 | try: 11 | from playhouse.fields import PasswordField 12 | except ImportError: 13 | PasswordField = None 14 | from playhouse.tests.base import database_initializer 15 | from playhouse.tests.base import ModelTestCase 16 | from playhouse.tests.base import skip_if 17 | from playhouse.tests.base import ulit 18 | from playhouse.tests.base import TestModel 19 | 20 | PY2 = sys.version_info[0] == 2 21 | 22 | 23 | db = database_initializer.get_in_memory_database() 24 | 25 | 26 | class BaseModel(Model): 27 | class Meta: 28 | database = db 29 | 30 | 31 | class CompressedModel(BaseModel): 32 | data = CompressedField() 33 | 34 | 35 | class PickledModel(BaseModel): 36 | data = PickledField() 37 | 38 | 39 | def convert_to_str(binary_data): 40 | if PY2: 41 | return str(binary_data) 42 | else: 43 | if isinstance(binary_data, str): 44 | return bytes(binary_data, 'utf-8') 45 | return binary_data 46 | 47 | 48 | class TestCompressedField(ModelTestCase): 49 | requires = [CompressedModel] 50 | 51 | def get_raw(self, cm): 52 | curs = db.execute_sql('SELECT data FROM %s WHERE id = %s;' % 53 | (CompressedModel._meta.db_table, cm.id)) 54 | return convert_to_str(curs.fetchone()[0]) 55 | 56 | def test_compressed_field(self): 57 | a_kb = 'a' * 1024 58 | b_kb = 'b' * 1024 59 | c_kb = 'c' * 1024 60 | d_kb = 'd' * 1024 61 | four_kb = ''.join((a_kb, b_kb, c_kb, d_kb)) 62 | data = four_kb * 16 # 64kb of data. 63 | cm = CompressedModel.create(data=data) 64 | cm_db = CompressedModel.get(CompressedModel.id == cm.id) 65 | self.assertEqual(cm_db.data, data) 66 | 67 | db_data = self.get_raw(cm) 68 | compressed = len(db_data) / float(len(data)) 69 | self.assertTrue(compressed < .01) 70 | 71 | def test_compress_random_data(self): 72 | data = ''.join( 73 | chr(random.randint(ord('A'), ord('z'))) 74 | for i in range(1024)) 75 | cm = CompressedModel.create(data=data) 76 | cm_db = CompressedModel.get(CompressedModel.id == cm.id) 77 | self.assertEqual(cm_db.data, data) 78 | 79 | 80 | @skip_if(lambda: AESEncryptedField is None) 81 | class TestAESEncryptedField(ModelTestCase): 82 | def setUp(self): 83 | class EncryptedModel(BaseModel): 84 | data = AESEncryptedField(key='testing') 85 | 86 | self.EncryptedModel = EncryptedModel 87 | self.requires = [EncryptedModel] 88 | super(TestAESEncryptedField, self).setUp() 89 | 90 | def test_encrypt_decrypt(self): 91 | field = self.EncryptedModel.data 92 | 93 | keys = ['testing', 'abcdefghijklmnop', 'a' * 31, 'a' * 32] 94 | for key in keys: 95 | field.key = key 96 | for i in range(128): 97 | data = ''.join(chr(65 + (j % 26)) for j in range(i)) 98 | encrypted = field.encrypt(data) 99 | decrypted = field.decrypt(encrypted) 100 | self.assertEqual(len(decrypted), i) 101 | if PY2: 102 | self.assertEqual(decrypted, data) 103 | else: 104 | self.assertEqual(decrypted, convert_to_str(data)) 105 | 106 | def test_encrypted_field(self): 107 | EM = self.EncryptedModel 108 | test_str = 'abcdefghij' 109 | em = EM.create(data=test_str) 110 | em_db = EM.get(EM.id == em.id) 111 | self.assertEqual(em_db.data, convert_to_str(test_str)) 112 | 113 | curs = db.execute_sql('SELECT data FROM %s WHERE id = %s' % 114 | (EM._meta.db_table, em.id)) 115 | raw_data = curs.fetchone()[0] 116 | self.assertNotEqual(raw_data, test_str) 117 | decrypted = EM.data.decrypt(raw_data) 118 | if PY2: 119 | self.assertEqual(decrypted, test_str) 120 | else: 121 | self.assertEqual(decrypted, convert_to_str(test_str)) 122 | 123 | EM.data.key = 'testingX' 124 | em_db_2 = EM.get(EM.id == em.id) 125 | self.assertNotEqual(em_db_2.data, test_str) 126 | 127 | # Because we pad the key with spaces until it is 32 bytes long, a 128 | # trailing space looks like the same key we used to encrypt with. 129 | EM.data.key = 'testing ' 130 | em_db_3 = EM.get(EM.id == em.id) 131 | self.assertEqual(em_db_3.data, convert_to_str(test_str)) 132 | 133 | 134 | @skip_if(lambda: PasswordField is None) 135 | class TestPasswordFields(ModelTestCase): 136 | def setUp(self): 137 | class PasswordModel(TestModel): 138 | username = TextField() 139 | password = PasswordField(iterations=4) 140 | 141 | self.PasswordModel = PasswordModel 142 | self.requires = [PasswordModel] 143 | super(TestPasswordFields, self).setUp() 144 | 145 | def test_valid_password(self): 146 | test_pwd = 'Hello!:)' 147 | 148 | tm = self.PasswordModel.create(username='User', password=test_pwd) 149 | tm_db = self.PasswordModel.get(self.PasswordModel.id == tm.id) 150 | 151 | self.assertTrue(tm_db.password.check_password(test_pwd),'Correct password did not match') 152 | 153 | def test_invalid_password(self): 154 | test_pwd = 'Hello!:)' 155 | 156 | tm = self.PasswordModel.create(username='User', password=test_pwd) 157 | tm_db = self.PasswordModel.get(self.PasswordModel.id == tm.id) 158 | 159 | self.assertFalse(tm_db.password.check_password('a'+test_pwd),'Incorrect password did match') 160 | 161 | def test_unicode(self): 162 | test_pwd = ulit('H\u00c3l\u00c5o!:)') 163 | 164 | tm = self.PasswordModel.create(username='User', password=test_pwd) 165 | tm_db = self.PasswordModel.get(self.PasswordModel.id == tm.id) 166 | 167 | self.assertTrue(tm_db.password.check_password(test_pwd),'Correct unicode password did not match') 168 | 169 | 170 | class TestPickledField(ModelTestCase): 171 | requires = [PickledModel] 172 | 173 | def test_pickled_field(self): 174 | test_1 = {'foo': [0, 1, '2']} 175 | test_2 = ['bar', ('nuggie', 'baze')] 176 | 177 | p1 = PickledModel.create(data=test_1) 178 | p2 = PickledModel.create(data=test_2) 179 | 180 | p1_db = PickledModel.get(PickledModel.id == p1.id) 181 | self.assertEqual(p1_db.data, test_1) 182 | 183 | p2_db = PickledModel.get(PickledModel.id == p2.id) 184 | self.assertEqual(p2_db.data, test_2) 185 | 186 | p1_db_g = PickledModel.get(PickledModel.data == test_1) 187 | self.assertEqual(p1_db_g.id, p1_db.id) 188 | -------------------------------------------------------------------------------- /playhouse/tests/test_flask_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from flask import Flask 4 | 5 | from peewee import * 6 | from playhouse.flask_utils import FlaskDB 7 | from playhouse.flask_utils import PaginatedQuery 8 | from playhouse.tests.base import ModelTestCase 9 | from playhouse.tests.base import PeeweeTestCase 10 | from playhouse.tests.base import test_db 11 | from playhouse.tests.models import * 12 | 13 | 14 | class TestPaginationHelpers(ModelTestCase): 15 | requires = [User] 16 | 17 | def setUp(self): 18 | super(TestPaginationHelpers, self).setUp() 19 | for i in range(10): 20 | User.create(username='u%02d' % i) 21 | 22 | self.app = Flask(__name__) 23 | 24 | def test_paginated_query(self): 25 | query = User.select().order_by(User.username) 26 | paginated_query = PaginatedQuery(query, 4) 27 | 28 | with self.app.test_request_context('/?page=2'): 29 | self.assertEqual(paginated_query.get_page(), 2) 30 | self.assertEqual(paginated_query.get_page_count(), 3) 31 | users = paginated_query.get_object_list() 32 | 33 | self.assertEqual( 34 | [user.username for user in users], 35 | ['u04', 'u05', 'u06', 'u07']) 36 | 37 | with self.app.test_request_context('/'): 38 | self.assertEqual(paginated_query.get_page(), 1) 39 | 40 | for value in ['1', '0', '-1', 'xxx']: 41 | with self.app.test_request_context('/?page=%s' % value): 42 | self.assertEqual(paginated_query.get_page(), 1) 43 | 44 | def test_bounds_checking(self): 45 | paginated_query = PaginatedQuery(User, 3, 'p', False) 46 | with self.app.test_request_context('/?p=5'): 47 | results = paginated_query.get_object_list() 48 | self.assertEqual(list(results), []) 49 | 50 | paginated_query = PaginatedQuery(User, 3, 'p', True) 51 | with self.app.test_request_context('/?p=2'): 52 | self.assertEqual(len(list(paginated_query.get_object_list())), 3) 53 | with self.app.test_request_context('/?p=4'): 54 | self.assertEqual(len(list(paginated_query.get_object_list())), 1) 55 | with self.app.test_request_context('/?p=5'): 56 | self.assertRaises(Exception, paginated_query.get_object_list) 57 | 58 | 59 | class TestFlaskDB(PeeweeTestCase): 60 | def tearDown(self): 61 | super(TestFlaskDB, self).tearDown() 62 | if not test_db.is_closed(): 63 | test_db.close() 64 | test_db.connect() 65 | 66 | def test_database(self): 67 | app = Flask(__name__) 68 | app.config.update({ 69 | 'DATABASE': { 70 | 'name': ':memory:', 71 | 'engine': 'peewee.SqliteDatabase'}}) 72 | database = FlaskDB(app) 73 | 74 | Model = database.Model 75 | self.assertTrue(isinstance(Model._meta.database, SqliteDatabase)) 76 | self.assertEqual(Model._meta.database.database, ':memory:') 77 | 78 | # Multiple calls reference the same object. 79 | self.assertTrue(database.Model is Model) 80 | 81 | def test_database_url(self): 82 | app = Flask(__name__) 83 | app.config['DATABASE'] = 'sqlite:///nugget.db' 84 | database = FlaskDB(app) 85 | Model = database.Model 86 | self.assertTrue(isinstance(Model._meta.database, SqliteDatabase)) 87 | self.assertEqual(Model._meta.database.database, 'nugget.db') 88 | 89 | # If a value is specified, it trumps config value. 90 | database = FlaskDB(app, 'sqlite:///nuglets.db') 91 | Model = database.Model 92 | self.assertEqual(Model._meta.database.database, 'nuglets.db') 93 | 94 | def test_database_instance(self): 95 | app = Flask(__name__) 96 | db = SqliteDatabase(':memory:') 97 | flask_db = FlaskDB(app, db) 98 | Model = flask_db.Model 99 | self.assertEqual(Model._meta.database, db) 100 | 101 | def test_database_instance_config(self): 102 | app = Flask(__name__) 103 | app.config['DATABASE'] = db = SqliteDatabase(':memory:') 104 | flask_db = FlaskDB(app) 105 | Model = flask_db.Model 106 | self.assertEqual(Model._meta.database, db) 107 | 108 | def test_deferred_database(self): 109 | app = Flask(__name__) 110 | app.config.update({ 111 | 'DATABASE': { 112 | 'name': ':memory:', 113 | 'engine': 'peewee.SqliteDatabase'}}) 114 | 115 | # Defer initialization of the database. 116 | database = FlaskDB() 117 | 118 | # Ensure we can access the Model attribute. 119 | Model = database.Model 120 | model_db = Model._meta.database 121 | 122 | # Because the database is not initialized, the models will point 123 | # to an uninitialized Proxy object. 124 | self.assertTrue(isinstance(model_db, Proxy)) 125 | self.assertRaises(AttributeError, lambda: model_db.database) 126 | 127 | class User(database.Model): 128 | username = CharField(unique=True) 129 | 130 | # Initialize the database with our Flask app. 131 | database.init_app(app) 132 | 133 | # Ensure the `Model` property points to the same object as it 134 | # did before. 135 | PostInitModel = database.Model 136 | self.assertTrue(Model is PostInitModel) 137 | 138 | # Ensure that the proxy is initialized. 139 | self.assertEqual(model_db.database, ':memory:') 140 | 141 | # Ensure we can use our database. 142 | User.create_table() 143 | for username in ['charlie', 'huey', 'zaizee']: 144 | User.create(username=username) 145 | 146 | self.assertEqual(User.select().count(), 3) 147 | users = User.select().order_by(User.username) 148 | self.assertEqual( 149 | [user.username for user in users], 150 | ['charlie', 'huey', 'zaizee']) 151 | 152 | self.assertEqual(User._meta.database, database.database) 153 | -------------------------------------------------------------------------------- /playhouse/tests/test_gfk.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | from playhouse.gfk import * 3 | from playhouse.tests.base import database_initializer 4 | from playhouse.tests.base import ModelTestCase 5 | 6 | 7 | db = database_initializer.get_in_memory_database() 8 | 9 | class BaseModel(Model): 10 | class Meta: 11 | database = db 12 | 13 | def add_tag(self, tag): 14 | t = Tag(tag=tag) 15 | t.object = self 16 | t.save() 17 | return t 18 | 19 | class Tag(BaseModel): 20 | tag = CharField() 21 | 22 | object_type = CharField(null=True) 23 | object_id = IntegerField(null=True) 24 | object = GFKField() 25 | 26 | class Meta: 27 | indexes = ( 28 | (('tag', 'object_type', 'object_id'), True), 29 | ) 30 | order_by = ('tag',) 31 | 32 | 33 | class Appetizer(BaseModel): 34 | name = CharField() 35 | tags = ReverseGFK(Tag) 36 | 37 | class Entree(BaseModel): 38 | name = CharField() 39 | tags = ReverseGFK(Tag) 40 | 41 | class Dessert(BaseModel): 42 | name = CharField() 43 | tags = ReverseGFK(Tag) 44 | 45 | 46 | 47 | class GFKTestCase(ModelTestCase): 48 | requires = [Tag, Appetizer, Entree, Dessert] 49 | 50 | data = { 51 | Appetizer: ( 52 | ('wings', ('fried', 'spicy')), 53 | ('mozzarella sticks', ('fried', 'sweet')), 54 | ('potstickers', ('fried',)), 55 | ('edamame', ('salty',)), 56 | ), 57 | Entree: ( 58 | ('phad thai', ('spicy',)), 59 | ('fried chicken', ('fried', 'salty')), 60 | ('tacos', ('fried', 'spicy')), 61 | ), 62 | Dessert: ( 63 | ('sundae', ('sweet',)), 64 | ('churro', ('fried', 'sweet')), 65 | ) 66 | } 67 | 68 | def create(self): 69 | for model, foods in self.data.items(): 70 | for name, tags in foods: 71 | inst = model.create(name=name) 72 | for tag in tags: 73 | inst.add_tag(tag) 74 | 75 | def test_creation(self): 76 | t = Tag.create(tag='a tag') 77 | t.object = t 78 | t.save() 79 | 80 | t_db = Tag.get(Tag.id == t.id) 81 | self.assertEqual(t_db.object_id, t_db._get_pk_value()) 82 | self.assertEqual(t_db.object_type, 'tag') 83 | self.assertEqual(t_db.object, t_db) 84 | 85 | def test_querying(self): 86 | self.create() 87 | 88 | tacos = Entree.get(Entree.name == 'tacos') 89 | tags = Tag.select().where(Tag.object == tacos).order_by(Tag.tag) 90 | self.assertEqual([tag.tag for tag in tags], ['fried', 'spicy']) 91 | 92 | def _test_get_create(self, method): 93 | a = Appetizer.create(name='walrus mix') 94 | tag, created = method(tag='walrus-food', object=a) 95 | self.assertTrue(created) 96 | self.assertEqual(tag.object, a) 97 | 98 | tag_db = Tag.get(Tag.id == tag.id) 99 | self.assertEqual(tag_db.object, a) 100 | 101 | tag, created = method(tag='walrus-food', object=a) 102 | self.assertFalse(created) 103 | self.assertEqual(Tag.select().count(), 1) 104 | self.assertEqual(tag, tag_db) 105 | 106 | tag2, created = method(tag='walrus-treats', object=a) 107 | self.assertTrue(created) 108 | tag2_db = Tag.get(Tag.id == tag2.id) 109 | self.assertEqual(tag2_db.tag, 'walrus-treats') 110 | self.assertEqual(tag2_db.object, a) 111 | 112 | b = Appetizer.create(name='walrus-meal') 113 | tag3, created = method(tag='walrus-treats', object=b) 114 | self.assertTrue(created) 115 | tag3_db = Tag.get(Tag.id == tag3.id) 116 | self.assertEqual(tag3_db.tag, 'walrus-treats') 117 | self.assertEqual(tag3_db.object, b) 118 | 119 | def test_get_or_create(self): 120 | self._test_get_create(Tag.get_or_create) 121 | 122 | def test_create_or_get(self): 123 | self._test_get_create(Tag.create_or_get) 124 | 125 | def test_gfk_api(self): 126 | self.create() 127 | 128 | # test instance api 129 | for model, foods in self.data.items(): 130 | for food, tags in foods: 131 | inst = model.get(model.name == food) 132 | self.assertEqual([t.tag for t in inst.tags], list(tags)) 133 | 134 | # test class api and ``object`` api 135 | apps_tags = [(t.tag, t.object.name) for t in Appetizer.tags.order_by(Tag.id)] 136 | data_tags = [] 137 | for food, tags in self.data[Appetizer]: 138 | for t in tags: 139 | data_tags.append((t, food)) 140 | 141 | self.assertEqual(apps_tags, data_tags) 142 | 143 | def test_missing(self): 144 | t = Tag.create(tag='sour') 145 | self.assertEqual(t.object, None) 146 | 147 | t.object_type = 'appetizer' 148 | t.object_id = 1 149 | # accessing the descriptor will raise a DoesNotExist 150 | self.assertRaises(Appetizer.DoesNotExist, getattr, t, 'object') 151 | 152 | t.object_type = 'unknown' 153 | t.object_id = 1 154 | self.assertRaises(AttributeError, getattr, t, 'object') 155 | 156 | def test_set_reverse(self): 157 | # assign query 158 | e = Entree.create(name='phad thai') 159 | s = Tag.create(tag='spicy') 160 | p = Tag.create(tag='peanuts') 161 | t = Tag.create(tag='thai') 162 | b = Tag.create(tag='beverage') 163 | 164 | e.tags = Tag.select().where(Tag.tag != 'beverage') 165 | self.assertEqual([t.tag for t in e.tags], ['peanuts', 'spicy', 'thai']) 166 | 167 | e = Entree.create(name='panang curry') 168 | c = Tag.create(tag='coconut') 169 | 170 | e.tags = [p, t, c, s] 171 | self.assertEqual([t.tag for t in e.tags], ['coconut', 'peanuts', 'spicy', 'thai']) 172 | -------------------------------------------------------------------------------- /playhouse/tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | from peewee import sort_models_topologically 3 | from playhouse.tests.base import PeeweeTestCase 4 | from playhouse.tests.base import test_db 5 | 6 | 7 | class TestHelperMethods(PeeweeTestCase): 8 | def test_assert_query_count(self): 9 | def execute_queries(n): 10 | for i in range(n): 11 | test_db.execute_sql('select 1;') 12 | 13 | with self.assertQueryCount(0): 14 | pass 15 | 16 | with self.assertQueryCount(1): 17 | execute_queries(1) 18 | 19 | with self.assertQueryCount(2): 20 | execute_queries(2) 21 | 22 | def fails_low(): 23 | with self.assertQueryCount(2): 24 | execute_queries(1) 25 | 26 | def fails_high(): 27 | with self.assertQueryCount(1): 28 | execute_queries(2) 29 | 30 | self.assertRaises(AssertionError, fails_low) 31 | self.assertRaises(AssertionError, fails_high) 32 | 33 | 34 | class TestTopologicalSorting(PeeweeTestCase): 35 | def test_topological_sort_fundamentals(self): 36 | FKF = ForeignKeyField 37 | # we will be topo-sorting the following models 38 | class A(Model): pass 39 | class B(Model): a = FKF(A) # must follow A 40 | class C(Model): a, b = FKF(A), FKF(B) # must follow A and B 41 | class D(Model): c = FKF(C) # must follow A and B and C 42 | class E(Model): e = FKF('self') 43 | # but excluding this model, which is a child of E 44 | class Excluded(Model): e = FKF(E) 45 | 46 | # property 1: output ordering must not depend upon input order 47 | repeatable_ordering = None 48 | for input_ordering in permutations([A, B, C, D, E]): 49 | output_ordering = sort_models_topologically(input_ordering) 50 | repeatable_ordering = repeatable_ordering or output_ordering 51 | self.assertEqual(repeatable_ordering, output_ordering) 52 | 53 | # property 2: output ordering must have same models as input 54 | self.assertEqual(len(output_ordering), 5) 55 | self.assertFalse(Excluded in output_ordering) 56 | 57 | # property 3: parents must precede children 58 | def assert_precedes(X, Y): 59 | lhs, rhs = map(output_ordering.index, [X, Y]) 60 | self.assertTrue(lhs < rhs) 61 | assert_precedes(A, B) 62 | assert_precedes(B, C) # if true, C follows A by transitivity 63 | assert_precedes(C, D) # if true, D follows A and B by transitivity 64 | 65 | # property 4: independent model hierarchies must be in name order 66 | assert_precedes(A, E) 67 | 68 | 69 | class TestDeclaredDependencies(PeeweeTestCase): 70 | def test_declared_dependencies(self): 71 | class A(Model): pass 72 | class B(Model): 73 | a = ForeignKeyField(A) 74 | b = ForeignKeyField('self') 75 | class NA(Model): 76 | class Meta: 77 | depends_on = (A, B) 78 | class C(Model): 79 | b = ForeignKeyField(B) 80 | c = ForeignKeyField('self') 81 | class Meta: 82 | depends_on = (NA,) 83 | class D1(Model): 84 | na = ForeignKeyField(NA) 85 | class Meta: 86 | depends_on = (A, C) 87 | class D2(Model): 88 | class Meta: 89 | depends_on = (NA, D1, C, B) 90 | 91 | models = [A, B, C, D1, D2] 92 | ordered = list(models) 93 | for pmodels in permutations(models): 94 | ordering = sort_models_topologically(pmodels) 95 | self.assertEqual(ordering, ordered) 96 | 97 | 98 | def permutations(xs): 99 | if not xs: 100 | yield [] 101 | else: 102 | for y, ys in selections(xs): 103 | for pys in permutations(ys): 104 | yield [y] + pys 105 | 106 | def selections(xs): 107 | for i in range(len(xs)): 108 | yield (xs[i], xs[:i] + xs[i + 1:]) 109 | -------------------------------------------------------------------------------- /playhouse/tests/test_hybrid.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | from playhouse.hybrid import hybrid_method 3 | from playhouse.hybrid import hybrid_property 4 | from playhouse.tests.base import database_initializer 5 | from playhouse.tests.base import ModelTestCase 6 | 7 | 8 | db = database_initializer.get_in_memory_database() 9 | 10 | class BaseModel(Model): 11 | class Meta: 12 | database = db 13 | 14 | class Interval(BaseModel): 15 | start = IntegerField() 16 | end = IntegerField() 17 | 18 | @hybrid_property 19 | def length(self): 20 | return self.end - self.start 21 | 22 | @hybrid_method 23 | def contains(self, point): 24 | return (self.start <= point) & (point < self.end) 25 | 26 | @hybrid_property 27 | def radius(self): 28 | return int(abs(self.length) / 2) 29 | 30 | @radius.expression 31 | def radius(cls): 32 | return fn.abs(cls.length) / 2 33 | 34 | 35 | class TestHybrid(ModelTestCase): 36 | requires = [Interval] 37 | 38 | def setUp(self): 39 | super(TestHybrid, self).setUp() 40 | intervals = ( 41 | (1, 5), 42 | (2, 6), 43 | (3, 5), 44 | (2, 5)) 45 | for start, end in intervals: 46 | Interval.create(start=start, end=end) 47 | 48 | def test_hybrid_property(self): 49 | query = Interval.select().where(Interval.length == 4) 50 | sql, params = query.sql() 51 | self.assertEqual(sql, ( 52 | 'SELECT "t1"."id", "t1"."start", "t1"."end" ' 53 | 'FROM "interval" AS t1 ' 54 | 'WHERE (("t1"."end" - "t1"."start") = ?)')) 55 | self.assertEqual(params, [4]) 56 | 57 | results = sorted( 58 | (interval.start, interval.end) 59 | for interval in query) 60 | self.assertEqual(results, [(1, 5), (2, 6)]) 61 | 62 | lengths = [4, 4, 2, 3] 63 | query = Interval.select().order_by(Interval.id) 64 | actuals = [interval.length for interval in query] 65 | self.assertEqual(actuals, lengths) 66 | 67 | def test_hybrid_method(self): 68 | query = Interval.select().where(Interval.contains(2)) 69 | sql, params = query.sql() 70 | self.assertEqual(sql, ( 71 | 'SELECT "t1"."id", "t1"."start", "t1"."end" ' 72 | 'FROM "interval" AS t1 ' 73 | 'WHERE (("t1"."start" <= ?) AND ("t1"."end" > ?))')) 74 | self.assertEqual(params, [2, 2]) 75 | 76 | results = sorted( 77 | (interval.start, interval.end) 78 | for interval in query) 79 | self.assertEqual(results, [(1, 5), (2, 5), (2, 6)]) 80 | 81 | contains = [True, True, False, True] 82 | query = Interval.select().order_by(Interval.id) 83 | actuals = [interval.contains(2) for interval in query] 84 | self.assertEqual(contains, actuals) 85 | 86 | def test_separate_expr(self): 87 | query = Interval.select().where(Interval.radius == 2) 88 | sql, params = query.sql() 89 | self.assertEqual(sql, ( 90 | 'SELECT "t1"."id", "t1"."start", "t1"."end" ' 91 | 'FROM "interval" AS t1 ' 92 | 'WHERE ((abs("t1"."end" - "t1"."start") / ?) = ?)')) 93 | self.assertEqual(params, [2, 2]) 94 | 95 | results = sorted( 96 | (interval.start, interval.end) 97 | for interval in query) 98 | self.assertEqual(results, [(1, 5), (2, 6)]) 99 | 100 | radii = [2, 2, 1, 1] 101 | query = Interval.select().order_by(Interval.id) 102 | actuals = [interval.radius for interval in query] 103 | self.assertEqual(actuals, radii) 104 | -------------------------------------------------------------------------------- /playhouse/tests/test_introspection.py: -------------------------------------------------------------------------------- 1 | from playhouse.tests.base import database_class 2 | from playhouse.tests.base import ModelTestCase 3 | from playhouse.tests.base import test_db 4 | from playhouse.tests.models import * 5 | 6 | 7 | class TestMetadataIntrospection(ModelTestCase): 8 | requires = [ 9 | User, Blog, Comment, CompositeKeyModel, MultiIndexModel, UniqueModel, 10 | Category] 11 | 12 | def setUp(self): 13 | super(TestMetadataIntrospection, self).setUp() 14 | self.pk_index = database_class is not SqliteDatabase 15 | 16 | def test_get_tables(self): 17 | tables = test_db.get_tables() 18 | for model in self.requires: 19 | self.assertTrue(model._meta.db_table in tables) 20 | 21 | UniqueModel.drop_table() 22 | self.assertFalse(UniqueModel._meta.db_table in test_db.get_tables()) 23 | 24 | def test_get_indexes(self): 25 | indexes = test_db.get_indexes(UniqueModel._meta.db_table) 26 | num_indexes = self.pk_index and 2 or 1 27 | self.assertEqual(len(indexes), num_indexes) 28 | 29 | idx, = [idx for idx in indexes if idx.name == 'uniquemodel_name'] 30 | self.assertEqual(idx.columns, ['name']) 31 | self.assertTrue(idx.unique) 32 | 33 | indexes = dict( 34 | (idx.name, idx) for idx in 35 | test_db.get_indexes(MultiIndexModel._meta.db_table)) 36 | num_indexes = self.pk_index and 3 or 2 37 | self.assertEqual(len(indexes), num_indexes) 38 | 39 | idx_f1f2 = indexes['multiindexmodel_f1_f2'] 40 | self.assertEqual(sorted(idx_f1f2.columns), ['f1', 'f2']) 41 | self.assertTrue(idx_f1f2.unique) 42 | 43 | idx_f2f3 = indexes['multiindexmodel_f2_f3'] 44 | self.assertEqual(sorted(idx_f2f3.columns), ['f2', 'f3']) 45 | self.assertFalse(idx_f2f3.unique) 46 | self.assertEqual(idx_f2f3.table, 'multiindexmodel') 47 | 48 | # SQLite *will* create an index here, so we will always have one. 49 | indexes = test_db.get_indexes(CompositeKeyModel._meta.db_table) 50 | self.assertEqual(len(indexes), 1) 51 | self.assertEqual(sorted(indexes[0].columns), ['f1', 'f2']) 52 | self.assertTrue(indexes[0].unique) 53 | 54 | def test_get_columns(self): 55 | def get_columns(model): 56 | return dict( 57 | (column.name, column) 58 | for column in test_db.get_columns(model._meta.db_table)) 59 | 60 | def assertColumns(model, col_names, nullable, pks): 61 | columns = get_columns(model) 62 | self.assertEqual(sorted(columns), col_names) 63 | for column, metadata in columns.items(): 64 | self.assertEqual(metadata.null, column in nullable) 65 | self.assertEqual(metadata.table, model._meta.db_table) 66 | self.assertEqual(metadata.primary_key, column in pks) 67 | 68 | assertColumns(User, ['id', 'username'], [], ['id']) 69 | assertColumns( 70 | Blog, 71 | ['content', 'pk', 'pub_date', 'title', 'user_id'], 72 | ['pub_date'], 73 | ['pk']) 74 | assertColumns(UniqueModel, ['id', 'name'], [], ['id']) 75 | assertColumns(MultiIndexModel, ['f1', 'f2', 'f3', 'id'], [], ['id']) 76 | assertColumns( 77 | CompositeKeyModel, 78 | ['f1', 'f2', 'f3'], 79 | [], 80 | ['f1', 'f2']) 81 | assertColumns( 82 | Category, 83 | ['id', 'name', 'parent_id'], 84 | ['parent_id'], 85 | ['id']) 86 | 87 | def test_get_primary_keys(self): 88 | def assertPKs(model_class, expected): 89 | self.assertEqual( 90 | test_db.get_primary_keys(model_class._meta.db_table), 91 | expected) 92 | 93 | assertPKs(User, ['id']) 94 | assertPKs(Blog, ['pk']) 95 | assertPKs(MultiIndexModel, ['id']) 96 | assertPKs(CompositeKeyModel, ['f1', 'f2']) 97 | assertPKs(UniqueModel, ['id']) 98 | assertPKs(Category, ['id']) 99 | 100 | def test_get_foreign_keys(self): 101 | def assertFKs(model_class, expected): 102 | foreign_keys = test_db.get_foreign_keys(model_class._meta.db_table) 103 | self.assertEqual(len(foreign_keys), len(expected)) 104 | self.assertEqual( 105 | [(fk.column, fk.dest_table, fk.dest_column) 106 | for fk in foreign_keys], 107 | expected) 108 | 109 | assertFKs(Category, [('parent_id', 'category', 'id')]) 110 | assertFKs(User, []) 111 | assertFKs(Blog, [('user_id', 'users', 'id')]) 112 | assertFKs(Comment, [('blog_id', 'blog', 'pk')]) 113 | -------------------------------------------------------------------------------- /playhouse/tests/test_kv.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from peewee import * 4 | from playhouse.kv import JSONKeyStore 5 | from playhouse.kv import KeyStore 6 | from playhouse.kv import PickledKeyStore 7 | from playhouse.tests.base import PeeweeTestCase 8 | from playhouse.tests.base import skip_if 9 | 10 | 11 | class TestKeyStore(PeeweeTestCase): 12 | def setUp(self): 13 | super(TestKeyStore, self).setUp() 14 | self.kv = KeyStore(CharField()) 15 | self.ordered_kv = KeyStore(CharField(), ordered=True) 16 | self.pickled_kv = PickledKeyStore(ordered=True) 17 | self.json_kv = JSONKeyStore(ordered=True) 18 | self.kv.clear() 19 | self.json_kv.clear() 20 | 21 | def test_json(self): 22 | self.json_kv['foo'] = 'bar' 23 | self.json_kv['baze'] = {'baze': [1, 2, 3]} 24 | self.json_kv['nugget'] = None 25 | 26 | self.assertEqual(self.json_kv['foo'], 'bar') 27 | self.assertEqual(self.json_kv['baze'], {'baze': [1, 2, 3]}) 28 | self.assertIsNone(self.json_kv['nugget']) 29 | self.assertRaises(KeyError, lambda: self.json_kv['missing']) 30 | 31 | results = self.json_kv[self.json_kv.key << ['baze', 'bar', 'nugget']] 32 | self.assertEqual(results, [ 33 | {'baze': [1, 2, 3]}, 34 | None, 35 | ]) 36 | 37 | def test_storage(self): 38 | self.kv['a'] = 'A' 39 | self.kv['b'] = 1 40 | self.assertEqual(self.kv['a'], 'A') 41 | self.assertEqual(self.kv['b'], '1') 42 | self.assertRaises(KeyError, self.kv.__getitem__, 'c') 43 | 44 | del(self.kv['a']) 45 | self.assertRaises(KeyError, self.kv.__getitem__, 'a') 46 | 47 | self.kv['a'] = 'A' 48 | self.kv['c'] = 'C' 49 | self.assertEqual(self.kv[self.kv.key << ('a', 'c')], ['A', 'C']) 50 | 51 | self.kv[self.kv.key << ('a', 'c')] = 'X' 52 | self.assertEqual(self.kv['a'], 'X') 53 | self.assertEqual(self.kv['b'], '1') 54 | self.assertEqual(self.kv['c'], 'X') 55 | 56 | key = self.kv.key 57 | results = self.kv[key << ('a', 'b')] 58 | self.assertEqual(results, ['X', '1']) 59 | 60 | del(self.kv[self.kv.key << ('a', 'c')]) 61 | self.assertRaises(KeyError, self.kv.__getitem__, 'a') 62 | self.assertRaises(KeyError, self.kv.__getitem__, 'c') 63 | self.assertEqual(self.kv['b'], '1') 64 | 65 | self.pickled_kv['a'] = 'A' 66 | self.pickled_kv['b'] = 1.1 67 | self.assertEqual(self.pickled_kv['a'], 'A') 68 | self.assertEqual(self.pickled_kv['b'], 1.1) 69 | 70 | def test_container_properties(self): 71 | self.kv['x'] = 'X' 72 | self.kv['y'] = 'Y' 73 | self.assertEqual(len(self.kv), 2) 74 | self.assertTrue('x' in self.kv) 75 | self.assertFalse('a' in self.kv) 76 | 77 | def test_dict_methods(self): 78 | for kv in (self.ordered_kv, self.pickled_kv): 79 | kv['a'] = 'A' 80 | kv['c'] = 'C' 81 | kv['b'] = 'B' 82 | self.assertEqual(list(kv.keys()), ['a', 'b', 'c']) 83 | self.assertEqual(list(kv.values()), ['A', 'B', 'C']) 84 | self.assertEqual(list(kv.items()), [ 85 | ('a', 'A'), 86 | ('b', 'B'), 87 | ('c', 'C'), 88 | ]) 89 | 90 | def test_iteration(self): 91 | for kv in (self.ordered_kv, self.pickled_kv): 92 | kv['a'] = 'A' 93 | kv['c'] = 'C' 94 | kv['b'] = 'B' 95 | 96 | items = list(kv) 97 | self.assertEqual(items, [ 98 | ('a', 'A'), 99 | ('b', 'B'), 100 | ('c', 'C'), 101 | ]) 102 | 103 | def test_shared_mem(self): 104 | self.kv['a'] = 'xxx' 105 | self.assertEqual(self.ordered_kv['a'], 'xxx') 106 | 107 | def set_k(): 108 | kv_t = KeyStore(CharField()) 109 | kv_t['b'] = 'yyy' 110 | t = threading.Thread(target=set_k) 111 | t.start() 112 | t.join() 113 | 114 | self.assertEqual(self.kv['b'], 'yyy') 115 | 116 | def test_get(self): 117 | self.kv['a'] = 'A' 118 | self.kv['b'] = 'B' 119 | self.assertEqual(self.kv.get('a'), 'A') 120 | self.assertEqual(self.kv.get('x'), None) 121 | self.assertEqual(self.kv.get('x', 'y'), 'y') 122 | 123 | self.assertEqual( 124 | list(self.kv.get(self.kv.key << ('a', 'b'))), 125 | ['A', 'B']) 126 | self.assertEqual( 127 | list(self.kv.get(self.kv.key << ('x', 'y'))), 128 | []) 129 | 130 | def test_pop(self): 131 | self.ordered_kv['a'] = 'A' 132 | self.ordered_kv['b'] = 'B' 133 | self.ordered_kv['c'] = 'C' 134 | 135 | self.assertEqual(self.ordered_kv.pop('a'), 'A') 136 | self.assertEqual(list(self.ordered_kv.keys()), ['b', 'c']) 137 | 138 | self.assertRaises(KeyError, self.ordered_kv.pop, 'x') 139 | self.assertEqual(self.ordered_kv.pop('x', 'y'), 'y') 140 | 141 | self.assertEqual( 142 | list(self.ordered_kv.pop(self.ordered_kv.key << ['b', 'c'])), 143 | ['B', 'C']) 144 | 145 | self.assertEqual(list(self.ordered_kv.keys()), []) 146 | 147 | try: 148 | import psycopg2 149 | except ImportError: 150 | psycopg2 = None 151 | 152 | @skip_if(lambda: psycopg2 is None) 153 | class TestPostgresqlKeyStore(PeeweeTestCase): 154 | def setUp(self): 155 | self.db = PostgresqlDatabase('peewee_test') 156 | self.kv = KeyStore(CharField(), ordered=True, database=self.db) 157 | self.kv.clear() 158 | 159 | def tearDown(self): 160 | self.db.close() 161 | 162 | def test_non_native_upsert(self): 163 | self.kv['a'] = 'A' 164 | self.kv['b'] = 'B' 165 | self.assertEqual(self.kv['a'], 'A') 166 | 167 | self.kv['a'] = 'C' 168 | self.assertEqual(self.kv['a'], 'C') 169 | -------------------------------------------------------------------------------- /playhouse/tests/test_pwiz.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | try: 4 | from StringIO import StringIO 5 | except ImportError: 6 | from io import StringIO 7 | import textwrap 8 | import sys 9 | 10 | from peewee import * 11 | from pwiz import * 12 | from playhouse.tests.base import database_initializer 13 | from playhouse.tests.base import mock 14 | from playhouse.tests.base import PeeweeTestCase 15 | from playhouse.tests.base import skip_if 16 | 17 | 18 | db = database_initializer.get_database('sqlite') 19 | 20 | class BaseModel(Model): 21 | class Meta: 22 | database = db 23 | 24 | class User(BaseModel): 25 | username = CharField(primary_key=True) 26 | id = IntegerField(default=0) 27 | 28 | class Note(BaseModel): 29 | user = ForeignKeyField(User) 30 | text = TextField(index=True) 31 | data = IntegerField(default=0) 32 | misc = IntegerField(default=0) 33 | 34 | class Meta: 35 | indexes = ( 36 | (('user', 'text'), True), 37 | (('user', 'data', 'misc'), False), 38 | ) 39 | 40 | class Category(BaseModel): 41 | name = CharField(unique=True) 42 | parent = ForeignKeyField('self', null=True) 43 | 44 | class OddColumnNames(BaseModel): 45 | spaces = CharField(db_column='s p aces') 46 | symbols = CharField(db_column='w/-nug!') 47 | 48 | class capture_output(object): 49 | def __enter__(self): 50 | self._stdout = sys.stdout 51 | sys.stdout = self._buffer = StringIO() 52 | return self 53 | 54 | def __exit__(self, *args): 55 | self.data = self._buffer.getvalue() 56 | sys.stdout = self._stdout 57 | 58 | EXPECTED = """ 59 | from peewee import * 60 | 61 | database = SqliteDatabase('/tmp/peewee_test.db', **{}) 62 | 63 | class UnknownField(object): 64 | def __init__(self, *_, **__): pass 65 | 66 | class BaseModel(Model): 67 | class Meta: 68 | database = database 69 | 70 | class Category(BaseModel): 71 | name = CharField(unique=True) 72 | parent = ForeignKeyField(db_column='parent_id', null=True, rel_model='self', to_field='id') 73 | 74 | class Meta: 75 | db_table = 'category' 76 | 77 | class User(BaseModel): 78 | id = IntegerField() 79 | username = CharField(primary_key=True) 80 | 81 | class Meta: 82 | db_table = 'user' 83 | 84 | class Note(BaseModel): 85 | data = IntegerField() 86 | misc = IntegerField() 87 | text = TextField(index=True) 88 | user = ForeignKeyField(db_column='user_id', rel_model=User, to_field='username') 89 | 90 | class Meta: 91 | db_table = 'note' 92 | indexes = ( 93 | (('user', 'data', 'misc'), False), 94 | (('user', 'text'), True), 95 | ) 96 | """.strip() 97 | 98 | 99 | EXPECTED_ORDERED = """ 100 | from peewee import * 101 | 102 | database = SqliteDatabase('/tmp/peewee_test.db', **{}) 103 | 104 | class UnknownField(object): 105 | def __init__(self, *_, **__): pass 106 | 107 | class BaseModel(Model): 108 | class Meta: 109 | database = database 110 | 111 | class User(BaseModel): 112 | username = CharField(primary_key=True) 113 | id = IntegerField() 114 | 115 | class Meta: 116 | db_table = 'user' 117 | 118 | class Note(BaseModel): 119 | user = ForeignKeyField(db_column='user_id', rel_model=User, to_field='username') 120 | text = TextField(index=True) 121 | data = IntegerField() 122 | misc = IntegerField() 123 | 124 | class Meta: 125 | db_table = 'note' 126 | indexes = ( 127 | (('user', 'data', 'misc'), False), 128 | (('user', 'text'), True), 129 | ) 130 | """.strip() 131 | 132 | 133 | class BasePwizTestCase(PeeweeTestCase): 134 | models = [] 135 | 136 | def setUp(self): 137 | super(BasePwizTestCase, self).setUp() 138 | if os.path.exists(db.database): 139 | os.unlink(db.database) 140 | db.connect() 141 | db.create_tables(self.models) 142 | self.introspector = Introspector.from_database(db) 143 | 144 | def tearDown(self): 145 | super(BasePwizTestCase, self).tearDown() 146 | db.drop_tables(self.models) 147 | db.close() 148 | 149 | 150 | class TestPwiz(BasePwizTestCase): 151 | models = [User, Note, Category] 152 | 153 | def test_print_models(self): 154 | with capture_output() as output: 155 | print_models(self.introspector) 156 | 157 | self.assertEqual(output.data.strip(), EXPECTED) 158 | 159 | def test_print_header(self): 160 | cmdline = '-i -e sqlite %s' % db.database 161 | 162 | with capture_output() as output: 163 | with mock.patch('pwiz.datetime.datetime') as mock_datetime: 164 | now = mock_datetime.now.return_value 165 | now.strftime.return_value = 'February 03, 2015 15:30PM' 166 | print_header(cmdline, self.introspector) 167 | 168 | self.assertEqual(output.data.strip(), ( 169 | '# Code generated by:\n' 170 | '# python -m pwiz %s\n' 171 | '# Date: February 03, 2015 15:30PM\n' 172 | '# Database: %s\n' 173 | '# Peewee version: %s') % (cmdline, db.database, peewee_version)) 174 | 175 | 176 | @skip_if(lambda: sys.version_info[:2] < (2, 7)) 177 | class TestPwizOrdered(BasePwizTestCase): 178 | models = [User, Note] 179 | 180 | def test_ordered_columns(self): 181 | with capture_output() as output: 182 | print_models(self.introspector, preserve_order=True) 183 | 184 | self.assertEqual(output.data.strip(), EXPECTED_ORDERED) 185 | 186 | 187 | class TestPwizInvalidColumns(BasePwizTestCase): 188 | models = [OddColumnNames] 189 | 190 | def test_invalid_columns(self): 191 | with capture_output() as output: 192 | print_models(self.introspector) 193 | 194 | result = output.data.strip() 195 | expected = textwrap.dedent(""" 196 | class Oddcolumnnames(BaseModel): 197 | s_p_aces = CharField(db_column='s p aces') 198 | w_nug_ = CharField(db_column='w/-nug!') 199 | 200 | class Meta: 201 | db_table = 'oddcolumnnames'""").strip() 202 | 203 | actual = result[-len(expected):] 204 | self.assertEqual(actual, expected) 205 | -------------------------------------------------------------------------------- /playhouse/tests/test_read_slave.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | from peewee import Using 3 | from playhouse.read_slave import ReadSlaveModel 4 | from playhouse.tests.base import database_initializer 5 | from playhouse.tests.base import ModelTestCase 6 | 7 | 8 | queries = [] 9 | 10 | def reset(): 11 | global queries 12 | queries = [] 13 | 14 | class QueryLogDatabase(SqliteDatabase): 15 | name = '' 16 | 17 | def execute_sql(self, query, *args, **kwargs): 18 | queries.append((self.name, query)) 19 | return super(QueryLogDatabase, self).execute_sql( 20 | query, *args, **kwargs) 21 | 22 | class Master(QueryLogDatabase): 23 | name = 'master' 24 | 25 | class Slave1(QueryLogDatabase): 26 | name = 'slave1' 27 | 28 | class Slave2(QueryLogDatabase): 29 | name = 'slave2' 30 | 31 | master = database_initializer.get_database('sqlite', db_class=Master) 32 | slave1 = database_initializer.get_database('sqlite', db_class=Slave1) 33 | slave2 = database_initializer.get_database('sqlite', db_class=Slave2) 34 | 35 | # Models to use for testing read slaves. 36 | 37 | class BaseModel(ReadSlaveModel): 38 | class Meta: 39 | database = master 40 | read_slaves = [slave1, slave2] 41 | 42 | class User(BaseModel): 43 | username = CharField() 44 | 45 | class Thing(BaseModel): 46 | name = CharField() 47 | 48 | class Meta: 49 | read_slaves = [slave2] 50 | 51 | # Regular models to use for testing `Using`. 52 | 53 | class BaseMasterOnly(Model): 54 | class Meta: 55 | database = master 56 | 57 | class A(BaseMasterOnly): 58 | data = CharField() 59 | 60 | class B(BaseMasterOnly): 61 | data = CharField() 62 | 63 | 64 | class TestUsing(ModelTestCase): 65 | requires = [A, B] 66 | 67 | def setUp(self): 68 | super(TestUsing, self).setUp() 69 | reset() 70 | 71 | def assertDatabaseVerb(self, expected): 72 | db_and_verb = [(db, sql.split()[0]) for db, sql in queries] 73 | self.assertEqual(db_and_verb, expected) 74 | reset() 75 | 76 | def test_using_context(self): 77 | models = [A, B] 78 | 79 | with Using(slave1, models, False): 80 | A.create(data='a1') 81 | B.create(data='b1') 82 | 83 | self.assertDatabaseVerb([ 84 | ('slave1', 'INSERT'), 85 | ('slave1', 'INSERT')]) 86 | 87 | with Using(slave2, models, False): 88 | A.create(data='a2') 89 | B.create(data='b2') 90 | a_obj = A.select().order_by(A.id).get() 91 | self.assertEqual(a_obj.data, 'a1') 92 | 93 | self.assertDatabaseVerb([ 94 | ('slave2', 'INSERT'), 95 | ('slave2', 'INSERT'), 96 | ('slave2', 'SELECT')]) 97 | 98 | with Using(master, models, False): 99 | query = A.select().order_by(A.data.desc()) 100 | values = [a_obj.data for a_obj in query] 101 | 102 | self.assertEqual(values, ['a2', 'a1']) 103 | self.assertDatabaseVerb([('master', 'SELECT')]) 104 | 105 | def test_using_transactions(self): 106 | with Using(slave1, [A]) as txn: 107 | list(B.select()) 108 | A.create(data='a1') 109 | 110 | B.create(data='b1') 111 | self.assertDatabaseVerb([ 112 | ('slave1', 'BEGIN'), 113 | ('master', 'SELECT'), 114 | ('slave1', 'INSERT'), 115 | ('master', 'INSERT')]) 116 | 117 | def fail_with_exc(data): 118 | with Using(slave2, [A]): 119 | A.create(data=data) 120 | raise ValueError('xxx') 121 | 122 | self.assertRaises(ValueError, fail_with_exc, 'a2') 123 | self.assertDatabaseVerb([ 124 | ('slave2', 'BEGIN'), 125 | ('slave2', 'INSERT')]) 126 | 127 | with Using(slave1, [A, B]): 128 | a_objs = [a_obj.data for a_obj in A.select()] 129 | self.assertEqual(a_objs, ['a1']) 130 | 131 | 132 | class TestMasterSlave(ModelTestCase): 133 | requires = [User, Thing] 134 | 135 | def setUp(self): 136 | super(TestMasterSlave, self).setUp() 137 | User.create(username='peewee') 138 | Thing.create(name='something') 139 | reset() 140 | 141 | def assertQueries(self, databases): 142 | self.assertEqual([q[0] for q in queries], databases) 143 | 144 | def test_balance_pair(self): 145 | for i in range(6): 146 | User.get() 147 | self.assertQueries([ 148 | 'slave1', 149 | 'slave2', 150 | 'slave1', 151 | 'slave2', 152 | 'slave1', 153 | 'slave2']) 154 | 155 | def test_balance_single(self): 156 | for i in range(3): 157 | Thing.get() 158 | self.assertQueries(['slave2', 'slave2', 'slave2']) 159 | 160 | def test_query_types(self): 161 | u = User.create(username='charlie') 162 | User.select().where(User.username == 'charlie').get() 163 | self.assertQueries(['master', 'slave1']) 164 | 165 | User.get(User.username == 'charlie') 166 | self.assertQueries(['master', 'slave1', 'slave2']) 167 | 168 | u.username = 'edited' 169 | u.save() # Update. 170 | self.assertQueries(['master', 'slave1', 'slave2', 'master']) 171 | 172 | u.delete_instance() 173 | self.assertQueries(['master', 'slave1', 'slave2', 'master', 'master']) 174 | 175 | def test_raw_queries(self): 176 | User.raw('insert into user (username) values (?)', 'charlie').execute() 177 | rq = list(User.raw('select * from user where username = ?', 'charlie')) 178 | self.assertEqual(rq[0].username, 'charlie') 179 | 180 | self.assertQueries(['master', 'slave1']) 181 | -------------------------------------------------------------------------------- /playhouse/tests/test_signals.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | from playhouse import signals 3 | from playhouse.tests.base import database_initializer 4 | from playhouse.tests.base import ModelTestCase 5 | 6 | 7 | db = database_initializer.get_in_memory_database() 8 | 9 | class BaseSignalModel(signals.Model): 10 | class Meta: 11 | database = db 12 | 13 | class ModelA(BaseSignalModel): 14 | a = CharField(default='') 15 | 16 | class ModelB(BaseSignalModel): 17 | b = CharField(default='') 18 | 19 | class SubclassOfModelB(ModelB): 20 | pass 21 | 22 | class SignalsTestCase(ModelTestCase): 23 | requires = [ModelA, ModelB, SubclassOfModelB] 24 | 25 | def tearDown(self): 26 | super(SignalsTestCase, self).tearDown() 27 | signals.pre_save._flush() 28 | signals.post_save._flush() 29 | signals.pre_delete._flush() 30 | signals.post_delete._flush() 31 | signals.pre_init._flush() 32 | signals.post_init._flush() 33 | 34 | def test_pre_save(self): 35 | state = [] 36 | 37 | @signals.pre_save() 38 | def pre_save(sender, instance, created): 39 | state.append((sender, instance, instance._get_pk_value(), created)) 40 | m = ModelA() 41 | res = m.save() 42 | self.assertEqual(state, [(ModelA, m, None, True)]) 43 | self.assertEqual(res, 1) 44 | 45 | res = m.save() 46 | self.assertTrue(m.id is not None) 47 | self.assertEqual(state[-1], (ModelA, m, m.id, False)) 48 | self.assertEqual(res, 1) 49 | 50 | def test_post_save(self): 51 | state = [] 52 | 53 | @signals.post_save() 54 | def post_save(sender, instance, created): 55 | state.append((sender, instance, instance._get_pk_value(), created)) 56 | m = ModelA() 57 | m.save() 58 | 59 | self.assertTrue(m.id is not None) 60 | self.assertEqual(state, [(ModelA, m, m.id, True)]) 61 | 62 | m.save() 63 | self.assertEqual(state[-1], (ModelA, m, m.id, False)) 64 | 65 | def test_pre_delete(self): 66 | state = [] 67 | 68 | m = ModelA() 69 | m.save() 70 | 71 | @signals.pre_delete() 72 | def pre_delete(sender, instance): 73 | state.append((sender, instance, ModelA.select().count())) 74 | res = m.delete_instance() 75 | self.assertEqual(state, [(ModelA, m, 1)]) 76 | self.assertEqual(res, 1) 77 | 78 | def test_post_delete(self): 79 | state = [] 80 | 81 | m = ModelA() 82 | m.save() 83 | 84 | @signals.post_delete() 85 | def post_delete(sender, instance): 86 | state.append((sender, instance, ModelA.select().count())) 87 | m.delete_instance() 88 | self.assertEqual(state, [(ModelA, m, 0)]) 89 | 90 | def test_pre_init(self): 91 | state = [] 92 | 93 | m = ModelA(a='a') 94 | m.save() 95 | 96 | @signals.pre_init() 97 | def pre_init(sender, instance): 98 | state.append((sender, instance.a)) 99 | 100 | ModelA.get() 101 | self.assertEqual(state, [(ModelA, '')]) 102 | 103 | def test_post_init(self): 104 | state = [] 105 | 106 | m = ModelA(a='a') 107 | m.save() 108 | 109 | @signals.post_init() 110 | def post_init(sender, instance): 111 | state.append((sender, instance.a)) 112 | 113 | ModelA.get() 114 | self.assertEqual(state, [(ModelA, 'a')]) 115 | 116 | def test_sender(self): 117 | state = [] 118 | 119 | @signals.post_save(sender=ModelA) 120 | def post_save(sender, instance, created): 121 | state.append(instance) 122 | 123 | m = ModelA.create() 124 | self.assertEqual(state, [m]) 125 | 126 | m2 = ModelB.create() 127 | self.assertEqual(state, [m]) 128 | 129 | def test_connect_disconnect(self): 130 | state = [] 131 | 132 | @signals.post_save(sender=ModelA) 133 | def post_save(sender, instance, created): 134 | state.append(instance) 135 | 136 | m = ModelA.create() 137 | self.assertEqual(state, [m]) 138 | 139 | signals.post_save.disconnect(post_save) 140 | m2 = ModelA.create() 141 | self.assertEqual(state, [m]) 142 | 143 | def test_subclass_instance_receive_signals(self): 144 | state = [] 145 | 146 | @signals.post_save(sender=ModelB) 147 | def post_save(sender, instance, created): 148 | state.append(instance) 149 | 150 | m = SubclassOfModelB.create() 151 | assert m in state 152 | -------------------------------------------------------------------------------- /playhouse/tests/test_speedups.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import unittest 3 | 4 | from peewee import * 5 | from playhouse import _speedups as speedups 6 | from playhouse.tests.base import database_initializer 7 | from playhouse.tests.base import ModelTestCase 8 | 9 | 10 | db = database_initializer.get_in_memory_database(use_speedups=True) 11 | 12 | class BaseModel(Model): 13 | class Meta: 14 | database = db 15 | 16 | class Note(BaseModel): 17 | content = TextField() 18 | timestamp = DateTimeField(default=datetime.datetime.now) 19 | 20 | 21 | class TestResultWrappers(ModelTestCase): 22 | requires = [Note] 23 | 24 | def setUp(self): 25 | super(TestResultWrappers, self).setUp() 26 | for i in range(10): 27 | Note.create(content='note-%s' % i) 28 | 29 | def test_dirty_fields(self): 30 | note = Note.create(content='huey') 31 | self.assertFalse(note.is_dirty()) 32 | self.assertEqual(note.dirty_fields, []) 33 | 34 | ndb = Note.get(Note.content == 'huey') 35 | self.assertFalse(ndb.is_dirty()) 36 | self.assertEqual(ndb.dirty_fields, []) 37 | 38 | ndb.content = 'x' 39 | self.assertTrue(ndb.is_dirty()) 40 | self.assertEqual(ndb.dirty_fields, ['content']) 41 | 42 | def test_gh_regression_1073_func_coerce(self): 43 | func = fn.GROUP_CONCAT(Note.id).alias('note_ids') 44 | query = Note.select(func) 45 | self.assertRaises(ValueError, query.get) 46 | 47 | query = Note.select(func.coerce(False)) 48 | result = query.get().note_ids 49 | self.assertEqual(result, ','.join(str(i) for i in range(1, 11))) 50 | 51 | def test_tuple_results(self): 52 | query = Note.select().order_by(Note.id).tuples() 53 | qr = query.execute() 54 | self.assertTrue(isinstance(qr, speedups._TuplesQueryResultWrapper)) 55 | 56 | results = list(qr) 57 | self.assertEqual(len(results), 10) 58 | first, last = results[0], results[-1] 59 | self.assertEqual(first[:2], (1, 'note-0')) 60 | self.assertEqual(last[:2], (10, 'note-9')) 61 | self.assertTrue(isinstance(first[2], datetime.datetime)) 62 | 63 | def test_dict_results(self): 64 | query = Note.select().order_by(Note.id).dicts() 65 | qr = query.execute() 66 | self.assertTrue(isinstance(qr, speedups._DictQueryResultWrapper)) 67 | 68 | results = list(qr) 69 | self.assertEqual(len(results), 10) 70 | first, last = results[0], results[-1] 71 | self.assertEqual(sorted(first.keys()), ['content', 'id', 'timestamp']) 72 | self.assertEqual(first['id'], 1) 73 | self.assertEqual(first['content'], 'note-0') 74 | self.assertTrue(isinstance(first['timestamp'], datetime.datetime)) 75 | 76 | self.assertEqual(last['id'], 10) 77 | self.assertEqual(last['content'], 'note-9') 78 | 79 | def test_model_results(self): 80 | query = Note.select().order_by(Note.id) 81 | qr = query.execute() 82 | self.assertTrue(isinstance(qr, speedups._ModelQueryResultWrapper)) 83 | 84 | results = list(qr) 85 | self.assertEqual(len(results), 10) 86 | first, last = results[0], results[-1] 87 | 88 | self.assertTrue(isinstance(first, Note)) 89 | self.assertEqual(first.id, 1) 90 | self.assertEqual(first.content, 'note-0') 91 | self.assertTrue(isinstance(first.timestamp, datetime.datetime)) 92 | 93 | self.assertEqual(last.id, 10) 94 | self.assertEqual(last.content, 'note-9') 95 | 96 | def test_aliases(self): 97 | query = (Note 98 | .select( 99 | Note.id, 100 | Note.content.alias('ct'), 101 | Note.timestamp.alias('ts')) 102 | .order_by(Note.id)) 103 | 104 | rows = list(query.tuples()) 105 | self.assertEqual(len(rows), 10) 106 | self.assertEqual(rows[0][:2], (1, 'note-0')) 107 | self.assertTrue(isinstance(rows[0][2], datetime.datetime)) 108 | 109 | rows = list(query.dicts()) 110 | first = rows[0] 111 | self.assertEqual(sorted(first.keys()), ['ct', 'id', 'ts']) 112 | self.assertEqual(first['id'], 1) 113 | self.assertEqual(first['ct'], 'note-0') 114 | self.assertTrue(isinstance(first['ts'], datetime.datetime)) 115 | 116 | rows = list(query) 117 | first = rows[0] 118 | self.assertTrue(isinstance(first, Note)) 119 | self.assertEqual(first.id, 1) 120 | self.assertEqual(first.ct, 'note-0') 121 | self.assertIsNone(first.content) 122 | self.assertTrue(isinstance(first.ts, datetime.datetime)) 123 | 124 | def test_fill_cache(self): 125 | with self.assertQueryCount(1): 126 | query = Note.select().order_by(Note.id) 127 | qr = query.execute() 128 | qr.fill_cache(3) 129 | 130 | self.assertEqual(qr._ct, 3) 131 | self.assertEqual(len(qr._result_cache), 3) 132 | 133 | # No changes to result wrapper. 134 | notes = query[:3] 135 | self.assertEqual([n.id for n in notes], [1, 2, 3]) 136 | self.assertEqual(qr._ct, 4) 137 | self.assertEqual(len(qr._result_cache), 4) 138 | self.assertFalse(qr._populated) 139 | 140 | qr.fill_cache(5) 141 | notes = query[:5] 142 | self.assertEqual([n.id for n in notes], [1, 2, 3, 4, 5]) 143 | self.assertEqual(qr._ct, 6) 144 | self.assertEqual(len(qr._result_cache), 6) 145 | 146 | notes = query[:7] 147 | self.assertEqual([n.id for n in notes], [1, 2, 3, 4, 5, 6, 7]) 148 | self.assertEqual(qr._ct, 8) 149 | self.assertFalse(qr._populated) 150 | 151 | qr.fill_cache() 152 | self.assertEqual( 153 | [n.id for n in query], 154 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 155 | self.assertEqual(qr._ct, 10) 156 | self.assertTrue(qr._populated) 157 | -------------------------------------------------------------------------------- /playhouse/tests/test_sqlcipher_ext.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from hashlib import sha1 3 | 4 | from peewee import DatabaseError 5 | from playhouse.sqlcipher_ext import * 6 | from playhouse.sqlite_ext import * 7 | from playhouse.tests.base import database_initializer 8 | from playhouse.tests.base import ModelTestCase 9 | 10 | 11 | db = database_initializer.get_database('sqlcipher') 12 | ext_db = database_initializer.get_database( 13 | 'sqlcipher_ext', 14 | passphrase='testing sqlcipher') 15 | 16 | 17 | class BaseModel(Model): 18 | class Meta: 19 | database = db 20 | 21 | class Thing(BaseModel): 22 | name = CharField() 23 | 24 | @ext_db.func('shazam') 25 | def shazam(s): 26 | return sha1(s or '').hexdigest()[:5] 27 | 28 | class ExtModel(Model): 29 | class Meta: 30 | database = ext_db 31 | 32 | class FTSNote(FTSModel): 33 | content = TextField() 34 | 35 | class Meta: 36 | database = ext_db 37 | 38 | class Note(ExtModel): 39 | content = TextField() 40 | timestamp = DateTimeField(default=datetime.datetime.now) 41 | 42 | 43 | class SqlCipherTestCase(ModelTestCase): 44 | requires = [Thing] 45 | 46 | def test_good_and_bad_passphrases(self): 47 | things = ('t1', 't2', 't3') 48 | for thing in things: 49 | Thing.create(name=thing) 50 | 51 | # Try to open db with wrong passphrase 52 | secure = False 53 | bad_db = database_initializer.get_database( 54 | 'sqlcipher', 55 | passphrase='wrong passphrase') 56 | 57 | self.assertRaises(DatabaseError, bad_db.get_tables) 58 | 59 | # Assert that we can still access the data with the good passphrase. 60 | query = Thing.select().order_by(Thing.name) 61 | self.assertEqual([t.name for t in query], ['t1', 't2', 't3']) 62 | 63 | def test_passphrase_length(self): 64 | db = database_initializer.get_database('sqlcipher', passphrase='x') 65 | self.assertRaises(ImproperlyConfigured, db.connect) 66 | 67 | def test_kdf_iter(self): 68 | db = database_initializer.get_database('sqlcipher', kdf_iter=9999) 69 | self.assertRaises(ImproperlyConfigured, db.connect) 70 | 71 | 72 | class SqlCipherExtTestCase(ModelTestCase): 73 | requires = [Note] 74 | 75 | def setUp(self): 76 | super(SqlCipherExtTestCase, self).setUp() 77 | FTSNote.drop_table(True) 78 | FTSNote.create_table(tokenize='porter', content=Note.content) 79 | 80 | def tearDown(self): 81 | super(SqlCipherExtTestCase, self).tearDown() 82 | FTSNote.drop_table(True) 83 | 84 | def test_fts(self): 85 | strings = [ 86 | 'python and peewee for working with databases', 87 | 'relational databases are the best', 88 | 'sqlite is the best relational database', 89 | 'sqlcipher is a cool database extension'] 90 | for s in strings: 91 | Note.create(content=s) 92 | FTSNote.rebuild() 93 | 94 | query = (FTSNote 95 | .select(FTSNote, FTSNote.rank().alias('score')) 96 | .where(FTSNote.match('relational databases')) 97 | .order_by(SQL('score').desc())) 98 | notes = [note.content for note in query] 99 | self.assertEqual(notes, [ 100 | 'relational databases are the best', 101 | 'sqlite is the best relational database']) 102 | 103 | alt_conn = SqliteDatabase(ext_db.database) 104 | self.assertRaises( 105 | DatabaseError, 106 | alt_conn.execute_sql, 107 | 'SELECT * FROM "%s"' % (FTSNote._meta.db_table)) 108 | 109 | def test_func(self): 110 | Note.create(content='hello') 111 | Note.create(content='baz') 112 | Note.create(content='nug') 113 | 114 | query = (Note 115 | .select(Note.content, fn.shazam(Note.content).alias('shz')) 116 | .order_by(Note.id) 117 | .dicts()) 118 | results = list(query) 119 | self.assertEqual(results, [ 120 | {'content': 'hello', 'shz': 'aaf4c'}, 121 | {'content': 'baz', 'shz': 'bbe96'}, 122 | {'content': 'nug', 'shz': '52616'}, 123 | ]) 124 | -------------------------------------------------------------------------------- /playhouse/tests/test_sqliteq.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | import os 3 | import sys 4 | import threading 5 | import time 6 | import unittest 7 | 8 | try: 9 | import gevent 10 | from gevent.event import Event as GreenEvent 11 | except ImportError: 12 | gevent = None 13 | 14 | from peewee import * 15 | from playhouse.sqliteq import ResultTimeout 16 | from playhouse.sqliteq import SqliteQueueDatabase 17 | from playhouse.sqliteq import WriterPaused 18 | from playhouse.tests.base import database_initializer 19 | from playhouse.tests.base import PeeweeTestCase 20 | from playhouse.tests.base import skip_if 21 | 22 | 23 | get_db = partial( 24 | database_initializer.get_database, 25 | 'sqlite', 26 | db_class=SqliteQueueDatabase) 27 | 28 | db = database_initializer.get_database('sqlite') 29 | 30 | class User(Model): 31 | name = TextField(unique=True) 32 | class Meta: 33 | database = db 34 | db_table = 'threaded_db_test_user' 35 | 36 | 37 | class BaseTestQueueDatabase(object): 38 | database_config = {} 39 | n_rows = 50 40 | n_threads = 20 41 | 42 | def setUp(self): 43 | super(BaseTestQueueDatabase, self).setUp() 44 | with db.execution_context(): 45 | User.create_table(True) 46 | User._meta.database = \ 47 | self.db = get_db(**self.database_config) 48 | 49 | # Sanity check at startup. 50 | self.assertEqual(self.db.queue_size(), 0) 51 | 52 | def tearDown(self): 53 | super(BaseTestQueueDatabase, self).tearDown() 54 | User._meta.database = db 55 | with db.execution_context(): 56 | User.drop_table() 57 | if not self.db.is_closed(): 58 | self.db.close() 59 | if not db.is_closed(): 60 | db.close() 61 | filename = db.database 62 | if os.path.exists(filename): 63 | os.unlink(filename) 64 | 65 | def test_query_execution(self): 66 | qr = User.select().execute() 67 | self.assertEqual(self.db.queue_size(), 0) 68 | 69 | self.db.start() 70 | 71 | users = list(qr) 72 | huey = User.create(name='huey') 73 | mickey = User.create(name='mickey') 74 | 75 | self.assertTrue(huey.id is not None) 76 | self.assertTrue(mickey.id is not None) 77 | self.assertEqual(self.db.queue_size(), 0) 78 | 79 | self.db.stop() 80 | 81 | def create_thread(self, fn, *args): 82 | raise NotImplementedError 83 | 84 | def create_event(self): 85 | raise NotImplementedError 86 | 87 | def test_multiple_threads(self): 88 | def create_rows(idx, nrows): 89 | for i in range(idx, idx + nrows): 90 | User.create(name='u-%s' % i) 91 | 92 | total = self.n_threads * self.n_rows 93 | self.db.start() 94 | threads = [self.create_thread(create_rows, i, self.n_rows) 95 | for i in range(0, total, self.n_rows)] 96 | [t.start() for t in threads] 97 | [t.join() for t in threads] 98 | 99 | self.assertEqual(User.select().count(), total) 100 | self.db.stop() 101 | 102 | def test_pause(self): 103 | event_a = self.create_event() 104 | event_b = self.create_event() 105 | 106 | def create_user(name, event, expect_paused): 107 | event.wait() 108 | if expect_paused: 109 | self.assertRaises(WriterPaused, lambda: User.create(name=name)) 110 | else: 111 | User.create(name=name) 112 | 113 | self.db.start() 114 | 115 | t_a = self.create_thread(create_user, 'a', event_a, True) 116 | t_a.start() 117 | t_b = self.create_thread(create_user, 'b', event_b, False) 118 | t_b.start() 119 | 120 | User.create(name='c') 121 | self.assertEqual(User.select().count(), 1) 122 | 123 | # Pause operations but preserve the writer thread/connection. 124 | self.db.pause() 125 | 126 | event_a.set() 127 | self.assertEqual(User.select().count(), 1) 128 | t_a.join() 129 | 130 | self.db.unpause() 131 | self.assertEqual(User.select().count(), 1) 132 | 133 | event_b.set() 134 | t_b.join() 135 | self.assertEqual(User.select().count(), 2) 136 | 137 | self.db.stop() 138 | 139 | def test_restart(self): 140 | self.db.start() 141 | User.create(name='a') 142 | self.db.stop() 143 | self.db._results_timeout = 0.0001 144 | 145 | self.assertRaises(ResultTimeout, User.create, name='b') 146 | self.assertEqual(User.select().count(), 1) 147 | 148 | self.db.start() # Will execute the pending "b" INSERT. 149 | self.db._results_timeout = None 150 | 151 | User.create(name='c') 152 | self.assertEqual(User.select().count(), 3) 153 | self.assertEqual(sorted(u.name for u in User.select()), 154 | ['a', 'b', 'c']) 155 | 156 | def test_waiting(self): 157 | D = {} 158 | 159 | def create_user(name): 160 | D[name] = User.create(name=name).id 161 | 162 | threads = [self.create_thread(create_user, name) 163 | for name in ('huey', 'charlie', 'zaizee')] 164 | [t.start() for t in threads] 165 | 166 | def get_users(): 167 | D['users'] = [(user.id, user.name) for user in User.select()] 168 | 169 | tg = self.create_thread(get_users) 170 | tg.start() 171 | threads.append(tg) 172 | 173 | self.db.start() 174 | [t.join() for t in threads] 175 | self.db.stop() 176 | 177 | self.assertEqual(sorted(D), ['charlie', 'huey', 'users', 'zaizee']) 178 | 179 | def test_next_method(self): 180 | self.db.start() 181 | 182 | User.create(name='mickey') 183 | User.create(name='huey') 184 | query = iter(User.select().order_by(User.name)) 185 | self.assertEqual(next(query).name, 'huey') 186 | self.assertEqual(next(query).name, 'mickey') 187 | self.assertRaises(StopIteration, lambda: next(query)) 188 | 189 | self.assertEqual( 190 | next(self.db.execute_sql('PRAGMA journal_mode'))[0], 191 | 'wal') 192 | 193 | self.db.stop() 194 | 195 | 196 | class TestThreadedDatabaseThreads(BaseTestQueueDatabase, PeeweeTestCase): 197 | database_config = {'use_gevent': False} 198 | 199 | def tearDown(self): 200 | self.db._results_timeout = None 201 | super(TestThreadedDatabaseThreads, self).tearDown() 202 | 203 | def create_thread(self, fn, *args): 204 | t = threading.Thread(target=fn, args=args) 205 | t.daemon = True 206 | return t 207 | 208 | def create_event(self): 209 | return threading.Event() 210 | 211 | def test_timeout(self): 212 | @self.db.func() 213 | def slow(n): 214 | time.sleep(n) 215 | return 'I slept for %s seconds' % n 216 | 217 | self.db.start() 218 | 219 | # Make the result timeout very small, then call our function which 220 | # will cause the query results to time-out. 221 | self.db._results_timeout = 0.001 222 | self.assertRaises( 223 | ResultTimeout, 224 | lambda: self.db.execute_sql('select slow(?)', (0.005,)).fetchone()) 225 | self.db.stop() 226 | 227 | 228 | @skip_if(lambda: gevent is None) 229 | class TestThreadedDatabaseGreenlets(BaseTestQueueDatabase, PeeweeTestCase): 230 | database_config = {'use_gevent': True} 231 | n_rows = 20 232 | n_threads = 200 233 | 234 | def create_thread(self, fn, *args): 235 | return gevent.Greenlet(fn, *args) 236 | 237 | def create_event(self): 238 | return GreenEvent() 239 | 240 | 241 | if __name__ == '__main__': 242 | unittest.main(argv=sys.argv) 243 | -------------------------------------------------------------------------------- /pwiz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import datetime 4 | import sys 5 | from getpass import getpass 6 | from optparse import OptionParser 7 | 8 | from peewee import * 9 | from peewee import print_ 10 | from peewee import __version__ as peewee_version 11 | from playhouse.reflection import * 12 | 13 | TEMPLATE = """from peewee import *%s 14 | 15 | database = %s('%s', **%s) 16 | 17 | class UnknownField(object): 18 | def __init__(self, *_, **__): pass 19 | 20 | class BaseModel(Model): 21 | class Meta: 22 | database = database 23 | """ 24 | 25 | DATABASE_ALIASES = { 26 | MySQLDatabase: ['mysql', 'mysqldb'], 27 | PostgresqlDatabase: ['postgres', 'postgresql'], 28 | SqliteDatabase: ['sqlite', 'sqlite3'], 29 | } 30 | 31 | DATABASE_MAP = dict((value, key) 32 | for key in DATABASE_ALIASES 33 | for value in DATABASE_ALIASES[key]) 34 | 35 | def make_introspector(database_type, database_name, **kwargs): 36 | if database_type not in DATABASE_MAP: 37 | err('Unrecognized database, must be one of: %s' % 38 | ', '.join(DATABASE_MAP.keys())) 39 | sys.exit(1) 40 | 41 | schema = kwargs.pop('schema', None) 42 | DatabaseClass = DATABASE_MAP[database_type] 43 | db = DatabaseClass(database_name, **kwargs) 44 | return Introspector.from_database(db, schema=schema) 45 | 46 | def print_models(introspector, tables=None, preserve_order=False): 47 | database = introspector.introspect(table_names=tables) 48 | 49 | print_(TEMPLATE % ( 50 | introspector.get_additional_imports(), 51 | introspector.get_database_class().__name__, 52 | introspector.get_database_name(), 53 | repr(introspector.get_database_kwargs()))) 54 | 55 | def _print_table(table, seen, accum=None): 56 | accum = accum or [] 57 | foreign_keys = database.foreign_keys[table] 58 | for foreign_key in foreign_keys: 59 | dest = foreign_key.dest_table 60 | 61 | # In the event the destination table has already been pushed 62 | # for printing, then we have a reference cycle. 63 | if dest in accum and table not in accum: 64 | print_('# Possible reference cycle: %s' % dest) 65 | 66 | # If this is not a self-referential foreign key, and we have 67 | # not already processed the destination table, do so now. 68 | if dest not in seen and dest not in accum: 69 | seen.add(dest) 70 | if dest != table: 71 | _print_table(dest, seen, accum + [table]) 72 | 73 | print_('class %s(BaseModel):' % database.model_names[table]) 74 | columns = database.columns[table].items() 75 | if not preserve_order: 76 | columns = sorted(columns) 77 | primary_keys = database.primary_keys[table] 78 | for name, column in columns: 79 | skip = all([ 80 | name in primary_keys, 81 | name == 'id', 82 | len(primary_keys) == 1, 83 | column.field_class in introspector.pk_classes]) 84 | if skip: 85 | continue 86 | if column.primary_key and len(primary_keys) > 1: 87 | # If we have a CompositeKey, then we do not want to explicitly 88 | # mark the columns as being primary keys. 89 | column.primary_key = False 90 | 91 | print_(' %s' % column.get_field()) 92 | 93 | print_('') 94 | print_(' class Meta:') 95 | print_(' db_table = \'%s\'' % table) 96 | multi_column_indexes = database.multi_column_indexes(table) 97 | if multi_column_indexes: 98 | print_(' indexes = (') 99 | for fields, unique in sorted(multi_column_indexes): 100 | print_(' ((%s), %s),' % ( 101 | ', '.join("'%s'" % field for field in fields), 102 | unique, 103 | )) 104 | print_(' )') 105 | 106 | if introspector.schema: 107 | print_(' schema = \'%s\'' % introspector.schema) 108 | if len(primary_keys) > 1: 109 | pk_field_names = sorted([ 110 | field.name for col, field in columns 111 | if col in primary_keys]) 112 | pk_list = ', '.join("'%s'" % pk for pk in pk_field_names) 113 | print_(' primary_key = CompositeKey(%s)' % pk_list) 114 | print_('') 115 | 116 | seen.add(table) 117 | 118 | seen = set() 119 | for table in sorted(database.model_names.keys()): 120 | if table not in seen: 121 | if not tables or table in tables: 122 | _print_table(table, seen) 123 | 124 | def print_header(cmd_line, introspector): 125 | timestamp = datetime.datetime.now() 126 | print_('# Code generated by:') 127 | print_('# python -m pwiz %s' % cmd_line) 128 | print_('# Date: %s' % timestamp.strftime('%B %d, %Y %I:%M%p')) 129 | print_('# Database: %s' % introspector.get_database_name()) 130 | print_('# Peewee version: %s' % peewee_version) 131 | print_('') 132 | 133 | 134 | def err(msg): 135 | sys.stderr.write('\033[91m%s\033[0m\n' % msg) 136 | sys.stderr.flush() 137 | 138 | def get_option_parser(): 139 | parser = OptionParser(usage='usage: %prog [options] database_name') 140 | ao = parser.add_option 141 | ao('-H', '--host', dest='host') 142 | ao('-p', '--port', dest='port', type='int') 143 | ao('-u', '--user', dest='user') 144 | ao('-P', '--password', dest='password', action='store_true') 145 | engines = sorted(DATABASE_MAP) 146 | ao('-e', '--engine', dest='engine', default='postgresql', choices=engines, 147 | help=('Database type, e.g. sqlite, mysql or postgresql. Default ' 148 | 'is "postgresql".')) 149 | ao('-s', '--schema', dest='schema') 150 | ao('-t', '--tables', dest='tables', 151 | help=('Only generate the specified tables. Multiple table names should ' 152 | 'be separated by commas.')) 153 | ao('-i', '--info', dest='info', action='store_true', 154 | help=('Add database information and other metadata to top of the ' 155 | 'generated file.')) 156 | ao('-o', '--preserve-order', action='store_true', dest='preserve_order', 157 | help='Model definition column ordering matches source table.') 158 | return parser 159 | 160 | def get_connect_kwargs(options): 161 | ops = ('host', 'port', 'user', 'schema') 162 | kwargs = dict((o, getattr(options, o)) for o in ops if getattr(options, o)) 163 | if options.password: 164 | kwargs['password'] = getpass() 165 | return kwargs 166 | 167 | 168 | if __name__ == '__main__': 169 | raw_argv = sys.argv 170 | 171 | parser = get_option_parser() 172 | options, args = parser.parse_args() 173 | 174 | if options.preserve_order: 175 | try: 176 | from collections import OrderedDict 177 | except ImportError: 178 | err('Preserve order requires Python >= 2.7.') 179 | sys.exit(1) 180 | 181 | if len(args) < 1: 182 | err('Missing required parameter "database"') 183 | parser.print_help() 184 | sys.exit(1) 185 | 186 | connect = get_connect_kwargs(options) 187 | database = args[-1] 188 | 189 | tables = None 190 | if options.tables: 191 | tables = [table.strip() for table in options.tables.split(',') 192 | if table.strip()] 193 | 194 | introspector = make_introspector(options.engine, database, **connect) 195 | if options.info: 196 | cmd_line = ' '.join(raw_argv[1:]) 197 | print_header(cmd_line, introspector) 198 | 199 | print_models(introspector, tables, preserve_order=options.preserve_order) 200 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asyncpg 2 | pytest -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import sys 4 | import warnings 5 | from distutils.core import setup 6 | from distutils.extension import Extension 7 | from distutils.version import StrictVersion 8 | 9 | f = open(os.path.join(os.path.dirname(__file__), 'README.rst')) 10 | readme = f.read() 11 | f.close() 12 | 13 | setup_kwargs = {} 14 | cython_min_version = '0.22.1' 15 | 16 | try: 17 | from Cython.Distutils import build_ext 18 | from Cython import __version__ as cython_version 19 | except ImportError: 20 | cython_installed = False 21 | warnings.warn('Cython C extensions for peewee will NOT be built, because ' 22 | 'Cython does not seem to be installed. To enable Cython C ' 23 | 'extensions, install Cython >=' + cython_min_version + '.') 24 | else: 25 | if platform.python_implementation() != 'CPython': 26 | cython_installed = False 27 | warnings.warn('Cython C extensions disabled as you are not using ' 28 | 'CPython.') 29 | elif StrictVersion(cython_version) < StrictVersion(cython_min_version): 30 | cython_installed = False 31 | warnings.warn('Cython C extensions for peewee will NOT be built, ' 32 | 'because the installed Cython version ' 33 | '(' + cython_version + ') is too old. To enable Cython ' 34 | 'C extensions, install Cython >=' + cython_min_version + 35 | '.') 36 | else: 37 | cython_installed = True 38 | 39 | speedups_ext_module = Extension( 40 | 'playhouse._speedups', 41 | ['playhouse/_speedups.pyx']) 42 | sqlite_udf_module = Extension( 43 | 'playhouse._sqlite_udf', 44 | ['playhouse/_sqlite_udf.pyx']) 45 | sqlite_ext_module = Extension( 46 | 'playhouse._sqlite_ext', 47 | ['playhouse/_sqlite_ext.pyx']) 48 | 49 | 50 | ext_modules = [] 51 | if cython_installed: 52 | ext_modules.extend([ 53 | speedups_ext_module, 54 | sqlite_udf_module, 55 | sqlite_ext_module]) 56 | 57 | if ext_modules: 58 | setup_kwargs.update( 59 | cmdclass={'build_ext': build_ext}, 60 | ext_modules=ext_modules) 61 | 62 | setup( 63 | name='peewee', 64 | version=__import__('peewee').__version__, 65 | description='a little orm', 66 | long_description=readme, 67 | author='Charles Leifer', 68 | author_email='coleifer@gmail.com', 69 | url='http://github.com/coleifer/peewee/', 70 | packages=['playhouse'], 71 | py_modules=['peewee', 'pwiz'], 72 | classifiers=[ 73 | 'Development Status :: 5 - Production/Stable', 74 | 'Intended Audience :: Developers', 75 | 'License :: OSI Approved :: MIT License', 76 | 'Operating System :: OS Independent', 77 | 'Programming Language :: Python', 78 | 'Programming Language :: Python :: 3', 79 | ], 80 | scripts = ['pwiz.py', 'playhouse/pskel'], 81 | **setup_kwargs 82 | ) 83 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Aggregate all the test modules and run from the command-line. For information 4 | about running tests, see the README located in the `playhouse/tests` directory. 5 | """ 6 | import sys 7 | import unittest 8 | 9 | from playhouse.tests.test_apis import * 10 | from playhouse.tests.test_compound_queries import * 11 | from playhouse.tests.test_database import * 12 | from playhouse.tests.test_fields import * 13 | from playhouse.tests.test_helpers import * 14 | from playhouse.tests.test_introspection import * 15 | from playhouse.tests.test_keys import * 16 | from playhouse.tests.test_models import * 17 | from playhouse.tests.test_queries import * 18 | from playhouse.tests.test_query_results import * 19 | from playhouse.tests.test_transactions import * 20 | 21 | 22 | if __name__ == '__main__': 23 | from peewee import print_ 24 | print_("""\033[1;31m 25 | ______ ______ ______ __ __ ______ ______ 26 | /\ == \ /\ ___\ /\ ___\ /\ \ _ \ \ /\ ___\ /\ ___\\ 27 | \ \ _-/ \ \ __\ \ \ __\ \ \ \/ ".\ \ \ \ __\ \ \ __\\ 28 | \ \_\ \ \_____\ \ \_____\ \ \__/".~\_\ \ \_____\ \ \_____\\ 29 | \/_/ \/_____/ \/_____/ \/_/ \/_/ \/_____/ \/_____/ 30 | \033[0m""") 31 | unittest.main(argv=sys.argv) 32 | -------------------------------------------------------------------------------- /tests/test_async.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('/code') 3 | 4 | import logging 5 | 6 | logging.basicConfig( 7 | level=logging.DEBUG, format="%(asctime)s: %(levelname)s: %(message)s") 8 | log = logging.getLogger(__name__) 9 | 10 | import asyncio 11 | 12 | from peewee import * 13 | import datetime 14 | 15 | db = PostgresqlDatabase('testing', host='database', user='admin', password='testpw') 16 | 17 | class BaseModel(Model): 18 | class Meta: 19 | database = db 20 | 21 | class User(BaseModel): 22 | username = CharField(unique=True) 23 | 24 | class Tweet(BaseModel): 25 | user = ForeignKeyField(User, related_name='tweets') 26 | message = TextField() 27 | created_date = DateTimeField(default=datetime.datetime.now) 28 | is_published = BooleanField(default=True) 29 | 30 | async def main_create(): 31 | print("Start") 32 | await db.connect() 33 | await db.create_tables([User, Tweet]) 34 | print("Done") 35 | 36 | async def main_users_insert(): 37 | print("Start") 38 | charlie = await User.create(username='charlizezz') 39 | print("charlie") 40 | print(charlie) 41 | print(charlie.id) 42 | huey = User(username='hueyzzz') 43 | await huey.save() 44 | print("huey") 45 | print(huey) 46 | print(huey.id) 47 | print("Done") 48 | 49 | async def main_users_get(): 50 | print("Start") 51 | charlie = await User.get(User.username == 'charlie') 52 | print("charlie") 53 | print(charlie) 54 | print(charlie.id) 55 | print("Done") 56 | 57 | loop = asyncio.get_event_loop() 58 | #loop.run_until_complete(main_create()) 59 | #loop.run_until_complete(main_users_insert()) 60 | loop.run_until_complete(main_users_get()) --------------------------------------------------------------------------------