├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── conf.py ├── index.rst ├── make.bat ├── make_readme.py └── pages │ ├── examples │ ├── create_table │ │ ├── create_table_from_csv.txt │ │ ├── create_table_from_data_matrix.txt │ │ ├── create_table_from_df.txt │ │ ├── create_table_from_excel.txt │ │ ├── create_table_from_gs.txt │ │ ├── create_table_from_json.txt │ │ └── index.rst │ ├── get_profile.rst │ ├── in_memory_database.rst │ ├── index.rst │ ├── insert_record.rst │ ├── insert_record_example.txt │ ├── orm │ │ ├── index.rst │ │ └── orm_model.txt │ ├── select_as │ │ ├── index.rst │ │ ├── select_as_dataframe.txt │ │ └── select_as_dict.txt │ └── update.rst │ ├── genindex.rst │ ├── introduction │ ├── badges.txt │ ├── feature.txt │ ├── index.rst │ ├── installation.rst │ └── summary.txt │ ├── links.rst │ ├── reference │ ├── error.rst │ ├── function.rst │ ├── index.rst │ ├── query.rst │ └── simplesqlite.rst │ └── sponsors.rst ├── pyproject.toml ├── requirements ├── docs_requirements.txt ├── requirements.txt └── test_requirements.txt ├── sample ├── create_table │ ├── sample_create_table_from_csv.py │ ├── sample_create_table_from_data_matrix.py │ ├── sample_create_table_from_dataframe.py │ ├── sample_create_table_from_excel.py │ ├── sample_create_table_from_gs.py │ ├── sample_create_table_from_json_multi.py │ ├── sample_create_table_from_json_single.py │ └── sample_create_table_in_mem_db.py ├── orm │ ├── orm.py │ └── simple_orm.py ├── sample_check_connection.py ├── sample_get_attribute_names.py ├── sample_get_profile.py ├── sample_get_sqlite_master.py ├── sample_get_table_names.py ├── sample_has_attribute.py ├── sample_has_attrs.py ├── sample_has_table.py ├── sample_insert_dict.py ├── sample_insert_mix_data.py ├── sample_update.py ├── sample_verify_attribute_existence.py ├── sample_verify_table_existence.py └── select │ ├── select_as_dataframe.py │ └── select_as_dict.py ├── setup.py ├── simplesqlite ├── __init__.py ├── __version__.py ├── _column.py ├── _common.py ├── _func.py ├── _logger │ ├── __init__.py │ ├── _logger.py │ └── _null_logger.py ├── _sanitizer.py ├── _validator.py ├── converter.py ├── core.py ├── error.py ├── model.py ├── py.typed ├── query.py └── sqlquery.py ├── test ├── __init__.py ├── _common.py ├── data │ └── python - Wiktionary.html ├── fixture.py ├── test_convertor.py ├── test_error.py ├── test_from_file.py ├── test_func.py ├── test_logger.py ├── test_orm.py ├── test_pandas.py ├── test_query.py ├── test_sanitizer.py ├── test_simplesqlite.py ├── test_sqlquery.py └── test_validator.py └── tox.ini /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: thombashi 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug 3 | title: "[Bug]: " 4 | labels: ["needs-triage"] 5 | body: 6 | - type: input 7 | id: title 8 | attributes: 9 | label: Summary 10 | description: "A brief summary of the bug" 11 | placeholder: "e.g., xxx method raises an exception when yyy" 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: description 17 | attributes: 18 | label: Description 19 | description: "Detailed description of the bug" 20 | placeholder: "A clear and concise description of what the bug is in detail..." 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: expected-behavior 26 | attributes: 27 | label: Expected Behavior 28 | description: "What you expected to happen" 29 | placeholder: "A clear and concise description of what you expected to happen." 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | id: context 35 | attributes: 36 | label: Context 37 | description: "How has this issue affected you? What are you trying to accomplish?" 38 | placeholder: "Providing context helps us come up with a solution that is most useful in the real world" 39 | validations: 40 | required: false 41 | 42 | - type: textarea 43 | id: possible-solution 44 | attributes: 45 | label: Possible Solution 46 | description: "Optional: suggest a fix/reason for the bug or ideas on how to implement the addition or change" 47 | validations: 48 | required: false 49 | 50 | - type: textarea 51 | id: code-to-reproduce 52 | attributes: 53 | label: Code to reproduce 54 | description: "How to reproduce the bug with code" 55 | placeholder: Paste the code that reproduces the bug 56 | render: python 57 | validations: 58 | required: true 59 | 60 | - type: textarea 61 | id: environment 62 | attributes: 63 | label: Your Environment 64 | description: "Include as many relevant details about the environment you experienced the bug in" 65 | placeholder: | 66 | Please execute the following commands and past the output here: 67 | 68 | pip install envinfopy[markdown] 69 | python -m envinfopy --format=markdown SimpleSQLite sqliteschema 70 | validations: 71 | required: true 72 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: GitHub Discussions 4 | url: https://github.com/thombashi/SimpleSQLite/discussions 5 | about: Please ask and answer questions here. 6 | - name: Documentation 7 | url: https://simplesqlite.rtfd.io/ 8 | about: Please check the documentation before creating an issue. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | title: "[Feature Request]: " 4 | labels: ["needs-triage"] 5 | body: 6 | - type: textarea 7 | id: feature-description 8 | attributes: 9 | label: Feature Description 10 | description: "Is your feature request related to a problem? Please describe." 11 | placeholder: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]" 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: solution-description 17 | attributes: 18 | label: Solution Description 19 | description: "Describe the solution you'd like" 20 | placeholder: "A clear and concise description of what you want to happen." 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: alternatives 26 | attributes: 27 | label: Alternatives 28 | description: "Describe alternatives you've considered" 29 | placeholder: "A clear and concise description of any alternative solutions or features you've considered." 30 | validations: 31 | required: false 32 | 33 | - type: textarea 34 | id: additional-context 35 | attributes: 36 | label: Additional Context 37 | description: "Add any other context or screenshots about the feature request here" 38 | placeholder: | 39 | How has this issue affected you? What are you trying to accomplish? 40 | Providing context helps us come up with a solution that is most useful in the real world 41 | validations: 42 | required: false 43 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | groups: 8 | actions-dependencies: 9 | patterns: ["*"] 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - "sample/**" 9 | - ".gitignore" 10 | - "README.rst" 11 | pull_request: 12 | paths-ignore: 13 | - "sample/**" 14 | - ".gitignore" 15 | - "README.rst" 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | build-package: 22 | runs-on: ubuntu-latest 23 | concurrency: 24 | group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-build 25 | cancel-in-progress: true 26 | timeout-minutes: 20 27 | container: 28 | image: ghcr.io/thombashi/python-ci:3.11 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - run: make build 34 | 35 | build-docs: 36 | runs-on: ubuntu-latest 37 | concurrency: 38 | group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-docs 39 | cancel-in-progress: true 40 | timeout-minutes: 20 41 | container: 42 | image: ghcr.io/thombashi/python-ci:3.11 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | 47 | - run: make docs 48 | 49 | lint: 50 | runs-on: ubuntu-latest 51 | concurrency: 52 | group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-lint 53 | cancel-in-progress: true 54 | timeout-minutes: 20 55 | container: 56 | image: ghcr.io/thombashi/python-ci:3.13 57 | 58 | steps: 59 | - uses: actions/checkout@v4 60 | 61 | - run: make check 62 | 63 | unit-test: 64 | runs-on: ${{ matrix.os }} 65 | concurrency: 66 | group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-ut-${{ matrix.os }}-${{ matrix.python-version }} 67 | cancel-in-progress: true 68 | strategy: 69 | fail-fast: false 70 | matrix: 71 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.10"] 72 | os: [ubuntu-latest, macos-latest, windows-latest] 73 | timeout-minutes: 30 74 | 75 | steps: 76 | - uses: actions/checkout@v4 77 | 78 | - name: Setup Python ${{ matrix.python-version }} 79 | uses: actions/setup-python@v5 80 | with: 81 | python-version: ${{ matrix.python-version }} 82 | cache: pip 83 | cache-dependency-path: | 84 | setup.py 85 | **/*requirements.txt 86 | tox.ini 87 | 88 | - run: make setup-ci 89 | 90 | - name: Run tests 91 | run: tox -e cov 92 | 93 | - name: Install coveralls 94 | run: python -m pip install --upgrade --disable-pip-version-check coveralls tomli 95 | 96 | - name: Upload coverage data to coveralls.io 97 | env: 98 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 99 | COVERALLS_FLAG_NAME: ${{ matrix.os }}-${{ matrix.python-version }} 100 | COVERALLS_PARALLEL: true 101 | run: coveralls 102 | 103 | coveralls: 104 | name: Indicate completion to coveralls.io 105 | needs: unit-test 106 | runs-on: ubuntu-latest 107 | container: python:3-slim 108 | 109 | steps: 110 | - run: python -m pip install --upgrade coveralls 111 | 112 | - name: Finished 113 | env: 114 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 115 | run: coveralls --finish 116 | 117 | run-sample: 118 | runs-on: ubuntu-latest 119 | concurrency: 120 | group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-run-sample 121 | cancel-in-progress: true 122 | timeout-minutes: 20 123 | 124 | steps: 125 | - uses: actions/checkout@v4 126 | 127 | - name: Setup Python 128 | uses: actions/setup-python@v5 129 | with: 130 | python-version: "3.13" 131 | cache: pip 132 | cache-dependency-path: | 133 | setup.py 134 | **/*requirements.txt 135 | tox.ini 136 | 137 | - name: Install packages 138 | run: | 139 | make setup-dev 140 | python -m pip install --upgrade --disable-pip-version-check pandas pytablereader[excel] XlsxWriter 141 | 142 | - run: make run-sample 143 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish the new version to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build-package: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 20 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | fetch-tags: true 21 | 22 | - uses: actions/setup-python@v5 23 | with: 24 | python-version: "3.13" 25 | cache: pip 26 | cache-dependency-path: | 27 | setup.py 28 | **/*requirements.txt 29 | tox.ini 30 | 31 | - run: make setup-ci 32 | 33 | - run: make build 34 | 35 | - uses: actions/upload-artifact@v4 36 | with: 37 | name: dist 38 | path: ./dist/* 39 | 40 | publish-package: 41 | needs: build-package 42 | runs-on: ubuntu-latest 43 | timeout-minutes: 10 44 | environment: 45 | name: pypi 46 | url: https://pypi.org/p/simplesqlite 47 | permissions: 48 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 49 | 50 | steps: 51 | - uses: actions/download-artifact@v4 52 | with: 53 | name: dist 54 | path: ./dist 55 | 56 | - name: Publish to PyPI 57 | uses: pypa/gh-action-pypi-publish@release/v1 58 | 59 | generate-relese: 60 | needs: publish-package 61 | runs-on: ubuntu-latest 62 | timeout-minutes: 10 63 | permissions: 64 | id-token: write 65 | contents: write 66 | 67 | steps: 68 | - uses: actions/download-artifact@v4 69 | with: 70 | name: dist 71 | path: ./dist 72 | 73 | - name: Sign the dists with Sigstore 74 | uses: sigstore/gh-action-sigstore-python@v3.0.0 75 | with: 76 | inputs: >- 77 | ./dist/*.tar.gz 78 | ./dist/*.whl 79 | 80 | - name: Generate a GitHub release 81 | uses: softprops/action-gh-release@v2 82 | with: 83 | generate_release_notes: true 84 | files: dist/* 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Environments 92 | .env 93 | .venv 94 | env/ 95 | venv/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | 100 | # Spyder project settings 101 | .spyderproject 102 | .spyproject 103 | 104 | # Rope project settings 105 | .ropeproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | .dmypy.json 113 | dmypy.json 114 | 115 | # Pyre type checker 116 | .pyre/ 117 | 118 | # User settings 119 | _sandbox/ 120 | *_profile 121 | Untitled.ipynb 122 | 123 | .pytype/ 124 | *.sqlite3 125 | *.xlsx 126 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | formats: 12 | - epub 13 | 14 | python: 15 | install: 16 | - method: pip 17 | path: . 18 | extra_requirements: 19 | - docs 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2025 Tsuyoshi Hombashi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | include tox.ini 4 | include docs/pages/introduction/summary.txt 5 | include */py.typed 6 | 7 | recursive-include test * 8 | recursive-include requirements * 9 | 10 | global-exclude __pycache__/* 11 | global-exclude *.pyc 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON := python3 2 | 3 | PACKAGE := SimpleSQLite 4 | BUILD_WORK_DIR := _work 5 | DOCS_DIR := docs 6 | PKG_BUILD_DIR := $(BUILD_WORK_DIR)/$(PACKAGE) 7 | 8 | AUTHOR := Tsuyoshi Hombashi 9 | FIRST_RELEASE_YEAR := 2016 10 | LAST_UPDATE_YEAR := $(shell git log -1 --format=%cd --date=format:%Y) 11 | 12 | 13 | .PHONY: build-remote 14 | build-remote: clean 15 | @mkdir -p $(BUILD_WORK_DIR) 16 | @cd $(BUILD_WORK_DIR) && \ 17 | git clone https://github.com/thombashi/$(PACKAGE).git --depth 1 && \ 18 | cd $(PACKAGE) && \ 19 | $(PYTHON) -m tox -e build 20 | ls -lh $(PKG_BUILD_DIR)/dist/* 21 | 22 | .PHONY: build 23 | build: clean 24 | @$(PYTHON) -m tox -e build 25 | ls -lh dist/* 26 | 27 | .PHONY: check 28 | check: 29 | $(PYTHON) -m tox -e lint 30 | $(PYTHON) -m tox -e lint-examples 31 | 32 | .PHONY: clean 33 | clean: 34 | @rm -rf $(BUILD_WORK_DIR) 35 | @$(PYTHON) -m tox -e clean 36 | 37 | .PHONY: docs 38 | docs: 39 | @$(PYTHON) -m tox -e docs 40 | 41 | .PHONY: run-sample 42 | run-sample: 43 | find sample/ -name "*.py" | \grep -v sample_create_table_from_gs.py | xargs -I{} $(PYTHON) {} 44 | 45 | .PHONY: idocs 46 | idocs: 47 | @$(PYTHON) -m pip install -q --disable-pip-version-check --upgrade . 48 | @$(MAKE) docs 49 | 50 | .PHONY: fmt 51 | fmt: 52 | @$(PYTHON) -m tox -e fmt 53 | 54 | .PHONY: readme 55 | readme: 56 | @$(PYTHON) -m tox -e readme 57 | 58 | .PHONY: release 59 | release: 60 | $(PYTHON) -m tox -e release 61 | $(MAKE) clean 62 | 63 | .PHONY: setup-ci 64 | setup-ci: 65 | $(PYTHON) -m pip install -q --disable-pip-version-check --upgrade pip 66 | $(PYTHON) -m pip install -q --disable-pip-version-check --upgrade tox 67 | 68 | .PHONY: setup-dev 69 | setup-dev: setup-ci 70 | $(PYTHON) -m pip install -q --disable-pip-version-check --upgrade -e .[test] 71 | -$(PYTHON) -m pip check 72 | 73 | .PHONY: update-copyright 74 | update-copyright: 75 | sed -i "s/f\"Copyright .*/f\"Copyright $(FIRST_RELEASE_YEAR)-$(LAST_UPDATE_YEAR), {__author__}\"/" simplesqlite/__version__.py 76 | sed -i "s/^Copyright (c) .* $(AUTHOR)/Copyright (c) $(FIRST_RELEASE_YEAR)-$(LAST_UPDATE_YEAR) $(AUTHOR)/" LICENSE 77 | -------------------------------------------------------------------------------- /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 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: html 55 | html: 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | .PHONY: dirhtml 61 | dirhtml: 62 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 63 | @echo 64 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 65 | 66 | .PHONY: singlehtml 67 | singlehtml: 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | .PHONY: pickle 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | .PHONY: json 79 | json: 80 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 81 | @echo 82 | @echo "Build finished; now you can process the JSON files." 83 | 84 | .PHONY: htmlhelp 85 | htmlhelp: 86 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 87 | @echo 88 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 89 | ".hhp project file in $(BUILDDIR)/htmlhelp." 90 | 91 | .PHONY: qthelp 92 | qthelp: 93 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 94 | @echo 95 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 96 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 97 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SimpleSQLite.qhcp" 98 | @echo "To view the help file:" 99 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SimpleSQLite.qhc" 100 | 101 | .PHONY: applehelp 102 | applehelp: 103 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 104 | @echo 105 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 106 | @echo "N.B. You won't be able to view it unless you put it in" \ 107 | "~/Library/Documentation/Help or install it in your application" \ 108 | "bundle." 109 | 110 | .PHONY: devhelp 111 | devhelp: 112 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 113 | @echo 114 | @echo "Build finished." 115 | @echo "To view the help file:" 116 | @echo "# mkdir -p $$HOME/.local/share/devhelp/SimpleSQLite" 117 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SimpleSQLite" 118 | @echo "# devhelp" 119 | 120 | .PHONY: epub 121 | epub: 122 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 123 | @echo 124 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 125 | 126 | .PHONY: latex 127 | latex: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo 130 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 131 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 132 | "(use \`make latexpdf' here to do that automatically)." 133 | 134 | .PHONY: latexpdf 135 | latexpdf: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through pdflatex..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | .PHONY: latexpdfja 142 | latexpdfja: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through platex and dvipdfmx..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: text 149 | text: 150 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 151 | @echo 152 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 153 | 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | 160 | .PHONY: texinfo 161 | texinfo: 162 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 163 | @echo 164 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 165 | @echo "Run \`make' in that directory to run these through makeinfo" \ 166 | "(use \`make info' here to do that automatically)." 167 | 168 | .PHONY: info 169 | info: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo "Running Texinfo files through makeinfo..." 172 | make -C $(BUILDDIR)/texinfo info 173 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 174 | 175 | .PHONY: gettext 176 | gettext: 177 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 178 | @echo 179 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 180 | 181 | .PHONY: changes 182 | changes: 183 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 184 | @echo 185 | @echo "The overview file is in $(BUILDDIR)/changes." 186 | 187 | .PHONY: linkcheck 188 | linkcheck: 189 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 190 | @echo 191 | @echo "Link check complete; look for any errors in the above output " \ 192 | "or in $(BUILDDIR)/linkcheck/output.txt." 193 | 194 | .PHONY: doctest 195 | doctest: 196 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 197 | @echo "Testing of doctests in the sources finished, look at the " \ 198 | "results in $(BUILDDIR)/doctest/output.txt." 199 | 200 | .PHONY: coverage 201 | coverage: 202 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 203 | @echo "Testing of coverage in the sources finished, look at the " \ 204 | "results in $(BUILDDIR)/coverage/python.txt." 205 | 206 | .PHONY: xml 207 | xml: 208 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 209 | @echo 210 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 211 | 212 | .PHONY: pseudoxml 213 | pseudoxml: 214 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 215 | @echo 216 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 217 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import sphinx_rtd_theme 5 | 6 | from simplesqlite import __author__, __copyright__, __name__, __version__ 7 | 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | sys.path.insert(0, os.path.abspath('../simplesqlite')) 13 | 14 | # -- General configuration ------------------------------------------------ 15 | 16 | # If your documentation needs a minimal Sphinx version, state it here. 17 | #needs_sphinx = '1.0' 18 | 19 | # Add any Sphinx extension module names here, as strings. They can be 20 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 21 | # ones. 22 | extensions = [ 23 | 'sphinx.ext.autodoc', 24 | 'sphinx.ext.todo', 25 | 'sphinx.ext.viewcode', 26 | 'sphinx.ext.intersphinx', 27 | 'sphinx.ext.napoleon', 28 | ] 29 | 30 | intersphinx_mapping = {'python': ('https://docs.python.org/', None)} 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # The suffix(es) of source filenames. 36 | # You can specify multiple suffix as a list of string: 37 | # source_suffix = ['.rst', '.md'] 38 | source_suffix = '.rst' 39 | 40 | # The encoding of source files. 41 | #source_encoding = 'utf-8-sig' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # General information about the project. 47 | project = __name__ 48 | copyright = __copyright__ 49 | author = __author__ 50 | 51 | # The version info for the project you're documenting, acts as replacement for 52 | # |version| and |release|, also used in various other places throughout the 53 | # built documents. 54 | # 55 | # The short X.Y version. 56 | version = __version__ 57 | # The full version, including alpha/beta/rc tags. 58 | release = version 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | # 63 | # This is also used if you do content translation via gettext catalogs. 64 | # Usually you set "language" from the command line for these cases. 65 | language = 'en' 66 | 67 | # There are two options for replacing |today|: either, you set today to some 68 | # non-false value, then it is used: 69 | #today = '' 70 | # Else, today_fmt is used as the format for a strftime call. 71 | #today_fmt = '%B %d, %Y' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | exclude_patterns = ['_build'] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all 78 | # documents. 79 | #default_role = None 80 | 81 | # If true, '()' will be appended to :func: etc. cross-reference text. 82 | #add_function_parentheses = True 83 | 84 | # If true, the current module name will be prepended to all description 85 | # unit titles (such as .. function::). 86 | #add_module_names = True 87 | 88 | # If true, sectionauthor and moduleauthor directives will be shown in the 89 | # output. They are ignored by default. 90 | #show_authors = False 91 | 92 | # The name of the Pygments (syntax highlighting) style to use. 93 | pygments_style = 'sphinx' 94 | 95 | # A list of ignored prefixes for module index sorting. 96 | #modindex_common_prefix = [] 97 | 98 | # If true, keep warnings as "system message" paragraphs in the built documents. 99 | #keep_warnings = False 100 | 101 | # If true, `todo` and `todoList` produce output, else they produce nothing. 102 | todo_include_todos = False 103 | 104 | 105 | # -- Options for HTML output ---------------------------------------------- 106 | 107 | # The theme to use for HTML and HTML Help pages. See the documentation for 108 | # a list of builtin themes. 109 | html_theme = 'sphinx_rtd_theme' 110 | 111 | # Theme options are theme-specific and customize the look and feel of a theme 112 | # further. For a list of options available for each theme, see the 113 | # documentation. 114 | #html_theme_options = {} 115 | 116 | # Add any paths that contain custom themes here, relative to this directory. 117 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 118 | 119 | # The name for this set of Sphinx documents. If None, it defaults to 120 | # " v documentation". 121 | #html_title = None 122 | 123 | # A shorter title for the navigation bar. Default is the same as html_title. 124 | #html_short_title = None 125 | 126 | # The name of an image file (relative to this directory) to place at the top 127 | # of the sidebar. 128 | #html_logo = None 129 | 130 | # The name of an image file (within the static path) to use as favicon of the 131 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 132 | # pixels large. 133 | #html_favicon = None 134 | 135 | # Add any paths that contain custom static files (such as style sheets) here, 136 | # relative to this directory. They are copied after the builtin static files, 137 | # so a file named "default.css" will overwrite the builtin "default.css". 138 | html_static_path = ['_static'] 139 | 140 | # Add any extra paths that contain custom files (such as robots.txt or 141 | # .htaccess) here, relative to this directory. These files are copied 142 | # directly to the root of the documentation. 143 | #html_extra_path = [] 144 | 145 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 146 | # using the given strftime format. 147 | #html_last_updated_fmt = '%b %d, %Y' 148 | 149 | # If true, SmartyPants will be used to convert quotes and dashes to 150 | # typographically correct entities. 151 | #html_use_smartypants = True 152 | 153 | # Custom sidebar templates, maps document names to template names. 154 | #html_sidebars = {} 155 | 156 | # Additional templates that should be rendered to pages, maps page names to 157 | # template names. 158 | #html_additional_pages = {} 159 | 160 | # If false, no module index is generated. 161 | #html_domain_indices = True 162 | 163 | # If false, no index is generated. 164 | #html_use_index = True 165 | 166 | # If true, the index is split into individual pages for each letter. 167 | #html_split_index = False 168 | 169 | # If true, links to the reST sources are added to the pages. 170 | #html_show_sourcelink = True 171 | 172 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 173 | #html_show_sphinx = True 174 | 175 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 176 | #html_show_copyright = True 177 | 178 | # If true, an OpenSearch description file will be output, and all pages will 179 | # contain a tag referring to it. The value of this option must be the 180 | # base URL from which the finished HTML is served. 181 | #html_use_opensearch = '' 182 | 183 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 184 | #html_file_suffix = None 185 | 186 | # Language to be used for generating the HTML full-text search index. 187 | # Sphinx supports the following languages: 188 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 189 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 190 | #html_search_language = 'en' 191 | 192 | # A dictionary with options for the search language support, empty by default. 193 | # Now only 'ja' uses this config value 194 | #html_search_options = {'type': 'default'} 195 | 196 | # The name of a javascript file (relative to the configuration directory) that 197 | # implements a search results scorer. If empty, the default will be used. 198 | #html_search_scorer = 'scorer.js' 199 | 200 | # Output file base name for HTML help builder. 201 | htmlhelp_basename = 'SimpleSQLitedoc' 202 | 203 | # -- Options for LaTeX output --------------------------------------------- 204 | 205 | latex_elements = { 206 | # The paper size ('letterpaper' or 'a4paper'). 207 | #'papersize': 'letterpaper', 208 | 209 | # The font size ('10pt', '11pt' or '12pt'). 210 | #'pointsize': '10pt', 211 | 212 | # Additional stuff for the LaTeX preamble. 213 | #'preamble': '', 214 | 215 | # Latex figure (float) alignment 216 | #'figure_align': 'htbp', 217 | } 218 | 219 | # Grouping the document tree into LaTeX files. List of tuples 220 | # (source start file, target name, title, 221 | # author, documentclass [howto, manual, or own class]). 222 | latex_documents = [ 223 | (master_doc, 'SimpleSQLite.tex', 'SimpleSQLite Documentation', 224 | __author__, 'manual'), 225 | ] 226 | 227 | # The name of an image file (relative to this directory) to place at the top of 228 | # the title page. 229 | #latex_logo = None 230 | 231 | # For "manual" documents, if this is true, then toplevel headings are parts, 232 | # not chapters. 233 | #latex_use_parts = False 234 | 235 | # If true, show page references after internal links. 236 | #latex_show_pagerefs = False 237 | 238 | # If true, show URL addresses after external links. 239 | #latex_show_urls = False 240 | 241 | # Documents to append as an appendix to all manuals. 242 | #latex_appendices = [] 243 | 244 | # If false, no module index is generated. 245 | #latex_domain_indices = True 246 | 247 | 248 | # -- Options for manual page output --------------------------------------- 249 | 250 | # One entry per manual page. List of tuples 251 | # (source start file, name, description, authors, manual section). 252 | man_pages = [ 253 | (master_doc, 'simplesqlite', 'SimpleSQLite Documentation', 254 | [author], 1) 255 | ] 256 | 257 | # If true, show URL addresses after external links. 258 | #man_show_urls = False 259 | 260 | 261 | # -- Options for Texinfo output ------------------------------------------- 262 | 263 | # Grouping the document tree into Texinfo files. List of tuples 264 | # (source start file, target name, title, author, 265 | # dir menu entry, description, category) 266 | texinfo_documents = [ 267 | (master_doc, 'SimpleSQLite', 'SimpleSQLite Documentation', 268 | author, 'SimpleSQLite', 'One line description of project.', 269 | 'Miscellaneous'), 270 | ] 271 | 272 | # Documents to append as an appendix to all manuals. 273 | #texinfo_appendices = [] 274 | 275 | # If false, no module index is generated. 276 | #texinfo_domain_indices = True 277 | 278 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 279 | #texinfo_show_urls = 'footnote' 280 | 281 | # If true, do not generate a @detailmenu in the "Top" node's menu. 282 | #texinfo_no_detailmenu = False 283 | 284 | 285 | # ------------------------------------------------ 286 | 287 | rp_common = """ 288 | .. |TM| replace:: :superscript:`TM` 289 | """ 290 | 291 | rp_builtin = """ 292 | .. |False| replace:: :py:obj:`False` 293 | .. |True| replace:: :py:obj:`True` 294 | .. |None| replace:: :py:obj:`None` 295 | 296 | .. |bool| replace:: :py:class:`bool` 297 | .. |dict| replace:: :py:class:`dict` 298 | .. |int| replace:: :py:class:`int` 299 | .. |list| replace:: :py:class:`list` 300 | .. |float| replace:: :py:class:`float` 301 | .. |str| replace:: :py:class:`str` 302 | .. |tuple| replace:: :py:obj:`tuple` 303 | 304 | .. |OrderedDict| replace:: :py:class:`collections.OrderedDict` 305 | """ 306 | 307 | rp_func = """ 308 | .. |namedtuple| replace:: :py:func:`~collections.namedtuple` 309 | """ 310 | 311 | rp_class = """ 312 | .. |Connection| replace:: :py:class:`sqlite3.Connection` 313 | .. |datetime| replace:: :py:class:`datetime.datetime` 314 | .. |timedelta| replace:: :py:class:`datetime.timedelta` 315 | 316 | .. |TableData| replace:: :py:class:`~simplesqlite.loader.data.TableData` 317 | .. |SimpleSQLite| replace:: :py:class:`~simplesqlite.SimpleSQLite` 318 | 319 | .. |And| replace:: :py:class:`~simplesqlite.query.And` 320 | .. |Or| replace:: :py:class:`~simplesqlite.query.Or` 321 | .. |Where| replace:: :py:class:`~simplesqlite.query.Where` 322 | """ 323 | 324 | rp_module = """ 325 | .. |sqlite3| replace:: :py:mod:`sqlite3` 326 | """ 327 | 328 | rp_attr = """ 329 | .. |attr_mode| replace:: :py:attr:`.mode` 330 | 331 | .. |attr_table_name_description| replace:: 332 | Table name string. 333 | Following format specifiers are replaced with specific string. 334 | 335 | .. |attr_table_name_header| replace:: format specifier 336 | """ 337 | 338 | rp_method = """ 339 | .. |make_table_name| replace:: 340 | Make table name string from :py:attr:`~simplesqlite.loader.interface.AbstractTableReader.table_name`. 341 | Following format specifiers are replaced with specific string. 342 | """ 343 | 344 | rp_param = """ 345 | .. |primary_key| replace:: 346 | Primary key of the creating table. 347 | 348 | .. |index_attrs| replace:: 349 | List of attribute names that creating indices. 350 | 351 | .. |arg_select_table_name| replace:: Table name of executing the query. 352 | .. |arg_select_where| replace:: ``WHERE`` clause for the query. 353 | .. |arg_select_extra| replace:: Any other SQL clause for the query. 354 | 355 | .. |arg_where_type| replace:: |str|/|Where|/|And|/|Or| 356 | 357 | .. |arg_select_as_xx_columns| replace:: 358 | Column names to get data. If the value is |None|, 359 | get data from all of the columns in the table. 360 | """ 361 | 362 | rp_raises = """ 363 | .. |raises_check_connection| replace:: 364 | If not connected to a SQLite database file. 365 | 366 | .. |raises_verify_table_existence| replace:: 367 | If the table not found in the database. 368 | 369 | .. |raises_operational_error| replace:: 370 | If failed to execute a query. 371 | 372 | .. |raises_validate_table_name| replace:: 373 | If the name is invalid for a SQLite table name. 374 | 375 | .. |raises_validate_attr_name| replace:: 376 | If the name is invalid for a SQLite attribute name. 377 | 378 | .. |raises_write_permission| replace:: 379 | If the open |attr_mode| is neither ``"w"`` nor ``"a"``. 380 | """ 381 | 382 | rp_template = """ 383 | .. |tnt_filename| replace:: %(filename)s 384 | .. |tnt_format_name| replace:: %(format_name)s 385 | .. |tnt_format_id| replace:: %(format_id)s 386 | .. |tnt_global_id| replace:: %(global_id)s 387 | .. |tnt_key| replace:: %(key)s 388 | .. |tnt_title| replace:: %(title)s 389 | .. |tnt_sheet| replace:: %(sheet)s 390 | """ 391 | 392 | rst_prolog = ( 393 | rp_common + 394 | rp_builtin + 395 | rp_func + 396 | rp_class + 397 | rp_module + 398 | rp_attr + 399 | rp_method + 400 | rp_param + 401 | rp_raises + 402 | rp_template 403 | ) 404 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to SimpleSQLite's documentation! 2 | ======================================== 3 | 4 | .. raw:: html 5 | 6 |
7 | 8 |
9 |
10 | 11 | .. toctree:: 12 | :caption: Table of Contents 13 | :maxdepth: 4 14 | :numbered: 15 | 16 | pages/introduction/index 17 | pages/examples/index 18 | pages/reference/index 19 | pages/links 20 | 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | -------------------------------------------------------------------------------- /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 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\SimpleSQLite.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\SimpleSQLite.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/make_readme.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | .. codeauthor:: Tsuyoshi Hombashi 5 | """ 6 | 7 | import sys 8 | 9 | from path import Path 10 | from readmemaker import ReadmeMaker 11 | 12 | 13 | PROJECT_NAME = "SimpleSQLite" 14 | OUTPUT_DIR = ".." 15 | 16 | 17 | def write_examples(maker): 18 | maker.set_indent_level(0) 19 | maker.write_chapter("Examples") 20 | 21 | examples_root = Path("pages").joinpath("examples") 22 | 23 | maker.inc_indent_level() 24 | maker.write_chapter("Create a table") 25 | 26 | with maker.indent(): 27 | maker.write_chapter("Create a table from a data matrix") 28 | maker.write_file(examples_root.joinpath("create_table/create_table_from_data_matrix.txt")) 29 | 30 | maker.write_chapter("Create a table from CSV") 31 | maker.write_file(examples_root.joinpath("create_table/create_table_from_csv.txt")) 32 | 33 | maker.write_chapter("Create a table from pandas.DataFrame") 34 | maker.write_file(examples_root.joinpath("create_table/create_table_from_df.txt")) 35 | 36 | maker.write_chapter("Insert records into a table") 37 | maker.write_file(examples_root.joinpath("insert_record_example.txt")) 38 | 39 | maker.write_chapter("Fetch data from a table as pandas DataFrame") 40 | maker.write_file(examples_root.joinpath("select_as/select_as_dataframe.txt")) 41 | 42 | maker.write_chapter("ORM functionality") 43 | maker.write_file(examples_root.joinpath("orm/orm_model.txt")) 44 | 45 | maker.write_chapter("For more information") 46 | maker.write_lines( 47 | [ 48 | "More examples are available at ", 49 | f"https://{PROJECT_NAME.lower():s}.rtfd.io/en/latest/pages/examples/index.html", 50 | ] 51 | ) 52 | 53 | 54 | def main() -> None: 55 | maker = ReadmeMaker( 56 | PROJECT_NAME, 57 | OUTPUT_DIR, 58 | is_make_toc=True, 59 | project_url=f"https://github.com/thombashi/{PROJECT_NAME}", 60 | ) 61 | 62 | maker.write_chapter("Summary") 63 | maker.write_introduction_file("summary.txt") 64 | maker.write_introduction_file("badges.txt") 65 | maker.write_introduction_file("feature.txt") 66 | 67 | write_examples(maker) 68 | 69 | maker.write_introduction_file("installation.rst") 70 | 71 | maker.set_indent_level(0) 72 | maker.write_chapter("Documentation") 73 | maker.write_lines([f"https://{PROJECT_NAME.lower():s}.rtfd.io/"]) 74 | 75 | maker.write_chapter("Related Project") 76 | maker.write_lines( 77 | [ 78 | "- `sqlitebiter `__: " 79 | "CLI tool to convert CSV/Excel/HTML/JSON/LTSV/Markdown/TSV/Google-Sheets " 80 | "SQLite database by using SimpleSQLite" 81 | ] 82 | ) 83 | 84 | maker.write_file(maker.doc_page_root_dir_path.joinpath("sponsors.rst")) 85 | 86 | return 0 87 | 88 | 89 | if __name__ == "__main__": 90 | sys.exit(main()) 91 | -------------------------------------------------------------------------------- /docs/pages/examples/create_table/create_table_from_csv.txt: -------------------------------------------------------------------------------- 1 | :Sample Code: 2 | .. code-block:: python 3 | :caption: Create a table in a SQLite database from CSV 4 | 5 | from simplesqlite import SimpleSQLite 6 | 7 | with open("sample_data.csv", "w") as f: 8 | f.write("\n".join([ 9 | '"attr_a","attr_b","attr_c"', 10 | '1,4,"a"', 11 | '2,2.1,"bb"', 12 | '3,120.9,"ccc"', 13 | ])) 14 | 15 | # create table --- 16 | con = SimpleSQLite("sample.sqlite", "w") 17 | con.create_table_from_csv("sample_data.csv") 18 | 19 | # output --- 20 | table_name = "sample_data" 21 | print(con.fetch_attr_names(table_name)) 22 | result = con.select(select="*", table_name=table_name) 23 | for record in result.fetchall(): 24 | print(record) 25 | 26 | :Output: 27 | .. code-block:: none 28 | 29 | ['attr_a', 'attr_b', 'attr_c'] 30 | (1, 4.0, 'a') 31 | (2, 2.1, 'bb') 32 | (3, 120.9, 'ccc') 33 | -------------------------------------------------------------------------------- /docs/pages/examples/create_table/create_table_from_data_matrix.txt: -------------------------------------------------------------------------------- 1 | :Sample Code: 2 | .. code-block:: python 3 | :caption: Create a table in a SQLite database from data matrix 4 | 5 | from simplesqlite import SimpleSQLite 6 | 7 | 8 | table_name = "sample_table" 9 | con = SimpleSQLite("sample.sqlite", "w") 10 | 11 | # create table ----- 12 | data_matrix = [[1, 1.1, "aaa", 1, 1], [2, 2.2, "bbb", 2.2, 2.2], [3, 3.3, "ccc", 3, "ccc"]] 13 | con.create_table_from_data_matrix( 14 | table_name, 15 | ["attr_a", "attr_b", "attr_c", "attr_d", "attr_e"], 16 | data_matrix, 17 | ) 18 | 19 | # display data type for each column in the table ----- 20 | print(con.schema_extractor.fetch_table_schema(table_name).dumps()) 21 | 22 | # display values in the table ----- 23 | print("records:") 24 | result = con.select(select="*", table_name=table_name) 25 | for record in result.fetchall(): 26 | print(record) 27 | 28 | :Output: 29 | .. code-block:: none 30 | 31 | .. table:: sample_table 32 | 33 | +---------+-------+-----------+--------+------+-----+ 34 | |Attribute| Type |PRIMARY KEY|NOT NULL|UNIQUE|Index| 35 | +=========+=======+===========+========+======+=====+ 36 | |attr_a |INTEGER| | | | | 37 | +---------+-------+-----------+--------+------+-----+ 38 | |attr_b |REAL | | | | | 39 | +---------+-------+-----------+--------+------+-----+ 40 | |attr_c |TEXT | | | | | 41 | +---------+-------+-----------+--------+------+-----+ 42 | |attr_d |REAL | | | | | 43 | +---------+-------+-----------+--------+------+-----+ 44 | |attr_e |TEXT | | | | | 45 | +---------+-------+-----------+--------+------+-----+ 46 | 47 | 48 | records: 49 | (1, 1.1, 'aaa', 1.0, '1') 50 | (2, 2.2, 'bbb', 2.2, '2.2') 51 | (3, 3.3, 'ccc', 3.0, 'ccc') 52 | -------------------------------------------------------------------------------- /docs/pages/examples/create_table/create_table_from_df.txt: -------------------------------------------------------------------------------- 1 | :Sample Code: 2 | .. code-block:: python 3 | :caption: Create a table in a SQLite database from pandas.DataFrame 4 | 5 | from simplesqlite import SimpleSQLite 6 | import pandas 7 | 8 | con = SimpleSQLite("pandas_df.sqlite") 9 | 10 | con.create_table_from_dataframe(pandas.DataFrame( 11 | [ 12 | [0, 0.1, "a"], 13 | [1, 1.1, "bb"], 14 | [2, 2.2, "ccc"], 15 | ], 16 | columns=['id', 'value', 'name'] 17 | ), table_name="pandas_df") 18 | 19 | :Output: 20 | .. code-block:: sql 21 | :caption: Output sqlite database file 22 | 23 | $ sqlite3 pandas_df.sqlite 24 | sqlite> .schema 25 | CREATE TABLE 'pandas_df' (id INTEGER, value REAL, name TEXT); 26 | -------------------------------------------------------------------------------- /docs/pages/examples/create_table/create_table_from_excel.txt: -------------------------------------------------------------------------------- 1 | :Sample Code: 2 | .. code-block:: python 3 | :caption: Create a table in a SQLite database from an Excel file 4 | 5 | import pytablereader 6 | import simplesqlite 7 | import xlsxwriter 8 | 9 | file_path = "sample_data.xlsx" 10 | 11 | # create sample data file --- 12 | workbook = xlsxwriter.Workbook(file_path) 13 | 14 | worksheet = workbook.add_worksheet("samplesheet1") 15 | table = [ 16 | ["", "", "", ""], 17 | ["", "a", "b", "c"], 18 | ["", 1, 1.1, "a"], 19 | ["", 2, 2.2, "bb"], 20 | ["", 3, 3.3, "cc"], 21 | ] 22 | for row_idx, row in enumerate(table): 23 | for col_idx, item in enumerate(row): 24 | worksheet.write(row_idx, col_idx, item) 25 | 26 | worksheet = workbook.add_worksheet("samplesheet2") 27 | 28 | worksheet = workbook.add_worksheet("samplesheet3") 29 | table = [ 30 | ["", "", ""], 31 | ["", "", ""], 32 | ["aa", "ab", "ac"], 33 | [1, "hoge", "a"], 34 | [2, "", "bb"], 35 | [3, "foo", ""], 36 | ] 37 | for row_idx, row in enumerate(table): 38 | for col_idx, item in enumerate(row): 39 | worksheet.write(row_idx, col_idx, item) 40 | 41 | workbook.close() 42 | 43 | # create table --- 44 | con = simplesqlite.SimpleSQLite("sample.sqlite", "w") 45 | 46 | loader = pytablereader.ExcelTableFileLoader(file_path) 47 | for table_data in loader.load(): 48 | con.create_table_from_tabledata(table_data) 49 | 50 | # output --- 51 | for table_name in con.fetch_table_names(): 52 | print("table: " + table_name) 53 | print(con.fetch_attr_names(table_name)) 54 | result = con.select(select="*", table_name=table_name) 55 | for record in result.fetchall(): 56 | print(record) 57 | print() 58 | 59 | :Output: 60 | .. code-block:: none 61 | 62 | table: samplesheet1 63 | ['a', 'b', 'c'] 64 | (1.0, 1.1, 'a') 65 | (2.0, 2.2, 'bb') 66 | (3.0, 3.3, 'cc') 67 | 68 | table: samplesheet3 69 | ['aa', 'ab', 'ac'] 70 | (1.0, 'hoge', 'a') 71 | (2.0, '', 'bb') 72 | (3.0, 'foo', '') 73 | -------------------------------------------------------------------------------- /docs/pages/examples/create_table/create_table_from_gs.txt: -------------------------------------------------------------------------------- 1 | :Sample Code: 2 | .. code-block:: python 3 | :caption: Create a table in a SQLite database from Google Sheets 4 | 5 | import simplesqlite 6 | import pytablereader as ptr 7 | 8 | 9 | credentials_file = "sample-xxxxxxxxxxxx.json" 10 | 11 | # create table --- 12 | con = simplesqlite.SimpleSQLite("sample.sqlite", "w") 13 | 14 | loader = ptr.GoogleSheetsTableLoader(credentials_file) 15 | loader.title = "samplebook" 16 | 17 | for table_data in loader.load(): 18 | con.create_table_from_tabledata(table_data) 19 | 20 | # output --- 21 | for table_name in con.fetch_table_names(): 22 | print("table: " + table_name) 23 | print(con.fetch_attr_names(table_name)) 24 | result = con.select(select="*", table_name=table_name) 25 | for record in result.fetchall(): 26 | print(record) 27 | print() 28 | -------------------------------------------------------------------------------- /docs/pages/examples/create_table/create_table_from_json.txt: -------------------------------------------------------------------------------- 1 | .. _example-convert-multi-json-table: 2 | 3 | :Sample Code 1: 4 | .. code-block:: python 5 | :caption: Create multiple tables in a SQLite database from multiple table data in a JSON file 6 | 7 | from simplesqlite import SimpleSQLite 8 | 9 | file_path = "sample_data_multi.json" 10 | 11 | # create sample data file --- 12 | with open(file_path, "w") as f: 13 | f.write("""{ 14 | "table_a" : [ 15 | {"attr_b": 4, "attr_c": "a", "attr_a": 1}, 16 | {"attr_b": 2.1, "attr_c": "bb", "attr_a": 2}, 17 | {"attr_b": 120.9, "attr_c": "ccc", "attr_a": 3} 18 | ], 19 | "table_b" : [ 20 | {"a": 1, "b": 4}, 21 | {"a": 2 }, 22 | {"a": 3, "b": 120.9} 23 | ] 24 | }""") 25 | 26 | # create table --- 27 | con = SimpleSQLite("sample.sqlite", "w") 28 | con.create_table_from_json(file_path) 29 | 30 | # output --- 31 | for table_name in con.fetch_table_names(): 32 | print("table: " + table_name) 33 | print(con.fetch_attr_names(table_name)) 34 | result = con.select(select="*", table_name=table_name) 35 | for record in result.fetchall(): 36 | print(record) 37 | print() 38 | 39 | :Output: 40 | .. code-block:: none 41 | :caption: Output of the sample code 1 42 | 43 | table: table_b 44 | ['a', 'b'] 45 | (1, '4') 46 | (2, 'NULL') 47 | (3, '120.9') 48 | 49 | table: table_a 50 | ['attr_a', 'attr_b', 'attr_c'] 51 | (1, 4.0, 'a') 52 | (2, 2.1, 'bb') 53 | (3, 120.9, 'ccc') 54 | 55 | 56 | .. _example-convert-single-json-table: 57 | 58 | :Sample Code 2: 59 | .. code-block:: python 60 | :caption: Create a table in a SQLite database from a table data in a JSON file 61 | 62 | from simplesqlite import SimpleSQLite 63 | 64 | file_path = "sample_data_single.json" 65 | 66 | # create sample data file --- 67 | with open(file_path, "w") as f: 68 | f.write("""[ 69 | {"attr_b": 4, "attr_c": "a", "attr_a": 1}, 70 | {"attr_b": 2.1, "attr_c": "bb", "attr_a": 2}, 71 | {"attr_b": 120.9, "attr_c": "ccc", "attr_a": 3} 72 | ]""") 73 | 74 | # create table --- 75 | con = SimpleSQLite("sample.sqlite", "w") 76 | con.create_table_from_json(file_path) 77 | 78 | # output --- 79 | for table_name in con.fetch_table_names(): 80 | print("table: " + table_name) 81 | print(con.fetch_attr_names(table_name)) 82 | result = con.select(select="*", table_name=table_name) 83 | for record in result.fetchall(): 84 | print(record) 85 | print() 86 | 87 | :Output: 88 | .. code-block:: none 89 | :caption: Output of the sample code 2 90 | 91 | table: sample_data_single 92 | ['attr_a', 'attr_b', 'attr_c'] 93 | (1, 4.0, 'a') 94 | (2, 2.1, 'bb') 95 | (3, 120.9, 'ccc') 96 | -------------------------------------------------------------------------------- /docs/pages/examples/create_table/index.rst: -------------------------------------------------------------------------------- 1 | Create table 2 | -------------- 3 | 4 | .. _example-create-table-from-data-matrix: 5 | 6 | Create a table from a data matrix 7 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | :py:meth:`~simplesqlite.SimpleSQLite.create_table_from_data_matrix` 9 | method create a table in a SQLite database from data matrix. 10 | Data matrix required one of the types: |dict|/|namedtuple|/|list|/|tuple|. 11 | 12 | .. include:: create_table_from_data_matrix.txt 13 | 14 | 15 | .. _example-create-table-from-csv: 16 | 17 | Create a table from CSV 18 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 19 | :py:meth:`~simplesqlite.SimpleSQLite.create_table_from_csv` 20 | method create a table from a :abbr:`CSV(Comma Separated Values)` file/text. 21 | 22 | .. include:: create_table_from_csv.txt 23 | 24 | 25 | .. _example-create-table-from-json: 26 | 27 | Create table(s) from JSON 28 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 29 | :py:meth:`~simplesqlite.SimpleSQLite.create_table_from_json` method 30 | can create a table from a JSON file/text. 31 | 32 | .. include:: create_table_from_json.txt 33 | 34 | 35 | .. _example-create-table-from-df: 36 | 37 | Create a table from pandas DataFrame 38 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 39 | :py:meth:`~simplesqlite.SimpleSQLite.create_table_from_dataframe` method 40 | can create a table from a ``pandas.DataFrame`` instance. 41 | 42 | .. include:: create_table_from_df.txt 43 | 44 | 45 | Create table(s) from Excel 46 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 47 | You can extract tabular data from an Excel file by 48 | :py:class:`pytablereader.ExcelTableFileLoader` class defined in 49 | `pytablereader `__ module. 50 | And you can create a table from extracted data by using 51 | :py:meth:`~simplesqlite.SimpleSQLite.create_table_from_tabledata` method. 52 | 53 | .. include:: create_table_from_excel.txt 54 | 55 | 56 | .. _example-create-table-from-gs: 57 | 58 | Create table(s) from Google Sheets 59 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 60 | :py:class:`~simplesqlite.loader.GoogleSheetsTableLoader` class 61 | and :py:meth:`~simplesqlite.SimpleSQLite.create_table_from_tabledata` method 62 | can create a table from Google Spreadsheet. 63 | 64 | Required packages: 65 | 66 | - `oauth2client `_ 67 | - `pyOpenSSL `_ 68 | 69 | .. seealso:: 70 | 71 | https://gspread.rtfd.io/en/latest/oauth2.html 72 | 73 | .. include:: create_table_from_gs.txt 74 | -------------------------------------------------------------------------------- /docs/pages/examples/get_profile.rst: -------------------------------------------------------------------------------- 1 | .. _example-get-profile: 2 | 3 | Profiling 4 | --------- 5 | :py:meth:`~simplesqlite.SimpleSQLite.get_profile` 6 | method can get profile of query execution time. 7 | 8 | :Sample Code: 9 | .. code-block:: python 10 | 11 | from simplesqlite import SimpleSQLite 12 | 13 | 14 | con = SimpleSQLite("sample.sqlite", "w", profile=True) 15 | data_matrix = [ 16 | [1, 1.1, "aaa", 1, 1], 17 | [2, 2.2, "bbb", 2.2, 2.2], 18 | [3, 3.3, "ccc", 3, "ccc"], 19 | ] 20 | con.create_table_from_data_matrix( 21 | "sample_table", 22 | ["a", "b", "c", "d", "e"], 23 | data_matrix, 24 | index_attrs=["a"]) 25 | 26 | for profile in con.get_profile(): 27 | print(profile) 28 | 29 | :Output: 30 | .. code-block:: none 31 | 32 | SqliteProfile(query="CREATE INDEX IF NOT EXISTS sample_table_a_index ON sample_table('a')", cumulative_time=0.021904945373535156, count=1) 33 | SqliteProfile(query="CREATE TABLE IF NOT EXISTS 'sample_table' ('a' INTEGER, 'b' REAL, 'c' TEXT, 'd' REAL, 'e' TEXT)", cumulative_time=0.015315055847167969, count=1) 34 | SqliteProfile(query="DROP TABLE IF EXISTS 'sample_table'", cumulative_time=0.011831998825073242, count=1) 35 | SqliteProfile(query="SELECT name FROM sqlite_master WHERE TYPE='table'", cumulative_time=0.0004591941833496094, count=6) 36 | SqliteProfile(query="SELECT * FROM 'sample_table'", cumulative_time=4.220008850097656e-05, count=1) 37 | -------------------------------------------------------------------------------- /docs/pages/examples/in_memory_database.rst: -------------------------------------------------------------------------------- 1 | .. _example-connect-sqlite-db-mem: 2 | 3 | Make an in-memory database 4 | -------------------------- 5 | :py:func:`~simplesqlite.connect_memdb` function can create a SQLite database in memory. 6 | This function return |SimpleSQLite| instance, 7 | the instance can execute methods as well as a |SimpleSQLite| instance opened with write mode. 8 | 9 | :Sample Code: 10 | .. code-block:: python 11 | :caption: Make an in-memory database and create a table in the database 12 | 13 | import simplesqlite 14 | 15 | 16 | table_name = "sample_table" 17 | con = simplesqlite.connect_memdb() 18 | 19 | # create table ----- 20 | data_matrix = [[1, 1.1, "aaa", 1, 1], [2, 2.2, "bbb", 2.2, 2.2], [3, 3.3, "ccc", 3, "ccc"]] 21 | con.create_table_from_data_matrix( 22 | table_name, 23 | ["attr_a", "attr_b", "attr_c", "attr_d", "attr_e"], 24 | data_matrix, 25 | ) 26 | 27 | # display data type for each column in the table ----- 28 | print(con.schema_extractor.fetch_table_schema(table_name).dumps()) 29 | 30 | # display values in the table ----- 31 | print("records:") 32 | result = con.select(select="*", table_name=table_name) 33 | for record in result.fetchall(): 34 | print(record) 35 | 36 | :Output: 37 | .. code-block:: none 38 | 39 | .. table:: sample_table 40 | 41 | +---------+-------+-----------+--------+------+-----+ 42 | |Attribute| Type |PRIMARY KEY|NOT NULL|UNIQUE|Index| 43 | +=========+=======+===========+========+======+=====+ 44 | |attr_a |INTEGER| | | | | 45 | +---------+-------+-----------+--------+------+-----+ 46 | |attr_b |REAL | | | | | 47 | +---------+-------+-----------+--------+------+-----+ 48 | |attr_c |TEXT | | | | | 49 | +---------+-------+-----------+--------+------+-----+ 50 | |attr_d |REAL | | | | | 51 | +---------+-------+-----------+--------+------+-----+ 52 | |attr_e |TEXT | | | | | 53 | +---------+-------+-----------+--------+------+-----+ 54 | 55 | 56 | records: 57 | (1, 1.1, 'aaa', 1.0, '1') 58 | (2, 2.2, 'bbb', 2.2, '2.2') 59 | (3, 3.3, 'ccc', 3.0, 'ccc') 60 | -------------------------------------------------------------------------------- /docs/pages/examples/index.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | create_table/index 8 | insert_record 9 | update 10 | select_as/index 11 | orm/index 12 | get_profile 13 | in_memory_database 14 | -------------------------------------------------------------------------------- /docs/pages/examples/insert_record.rst: -------------------------------------------------------------------------------- 1 | .. _example-insert-records: 2 | 3 | Insert records into a table 4 | --------------------------- 5 | :py:meth:`~simplesqlite.SimpleSQLite.insert`/:py:meth:`~simplesqlite.SimpleSQLite.insert_many` 6 | method can insert record(s) into a table. 7 | Record is one of the |dict|/|namedtuple|/|list|/|tuple|. 8 | 9 | .. include:: insert_record_example.txt 10 | -------------------------------------------------------------------------------- /docs/pages/examples/insert_record_example.txt: -------------------------------------------------------------------------------- 1 | Insert dictionary 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | :Sample Code: 5 | .. code-block:: python 6 | 7 | from simplesqlite import SimpleSQLite 8 | 9 | table_name = "sample_table" 10 | con = SimpleSQLite("sample.sqlite", "w") 11 | con.create_table_from_data_matrix( 12 | table_name, 13 | ["attr_a", "attr_b", "attr_c", "attr_d", "attr_e"], 14 | [[1, 1.1, "aaa", 1, 1]]) 15 | 16 | con.insert( 17 | table_name, 18 | record={ 19 | "attr_a": 4, 20 | "attr_b": 4.4, 21 | "attr_c": "ddd", 22 | "attr_d": 4.44, 23 | "attr_e": "hoge", 24 | }) 25 | con.insert_many( 26 | table_name, 27 | records=[ 28 | { 29 | "attr_a": 5, 30 | "attr_b": 5.5, 31 | "attr_c": "eee", 32 | "attr_d": 5.55, 33 | "attr_e": "foo", 34 | }, 35 | { 36 | "attr_a": 6, 37 | "attr_c": "fff", 38 | }, 39 | ]) 40 | 41 | result = con.select(select="*", table_name=table_name) 42 | for record in result.fetchall(): 43 | print(record) 44 | 45 | :Output: 46 | .. code-block:: none 47 | 48 | (1, 1.1, 'aaa', 1, 1) 49 | (4, 4.4, 'ddd', 4.44, 'hoge') 50 | (5, 5.5, 'eee', 5.55, 'foo') 51 | (6, None, 'fff', None, None) 52 | 53 | 54 | Insert list/tuple/namedtuple 55 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | :Sample Code: 58 | .. code-block:: python 59 | 60 | from collections import namedtuple 61 | from simplesqlite import SimpleSQLite 62 | 63 | table_name = "sample_table" 64 | con = SimpleSQLite("sample.sqlite", "w") 65 | con.create_table_from_data_matrix( 66 | table_name, 67 | ["attr_a", "attr_b", "attr_c", "attr_d", "attr_e"], 68 | [[1, 1.1, "aaa", 1, 1]], 69 | ) 70 | 71 | # insert namedtuple 72 | SampleTuple = namedtuple("SampleTuple", "attr_a attr_b attr_c attr_d attr_e") 73 | 74 | con.insert(table_name, record=[7, 7.7, "fff", 7.77, "bar"]) 75 | con.insert_many( 76 | table_name, 77 | records=[(8, 8.8, "ggg", 8.88, "foobar"), SampleTuple(9, 9.9, "ggg", 9.99, "hogehoge")], 78 | ) 79 | 80 | # print 81 | result = con.select(select="*", table_name=table_name) 82 | for record in result.fetchall(): 83 | print(record) 84 | 85 | :Output: 86 | .. code-block:: none 87 | 88 | (1, 1.1, 'aaa', 1, 1) 89 | (7, 7.7, 'fff', 7.77, 'bar') 90 | (8, 8.8, 'ggg', 8.88, 'foobar') 91 | (9, 9.9, 'ggg', 9.99, 'hogehoge') 92 | -------------------------------------------------------------------------------- /docs/pages/examples/orm/index.rst: -------------------------------------------------------------------------------- 1 | ORM functionality 2 | ---------------------------- 3 | 4 | .. include:: orm_model.txt 5 | -------------------------------------------------------------------------------- /docs/pages/examples/orm/orm_model.txt: -------------------------------------------------------------------------------- 1 | :Sample Code: 2 | .. code-block:: python 3 | 4 | from simplesqlite import connect_memdb 5 | from simplesqlite.model import Integer, Model, Real, Text 6 | 7 | 8 | class Sample(Model): 9 | foo_id = Integer(primary_key=True) 10 | name = Text(not_null=True, unique=True) 11 | value = Real(default=0) 12 | 13 | 14 | def main() -> None: 15 | con = connect_memdb() 16 | 17 | Sample.attach(con) 18 | Sample.create() 19 | Sample.insert(Sample(name="abc", value=0.1)) 20 | Sample.insert(Sample(name="xyz", value=1.11)) 21 | Sample.insert(Sample(name="bar")) 22 | 23 | print(Sample.fetch_schema().dumps()) 24 | print("records:") 25 | for record in Sample.select(): 26 | print(f" {record}") 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | 32 | :Output: 33 | .. code-block:: none 34 | 35 | .. table:: sample 36 | 37 | +--------+---------+----------+-----+---------+-------+-------+ 38 | | Field | Type | Nullable | Key | Default | Index | Extra | 39 | +========+=========+==========+=====+=========+=======+=======+ 40 | | foo_id | INTEGER | YES | PRI | NULL | X | | 41 | +--------+---------+----------+-----+---------+-------+-------+ 42 | | name | TEXT | NO | UNI | | X | | 43 | +--------+---------+----------+-----+---------+-------+-------+ 44 | | value | REAL | YES | | 0 | | | 45 | +--------+---------+----------+-----+---------+-------+-------+ 46 | 47 | records: 48 | Sample (foo_id=1, name=abc, value=0.1) 49 | Sample (foo_id=2, name=xyz, value=1.11) 50 | Sample (foo_id=3, name=bar, value=0.0) 51 | -------------------------------------------------------------------------------- /docs/pages/examples/select_as/index.rst: -------------------------------------------------------------------------------- 1 | Get Data from a Table 2 | ---------------------------- 3 | 4 | .. _example-select-as-dict: 5 | 6 | Get Data from a table as OrderedDict 7 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | :py:meth:`~simplesqlite.SimpleSQLite.select_as_dict` 9 | method can get data from a table in a SQLite database as 10 | a |OrderedDict| list. 11 | 12 | .. include:: select_as_dict.txt 13 | 14 | 15 | .. _example-select-as-dataframe: 16 | 17 | Get Data from a table as pandas DataFrame 18 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 19 | :py:meth:`~simplesqlite.SimpleSQLite.select_as_dataframe` 20 | method can get data from a table in a SQLite database as 21 | a :py:class:`pandas.Dataframe` instance. 22 | 23 | .. include:: select_as_dataframe.txt 24 | -------------------------------------------------------------------------------- /docs/pages/examples/select_as/select_as_dataframe.txt: -------------------------------------------------------------------------------- 1 | :Sample Code: 2 | .. code-block:: python 3 | 4 | from simplesqlite import SimpleSQLite 5 | 6 | con = SimpleSQLite("sample.sqlite", "w", profile=True) 7 | 8 | con.create_table_from_data_matrix( 9 | "sample_table", 10 | ["a", "b", "c", "d", "e"], 11 | [ 12 | [1, 1.1, "aaa", 1, 1], 13 | [2, 2.2, "bbb", 2.2, 2.2], 14 | [3, 3.3, "ccc", 3, "ccc"], 15 | ]) 16 | 17 | print(con.select_as_dataframe(table_name="sample_table")) 18 | 19 | :Output: 20 | .. code-block:: none 21 | 22 | $ sample/select_as_dataframe.py 23 | a b c d e 24 | 0 1 1.1 aaa 1.0 1 25 | 1 2 2.2 bbb 2.2 2.2 26 | 2 3 3.3 ccc 3.0 ccc 27 | -------------------------------------------------------------------------------- /docs/pages/examples/select_as/select_as_dict.txt: -------------------------------------------------------------------------------- 1 | :Sample Code: 2 | .. code-block:: python 3 | 4 | from simplesqlite import SimpleSQLite 5 | 6 | con = SimpleSQLite("sample.sqlite", "w", profile=True) 7 | 8 | con.create_table_from_data_matrix( 9 | "sample_table", 10 | ["a", "b", "c", "d", "e"], 11 | [ 12 | [1, 1.1, "aaa", 1, 1], 13 | [2, 2.2, "bbb", 2.2, 2.2], 14 | [3, 3.3, "ccc", 3, "ccc"], 15 | ]) 16 | 17 | for record in con.select_as_dict(table_name="sample_table"): 18 | print(record) 19 | 20 | :Output: 21 | .. code-block:: none 22 | 23 | OrderedDict([('a', 1), ('b', 1.1), ('c', 'aaa'), ('d', 1), ('e', 1)]) 24 | OrderedDict([('a', 2), ('b', 2.2), ('c', 'bbb'), ('d', 2.2), ('e', 2.2)]) 25 | OrderedDict([('a', 3), ('b', 3.3), ('c', 'ccc'), ('d', 3), ('e', 'ccc')]) 26 | -------------------------------------------------------------------------------- /docs/pages/examples/update.rst: -------------------------------------------------------------------------------- 1 | Update a record in a table 2 | -------------------------- 3 | 4 | :py:meth:`~simplesqlite.SimpleSQLite.update` 5 | method can update record(s) in a table. 6 | 7 | :Sample Code: 8 | .. code-block:: python 9 | 10 | from simplesqlite import SimpleSQLite 11 | from simplesqlite.query import Where 12 | 13 | 14 | table_name = "sample_table" 15 | con = SimpleSQLite("sample.sqlite", "w") 16 | 17 | data_matrix = [ 18 | [1, "aaa"], 19 | [2, "bbb"], 20 | ] 21 | con.create_table_from_data_matrix( 22 | table_name, 23 | ["key", "value"], 24 | data_matrix) 25 | 26 | print("---- before update ----") 27 | for record in con.select(select="*", table_name=table_name).fetchall(): 28 | print(record) 29 | print() 30 | 31 | con.update(table_name, set_query="value = 'ccc'", where=Where(key="key", value=1)) 32 | 33 | print("---- after update ----") 34 | for record in con.select(select="*", table_name=table_name).fetchall(): 35 | print(record) 36 | 37 | :Output: 38 | .. code-block:: none 39 | 40 | ---- before update ---- 41 | (1, 'aaa') 42 | (2, 'bbb') 43 | 44 | ---- after update ---- 45 | (1, 'ccc') 46 | (2, 'bbb') 47 | -------------------------------------------------------------------------------- /docs/pages/genindex.rst: -------------------------------------------------------------------------------- 1 | Indices and tables 2 | ================== 3 | 4 | * :ref:`genindex` -------------------------------------------------------------------------------- /docs/pages/introduction/badges.txt: -------------------------------------------------------------------------------- 1 | .. image:: https://badge.fury.io/py/SimpleSQLite.svg 2 | :target: https://badge.fury.io/py/SimpleSQLite 3 | :alt: PyPI package version 4 | 5 | .. image:: https://img.shields.io/pypi/pyversions/SimpleSQLite.svg 6 | :target: https://pypi.org/project/SimpleSQLite 7 | :alt: Supported Python versions 8 | 9 | .. image:: https://img.shields.io/pypi/implementation/SimpleSQLite.svg 10 | :target: https://pypi.org/project/SimpleSQLite 11 | :alt: Supported Python implementations 12 | 13 | .. image:: https://github.com/thombashi/SimpleSQLite/actions/workflows/ci.yml/badge.svg 14 | :target: https://github.com/thombashi/SimpleSQLite/actions/workflows/ci.yml 15 | :alt: CI status of Linux/macOS/Windows 16 | 17 | .. image:: https://github.com/thombashi/SimpleSQLite/actions/workflows/github-code-scanning/codeql/badge.svg 18 | :target: https://github.com/thombashi/SimpleSQLite/actions/workflows/github-code-scanning/codeql 19 | :alt: CodeQL 20 | 21 | .. image:: https://coveralls.io/repos/github/thombashi/SimpleSQLite/badge.svg?branch=master 22 | :target: https://coveralls.io/github/thombashi/SimpleSQLite?branch=master 23 | :alt: Test coverage 24 | -------------------------------------------------------------------------------- /docs/pages/introduction/feature.txt: -------------------------------------------------------------------------------- 1 | Features 2 | -------- 3 | - Automated SQLite table creation from data 4 | - Support various data types of record(s) insertion into a table: 5 | - ``dict`` 6 | - ``namedtuple`` 7 | - ``list`` 8 | - ``tuple`` 9 | - Create table(s) from: 10 | - CSV file/text 11 | - JSON file/text 12 | - `pandas.DataFrame `__ instance 13 | - `tabledata.TableData `__ instance loaded by `pytablereader `__ 14 | - Get data from a table as: 15 | - `pandas.DataFrame `__ instance 16 | - `tabledata.TableData `__ instance 17 | - Simple object-relational mapping (ORM) functionality 18 | -------------------------------------------------------------------------------- /docs/pages/introduction/index.rst: -------------------------------------------------------------------------------- 1 | SimpleSQLite 2 | ============ 3 | 4 | .. include:: badges.txt 5 | 6 | 7 | Summary 8 | ------- 9 | 10 | .. include:: summary.txt 11 | 12 | .. raw:: html 13 | 14 |
15 | 16 |
17 |
18 | 19 | 20 | .. include:: feature.txt 21 | 22 | 23 | .. include:: installation.rst 24 | -------------------------------------------------------------------------------- /docs/pages/introduction/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | Install from PyPI 4 | ------------------------------ 5 | :: 6 | 7 | pip install SimpleSQLite 8 | 9 | Install from PPA (for Ubuntu) 10 | ------------------------------ 11 | :: 12 | 13 | sudo add-apt-repository ppa:thombashi/ppa 14 | sudo apt update 15 | sudo apt install python3-simplesqlite 16 | 17 | 18 | Dependencies 19 | ============ 20 | - Python 3.9+ 21 | - `Python package dependencies (automatically installed) `__ 22 | 23 | Optional Dependencies 24 | ---------------------------------- 25 | - `loguru `__ 26 | - Used for logging if the package installed 27 | - `pandas `__ 28 | - `pytablereader `__ 29 | -------------------------------------------------------------------------------- /docs/pages/introduction/summary.txt: -------------------------------------------------------------------------------- 1 | SimpleSQLite is a Python library to simplify SQLite database operations: table creation, data insertion and get data as other data formats. Simple ORM functionality for SQLite. 2 | -------------------------------------------------------------------------------- /docs/pages/links.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========== 3 | https://github.com/thombashi/SimpleSQLite/releases 4 | 5 | 6 | .. include:: sponsors.rst 7 | 8 | .. include:: genindex.rst 9 | 10 | 11 | Links 12 | ===== 13 | - `GitHub repository `__ 14 | - `Issue tracker `__ 15 | - `pip: A tool for installing python packages `__ 16 | 17 | Related project 18 | -------------------- 19 | - `sqlitebiter: A CLI tool to create a SQLite database from CSV/JSON/Excel/Google-Sheets by using SimpleSQLite `__ 20 | -------------------------------------------------------------------------------- /docs/pages/reference/error.rst: -------------------------------------------------------------------------------- 1 | Errors 2 | ---------------------------- 3 | 4 | .. autoexception:: simplesqlite.DatabaseError 5 | :show-inheritance: 6 | 7 | .. autoexception:: simplesqlite.NullDatabaseConnectionError 8 | :show-inheritance: 9 | 10 | .. autoexception:: simplesqlite.TableNotFoundError 11 | :show-inheritance: 12 | 13 | .. autoexception:: simplesqlite.AttributeNotFoundError 14 | :show-inheritance: 15 | 16 | .. autoexception:: simplesqlite.SqlSyntaxError 17 | :show-inheritance: 18 | 19 | .. autoexception:: simplesqlite.OperationalError 20 | :show-inheritance: 21 | -------------------------------------------------------------------------------- /docs/pages/reference/function.rst: -------------------------------------------------------------------------------- 1 | Functions 2 | ---------------------------- 3 | 4 | .. autofunction:: simplesqlite.append_table 5 | 6 | .. autofunction:: simplesqlite.copy_table 7 | 8 | .. autofunction:: simplesqlite.connect_memdb 9 | -------------------------------------------------------------------------------- /docs/pages/reference/index.rst: -------------------------------------------------------------------------------- 1 | Reference 2 | ========= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | simplesqlite 8 | query 9 | function 10 | error 11 | -------------------------------------------------------------------------------- /docs/pages/reference/query.rst: -------------------------------------------------------------------------------- 1 | Query classes 2 | -------------- 3 | 4 | .. autoclass:: simplesqlite.query.Table 5 | :members: 6 | :undoc-members: 7 | 8 | .. autoclass:: simplesqlite.query.Attr 9 | :members: 10 | :undoc-members: 11 | 12 | .. autoclass:: simplesqlite.query.AttrList 13 | :members: 14 | :undoc-members: 15 | 16 | .. autoclass:: simplesqlite.query.Value 17 | :members: 18 | :undoc-members: 19 | 20 | .. autoclass:: simplesqlite.query.Where 21 | :members: 22 | :undoc-members: 23 | 24 | .. autoclass:: simplesqlite.query.Select 25 | :members: 26 | :undoc-members: 27 | 28 | .. autoclass:: simplesqlite.query.And 29 | :members: 30 | :undoc-members: 31 | 32 | .. autoclass:: simplesqlite.query.Or 33 | :members: 34 | :undoc-members: 35 | -------------------------------------------------------------------------------- /docs/pages/reference/simplesqlite.rst: -------------------------------------------------------------------------------- 1 | SimpleSQLite class 2 | ------------------ 3 | 4 | .. autoclass:: simplesqlite.SimpleSQLite 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/pages/sponsors.rst: -------------------------------------------------------------------------------- 1 | Sponsors 2 | ==================================== 3 | |chasbecker| |shiguredo| |b4tman| |Arturi0| |github| 4 | 5 | .. |chasbecker| image:: https://avatars.githubusercontent.com/u/44389260?s=48&u=6da7176e51ae2654bcfd22564772ef8a3bb22318&v=4 6 | :target: https://github.com/chasbecker 7 | :alt: ex-sponsor: Charles Becker (chasbecker) 8 | .. |shiguredo| image:: https://avatars.githubusercontent.com/u/2549434?s=48&v=4 9 | :target: https://github.com/shiguredo 10 | :alt: ex-sponsor: 時雨堂 (shiguredo) 11 | .. |b4tman| image:: https://avatars.githubusercontent.com/u/3658062?s=48&v=4 12 | :target: https://github.com/b4tman 13 | :alt: onetime: Dmitry Belyaev (b4tman) 14 | .. |Arturi0| image:: https://avatars.githubusercontent.com/u/46711571?s=48&u=57687c0e02d5d6e8eeaf9177f7b7af4c9f275eb5&v=4 15 | :target: https://github.com/Arturi0 16 | :alt: onetime: Arturi0 17 | .. |github| image:: https://avatars.githubusercontent.com/u/9919?s=48&v=4 18 | :target: https://github.com/github 19 | :alt: onetime: GitHub (github) 20 | 21 | `Become a sponsor `__ 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = ["setuptools>=61.0"] 4 | 5 | [tool.black] 6 | exclude = ''' 7 | /( 8 | \.eggs 9 | | \.git 10 | | \.mypy_cache 11 | | \.tox 12 | | \.venv 13 | | \.pytype 14 | | _build 15 | | buck-out 16 | | build 17 | | dist 18 | )/ 19 | | docs/conf.py 20 | ''' 21 | line-length = 100 22 | target-version = ['py39', 'py310', 'py311', 'py312'] 23 | 24 | [tool.isort] 25 | include_trailing_comma = true 26 | known_third_party = [ 27 | 'dataproperty', 28 | 'logbook', 29 | 'mbstrdecoder', 30 | 'pandas', 31 | 'pathvalidate', 32 | 'pytablereader', 33 | 'pytest', 34 | 'sphinx_rtd_theme', 35 | 'sqliteschema', 36 | 'tabledata', 37 | 'typepy', 38 | 'xlsxwriter', 39 | ] 40 | line_length = 100 41 | lines_after_imports = 2 42 | multi_line_output = 3 43 | skip_glob = [ 44 | '*/.eggs/*', 45 | '*/.pytype/*', 46 | '*/.tox/*', 47 | ] 48 | 49 | [tool.coverage.run] 50 | branch = true 51 | source = ['simplesqlite'] 52 | 53 | [tool.coverage.report] 54 | exclude_lines = [ 55 | 'except ImportError', 56 | 'raise NotImplementedError', 57 | 'pass', 58 | 'ABCmeta', 59 | 'abstractmethod', 60 | 'warnings.warn', 61 | ] 62 | precision = 1 63 | show_missing = true 64 | 65 | [tool.mypy] 66 | ignore_missing_imports = true 67 | python_version = 3.9 68 | 69 | pretty = true 70 | 71 | check_untyped_defs = true 72 | disallow_incomplete_defs = true 73 | disallow_untyped_defs = true 74 | no_implicit_optional = true 75 | show_error_codes = true 76 | show_error_context = true 77 | warn_redundant_casts = true 78 | warn_unreachable = true 79 | warn_unused_configs = true 80 | warn_unused_ignores = true 81 | 82 | [tool.pyright] 83 | exclude = [ 84 | "**/node_modules", 85 | "**/__pycache__", 86 | ".tox", 87 | ".venv", 88 | "_build", 89 | "_sandbox", 90 | "build", 91 | "dist" 92 | ] 93 | pythonVersion = "3.9" 94 | 95 | [tool.pytest.ini_options] 96 | testpaths = ["test"] 97 | 98 | md_report = true 99 | md_report_color = "auto" 100 | md_report_verbose = 0 101 | md_report_zeros = "number" 102 | 103 | [tool.ruff] 104 | line-length = 100 105 | target-version = "py39" 106 | exclude = [ 107 | ".eggs/", 108 | ".tox/", 109 | "_sandbox/*", 110 | "build/", 111 | "docs/conf.py", 112 | ] 113 | -------------------------------------------------------------------------------- /requirements/docs_requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx_rtd_theme>=1.2.2 2 | Sphinx>=2.4 3 | -------------------------------------------------------------------------------- /requirements/requirements.txt: -------------------------------------------------------------------------------- 1 | DataProperty>=1.0.2,<2 2 | mbstrdecoder>=1.0.0,<2 3 | pathvalidate>=2.5.2,<4 4 | sqliteschema>=1.4.0,<3 5 | tabledata>=1.1.3,<2 6 | typepy>=1.2.0,<2 7 | -------------------------------------------------------------------------------- /requirements/test_requirements.txt: -------------------------------------------------------------------------------- 1 | pytablereader>=0.31.3 2 | pytablewriter>=0.50 3 | pytest>=6.0.1 4 | # pytest-discord>=0.1.6 5 | pytest-md-report>=0.6.2 6 | beautifulsoup4>=4.10 7 | -------------------------------------------------------------------------------- /sample/create_table/sample_create_table_from_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | with open("sample_data.csv", "w") as f: 8 | f.write("\n".join(['"attr_a","attr_b","attr_c"', '1,4,"a"', '2,2.1,"bb"', '3,120.9,"ccc"'])) 9 | 10 | # create table --- 11 | con = SimpleSQLite("sample.sqlite", "w") 12 | con.create_table_from_csv("sample_data.csv") 13 | 14 | # output --- 15 | table_name = "sample_data" 16 | print(con.fetch_attr_names(table_name)) 17 | result = con.select(select="*", table_name=table_name) 18 | assert result 19 | for record in result.fetchall(): 20 | print(record) 21 | 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /sample/create_table/sample_create_table_from_data_matrix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | table_name = "sample_table" 8 | con = SimpleSQLite("sample.sqlite", "w") 9 | 10 | # create table ----- 11 | data_matrix = [[1, 1.1, "aaa", 1, 1], [2, 2.2, "bbb", 2.2, 2.2], [3, 3.3, "ccc", 3, "ccc"]] 12 | con.create_table_from_data_matrix( 13 | table_name, ["attr_a", "attr_b", "attr_c", "attr_d", "attr_e"], data_matrix 14 | ) 15 | 16 | # display data type for each column in the table ----- 17 | print(con.schema_extractor.fetch_table_schema(table_name).dumps()) 18 | 19 | # display values in the table ----- 20 | print("records:") 21 | result = con.select(select="*", table_name=table_name) 22 | assert result 23 | for record in result.fetchall(): 24 | print(record) 25 | 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /sample/create_table/sample_create_table_from_dataframe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pandas 4 | 5 | from simplesqlite import SimpleSQLite 6 | 7 | 8 | def main() -> None: 9 | con = SimpleSQLite("pandas_df.sqlite") 10 | 11 | con.create_table_from_dataframe( 12 | pandas.DataFrame( 13 | [ 14 | [0, 0.1, "a"], 15 | [1, 1.1, "bb"], 16 | [2, 2.2, "ccc"], 17 | ], 18 | columns=pandas.Index(["id", "value", "name"]), 19 | ), 20 | table_name="pandas_df", 21 | ) 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /sample/create_table/sample_create_table_from_excel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from typing import Any 4 | 5 | import pytablereader 6 | import xlsxwriter 7 | 8 | import simplesqlite 9 | 10 | 11 | def main() -> None: 12 | file_path = "sample_data.xlsx" 13 | 14 | # create sample data file --- 15 | workbook = xlsxwriter.Workbook(file_path) 16 | 17 | worksheet = workbook.add_worksheet("samplesheet1") 18 | table: list[list[Any]] = [ 19 | ["", "", "", ""], 20 | ["", "a", "b", "c"], 21 | ["", 1, 1.1, "a"], 22 | ["", 2, 2.2, "bb"], 23 | ["", 3, 3.3, "cc"], 24 | ] 25 | for row_idx, row in enumerate(table): 26 | for col_idx, item in enumerate(row): 27 | worksheet.write(row_idx, col_idx, item) 28 | 29 | worksheet = workbook.add_worksheet("samplesheet2") 30 | 31 | worksheet = workbook.add_worksheet("samplesheet3") 32 | table = [ 33 | ["", "", ""], 34 | ["", "", ""], 35 | ["aa", "ab", "ac"], 36 | [1, "hoge", "a"], 37 | [2, "", "bb"], 38 | [3, "foo", ""], 39 | ] 40 | for row_idx, row in enumerate(table): 41 | for col_idx, item in enumerate(row): 42 | worksheet.write(row_idx, col_idx, item) 43 | 44 | workbook.close() 45 | 46 | # create table --- 47 | con = simplesqlite.SimpleSQLite("sample.sqlite", "w") 48 | 49 | loader = pytablereader.ExcelTableFileLoader(file_path) 50 | for table_data in loader.load(): 51 | con.create_table_from_tabledata(table_data) 52 | 53 | # output --- 54 | for table_name in con.fetch_table_names(): 55 | print("table: " + table_name) 56 | print(con.fetch_attr_names(table_name)) 57 | result = con.select(select="*", table_name=table_name) 58 | assert result 59 | for record in result.fetchall(): 60 | print(record) 61 | print() 62 | 63 | 64 | if __name__ == "__main__": 65 | main() 66 | -------------------------------------------------------------------------------- /sample/create_table/sample_create_table_from_gs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pytablereader as ptr 4 | 5 | import simplesqlite 6 | 7 | 8 | def main() -> None: 9 | credentials_file = "sample-xxxxxxxxxxxx.json" 10 | 11 | # create table --- 12 | con = simplesqlite.SimpleSQLite("sample.sqlite", "w") 13 | 14 | loader = ptr.GoogleSheetsTableLoader(credentials_file) 15 | loader.title = "samplebook" # type: ignore 16 | 17 | for table_data in loader.load(): 18 | con.create_table_from_tabledata(table_data) 19 | 20 | # output --- 21 | for table_name in con.fetch_table_names(): 22 | print("table: " + table_name) 23 | print(con.fetch_attr_names(table_name)) 24 | result = con.select(select="*", table_name=table_name) 25 | assert result 26 | for record in result.fetchall(): 27 | print(record) 28 | print() 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /sample/create_table/sample_create_table_from_json_multi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | file_path = "sample_data_multi.json" 8 | 9 | # create sample data file --- 10 | with open(file_path, "w") as f: 11 | f.write( 12 | """{ 13 | "table_a" : [ 14 | {"attr_b": 4, "attr_c": "a", "attr_a": 1}, 15 | {"attr_b": 2.1, "attr_c": "bb", "attr_a": 2}, 16 | {"attr_b": 120.9, "attr_c": "ccc", "attr_a": 3} 17 | ], 18 | "table_b" : [ 19 | {"a": 1, "b": 4}, 20 | {"a": 2 }, 21 | {"a": 3, "b": 120.9} 22 | ] 23 | }""" 24 | ) 25 | 26 | # create table --- 27 | con = SimpleSQLite("sample.sqlite", "w") 28 | con.create_table_from_json(file_path) 29 | 30 | # output --- 31 | for table_name in con.fetch_table_names(): 32 | print("table: " + table_name) 33 | print(con.fetch_attr_names(table_name)) 34 | result = con.select(select="*", table_name=table_name) 35 | assert result 36 | for record in result.fetchall(): 37 | print(record) 38 | print() 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /sample/create_table/sample_create_table_from_json_single.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | file_path = "sample_data_single.json" 8 | 9 | # create sample data file --- 10 | with open(file_path, "w") as f: 11 | f.write( 12 | """[ 13 | {"attr_b": 4, "attr_c": "a", "attr_a": 1}, 14 | {"attr_b": 2.1, "attr_c": "bb", "attr_a": 2}, 15 | {"attr_b": 120.9, "attr_c": "ccc", "attr_a": 3} 16 | ]""" 17 | ) 18 | 19 | # create table --- 20 | con = SimpleSQLite("sample.sqlite", "w") 21 | con.create_table_from_json(file_path) 22 | 23 | # output --- 24 | for table_name in con.fetch_table_names(): 25 | print("table: " + table_name) 26 | print(con.fetch_attr_names(table_name)) 27 | result = con.select(select="*", table_name=table_name) 28 | assert result 29 | for record in result.fetchall(): 30 | print(record) 31 | print() 32 | 33 | 34 | if __name__ == "__main__": 35 | main() 36 | -------------------------------------------------------------------------------- /sample/create_table/sample_create_table_in_mem_db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import simplesqlite 4 | 5 | 6 | def main() -> None: 7 | table_name = "sample_table" 8 | con = simplesqlite.connect_memdb() 9 | 10 | # create table ----- 11 | data_matrix = [[1, 1.1, "aaa", 1, 1], [2, 2.2, "bbb", 2.2, 2.2], [3, 3.3, "ccc", 3, "ccc"]] 12 | con.create_table_from_data_matrix( 13 | table_name, ["attr_a", "attr_b", "attr_c", "attr_d", "attr_e"], data_matrix 14 | ) 15 | 16 | # display data type for each column in the table ----- 17 | print(con.schema_extractor.fetch_table_schema(table_name).dumps()) 18 | 19 | # display values in the table ----- 20 | print("records:") 21 | result = con.select(select="*", table_name=table_name) 22 | assert result 23 | for record in result.fetchall(): 24 | print(record) 25 | 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /sample/orm/orm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | .. codeauthor:: Tsuyoshi Hombashi 5 | """ 6 | 7 | from simplesqlite import connect_memdb 8 | from simplesqlite.model import Blob, Integer, Model, Real, Text 9 | from simplesqlite.query import Set, Where 10 | 11 | 12 | def print_all_records(model: type[Model]) -> None: 13 | for record in model.select(): 14 | print(record) 15 | 16 | 17 | class Hoge(Model): 18 | hoge_id = Integer() 19 | name = Text() 20 | 21 | 22 | class Foo(Model): 23 | foo_id = Integer(primary_key=True) 24 | name = Text(not_null=True) 25 | value = Real(not_null=True) 26 | blob = Blob() 27 | nullable = Text() 28 | created_at = Text(default="CURRENT_TIMESTAMP") 29 | 30 | 31 | def main() -> None: 32 | con = connect_memdb() 33 | 34 | Hoge.attach(con, is_hidden=True) 35 | Hoge.create() 36 | Hoge.insert(Hoge(hoge_id=10, name="a")) 37 | Hoge.insert(Hoge(hoge_id=20, name="b")) 38 | 39 | Foo.attach(con) 40 | Foo.create() 41 | Foo.insert(Foo(name="aq", value=0.1)) 42 | Foo.insert(Foo(name="cc", value=2.2, nullable=None)) 43 | Foo.insert(Foo(name="dd", value=3.3, nullable="hoge")) 44 | 45 | record = Foo(name="bb") 46 | record.value = 1.1 # type: ignore 47 | Foo.insert(record) 48 | 49 | print(Hoge.fetch_schema().dumps()) 50 | table_name = Hoge.get_table_name() 51 | print(f"\nSELECT all the records: table={table_name}") 52 | for hoge in Hoge.select(): 53 | print(hoge.hoge_id, hoge.name) 54 | 55 | print(f"\nSELECT with WHERE: table={table_name}") 56 | for hoge in Hoge.select(where=Where(Hoge.hoge_id, 10)): 57 | print(hoge.hoge_id, hoge.name) 58 | 59 | print("\n--------------------\n") 60 | print(Foo.fetch_schema().dumps()) 61 | table_name = Foo.get_table_name() 62 | 63 | print(f"\nSELECT all the records: table={table_name}") 64 | print_all_records(Foo) 65 | 66 | print(f"\nDELETE: table={table_name}") 67 | Foo.delete(where=Where(Foo.foo_id, 22)) 68 | print_all_records(Foo) 69 | 70 | print(f"\nUPDATE: table={table_name}") 71 | Foo.update(set_query=[Set(Foo.value, 1000)], where=Where(Foo.foo_id, 33)) 72 | print_all_records(Foo) 73 | 74 | 75 | if __name__ == "__main__": 76 | main() 77 | -------------------------------------------------------------------------------- /sample/orm/simple_orm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | .. codeauthor:: Tsuyoshi Hombashi 5 | """ 6 | 7 | from simplesqlite import connect_memdb 8 | from simplesqlite.model import Integer, Model, Real, Text 9 | 10 | 11 | class Sample(Model): 12 | foo_id = Integer(primary_key=True) 13 | name = Text(not_null=True, unique=True) 14 | value = Real(default=0) 15 | 16 | 17 | def main() -> None: 18 | con = connect_memdb() 19 | 20 | Sample.attach(con) 21 | Sample.create() 22 | Sample.insert(Sample(name="abc", value=0.1)) 23 | Sample.insert(Sample(name="xyz", value=1.11)) 24 | Sample.insert(Sample(name="bar")) 25 | 26 | print(Sample.fetch_schema().dumps()) 27 | print("records:") 28 | for record in Sample.select(): 29 | print(f" {record}") 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /sample/sample_check_connection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import simplesqlite 4 | 5 | 6 | def main() -> None: 7 | con = simplesqlite.SimpleSQLite("sample.sqlite", "w") 8 | 9 | print("---- connected to a database ----") 10 | con.check_connection() 11 | 12 | print("---- disconnected from a database ----") 13 | con.close() 14 | try: 15 | con.check_connection() 16 | except simplesqlite.NullDatabaseConnectionError as e: 17 | print(e) 18 | 19 | 20 | if __name__ == "__main__": 21 | main() 22 | -------------------------------------------------------------------------------- /sample/sample_get_attribute_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import simplesqlite 4 | 5 | 6 | def main() -> None: 7 | table_name = "sample_table" 8 | con = simplesqlite.SimpleSQLite("sample.sqlite", "w") 9 | con.create_table_from_data_matrix(table_name, ["attr_a", "attr_b"], [[1, "a"], [2, "b"]]) 10 | 11 | print(con.fetch_attr_names(table_name)) 12 | 13 | try: 14 | print(con.fetch_attr_names("not_existing")) 15 | except simplesqlite.DatabaseError as e: 16 | print(e) 17 | 18 | 19 | if __name__ == "__main__": 20 | main() 21 | -------------------------------------------------------------------------------- /sample/sample_get_profile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | con = SimpleSQLite("sample.sqlite", "w", profile=True) 8 | data_matrix = [[1, 1.1, "aaa", 1, 1], [2, 2.2, "bbb", 2.2, 2.2], [3, 3.3, "ccc", 3, "ccc"]] 9 | con.create_table_from_data_matrix( 10 | "sample_table", ["a", "b", "c", "d", "e"], data_matrix, index_attrs=["a"] 11 | ) 12 | 13 | for profile in con.get_profile(): 14 | print(profile) 15 | 16 | 17 | if __name__ == "__main__": 18 | main() 19 | -------------------------------------------------------------------------------- /sample/sample_get_sqlite_master.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | 5 | from simplesqlite import SimpleSQLite 6 | 7 | 8 | def main() -> None: 9 | con = SimpleSQLite("sample.sqlite", "w") 10 | data_matrix = [[1, 1.1, "aaa", 1, 1], [2, 2.2, "bbb", 2.2, 2.2], [3, 3.3, "ccc", 3, "ccc"]] 11 | con.create_table_from_data_matrix( 12 | "sample_table", ["a", "b", "c", "d", "e"], data_matrix, index_attrs=["a"] 13 | ) 14 | 15 | print(json.dumps(con.fetch_sqlite_master(), indent=4)) 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /sample/sample_get_table_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | con = SimpleSQLite("sample.sqlite", "w") 8 | con.create_table_from_data_matrix("hoge", ["attr_a", "attr_b"], [[1, "a"], [2, "b"]]) 9 | print(con.fetch_table_names()) 10 | 11 | 12 | if __name__ == "__main__": 13 | main() 14 | -------------------------------------------------------------------------------- /sample/sample_has_attribute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import simplesqlite 4 | 5 | 6 | def main() -> None: 7 | table_name = "sample_table" 8 | con = simplesqlite.SimpleSQLite("sample.sqlite", "w") 9 | con.create_table_from_data_matrix(table_name, ["attr_a", "attr_b"], [[1, "a"], [2, "b"]]) 10 | 11 | print(con.has_attr(table_name, "attr_a")) 12 | print(con.has_attr(table_name, "not_existing")) 13 | try: 14 | print(con.has_attr("not_existing_table", "attr_a")) 15 | except simplesqlite.DatabaseError as e: 16 | print(e) 17 | 18 | 19 | if __name__ == "__main__": 20 | main() 21 | -------------------------------------------------------------------------------- /sample/sample_has_attrs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import simplesqlite 4 | 5 | 6 | def main() -> None: 7 | table_name = "sample_table" 8 | con = simplesqlite.SimpleSQLite("sample.sqlite", "w") 9 | con.create_table_from_data_matrix(table_name, ["attr_a", "attr_b"], [[1, "a"], [2, "b"]]) 10 | 11 | print(con.has_attrs(table_name, ["attr_a"])) 12 | print(con.has_attrs(table_name, ["attr_a", "attr_b"])) 13 | print(con.has_attrs(table_name, ["attr_a", "attr_b", "not_existing"])) 14 | try: 15 | print(con.has_attrs("not_existing_table", ["attr_a"])) 16 | except simplesqlite.DatabaseError as e: 17 | print(e) 18 | 19 | 20 | if __name__ == "__main__": 21 | main() 22 | -------------------------------------------------------------------------------- /sample/sample_has_table.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | con = SimpleSQLite("sample.sqlite", "w") 8 | con.create_table_from_data_matrix("hoge", ["attr_a", "attr_b"], [[1, "a"], [2, "b"]]) 9 | 10 | print(con.has_table("hoge")) 11 | print(con.has_table("not_existing")) 12 | 13 | 14 | if __name__ == "__main__": 15 | main() 16 | -------------------------------------------------------------------------------- /sample/sample_insert_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | table_name = "sample_table" 8 | con = SimpleSQLite("sample.sqlite", "w") 9 | con.create_table_from_data_matrix( 10 | table_name, ["attr_a", "attr_b", "attr_c", "attr_d", "attr_e"], [[1, 1.1, "aaa", 1, 1]] 11 | ) 12 | 13 | con.insert( 14 | table_name, 15 | record={"attr_a": 4, "attr_b": 4.4, "attr_c": "ddd", "attr_d": 4.44, "attr_e": "hoge"}, 16 | ) 17 | con.insert_many( 18 | table_name, 19 | records=[ 20 | {"attr_a": 5, "attr_b": 5.5, "attr_c": "eee", "attr_d": 5.55, "attr_e": "foo"}, 21 | {"attr_a": 6, "attr_c": "fff"}, 22 | ], 23 | ) 24 | 25 | result = con.select(select="*", table_name=table_name) 26 | assert result 27 | for record in result.fetchall(): 28 | print(record) 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /sample/sample_insert_mix_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from collections import namedtuple 4 | 5 | from simplesqlite import SimpleSQLite 6 | 7 | 8 | def main() -> None: 9 | table_name = "sample_table" 10 | con = SimpleSQLite("sample.sqlite", "w") 11 | con.create_table_from_data_matrix( 12 | table_name, ["attr_a", "attr_b", "attr_c", "attr_d", "attr_e"], [[1, 1.1, "aaa", 1, 1]] 13 | ) 14 | 15 | # insert namedtuple 16 | SampleTuple = namedtuple("SampleTuple", "attr_a attr_b attr_c attr_d attr_e") 17 | 18 | con.insert(table_name, record=[7, 7.7, "fff", 7.77, "bar"]) 19 | con.insert_many( 20 | table_name, 21 | records=[(8, 8.8, "ggg", 8.88, "foobar"), SampleTuple(9, 9.9, "ggg", 9.99, "hogehoge")], 22 | ) 23 | 24 | # run select clause 25 | result = con.select(select="*", table_name=table_name) 26 | assert result 27 | for record in result.fetchall(): 28 | print(record) 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /sample/sample_update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | from simplesqlite.query import Where 5 | 6 | 7 | def main() -> None: 8 | table_name = "sample_table" 9 | con = SimpleSQLite("sample.sqlite", "w") 10 | 11 | data_matrix = [[1, "aaa"], [2, "bbb"]] 12 | con.create_table_from_data_matrix(table_name, ["key", "value"], data_matrix) 13 | 14 | print("---- before update ----") 15 | result = con.select(select="*", table_name=table_name) 16 | assert result 17 | for record in result.fetchall(): 18 | print(record) 19 | print() 20 | 21 | con.update(table_name, set_query="value = 'ccc'", where=Where(key="key", value=1)) 22 | 23 | print("---- after update ----") 24 | result = con.select(select="*", table_name=table_name) 25 | assert result 26 | for record in result.fetchall(): 27 | print(record) 28 | 29 | 30 | if __name__ == "__main__": 31 | main() 32 | -------------------------------------------------------------------------------- /sample/sample_verify_attribute_existence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import AttributeNotFoundError, DatabaseError, SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | table_name = "sample_table" 8 | con = SimpleSQLite("sample.sqlite", "w") 9 | con.create_table_from_data_matrix(table_name, ["attr_a", "attr_b"], [[1, "a"], [2, "b"]]) 10 | 11 | con.verify_attr_existence(table_name, "attr_a") 12 | try: 13 | con.verify_attr_existence(table_name, "not_existing") 14 | except AttributeNotFoundError as e: 15 | print(e) 16 | try: 17 | con.verify_attr_existence("not_existing", "attr_a") 18 | except DatabaseError as e: 19 | print(e) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /sample/sample_verify_table_existence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import simplesqlite 4 | 5 | 6 | def main() -> None: 7 | table_name = "sample_table" 8 | con = simplesqlite.SimpleSQLite("sample.sqlite", "w") 9 | con.create_table_from_data_matrix(table_name, ["attr_a", "attr_b"], [[1, "a"], [2, "b"]]) 10 | 11 | con.verify_table_existence(table_name) 12 | try: 13 | con.verify_table_existence("not_existing") 14 | except simplesqlite.DatabaseError as e: 15 | print(e) 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /sample/select/select_as_dataframe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | con = SimpleSQLite("sample.sqlite", "w", profile=True) 8 | 9 | con.create_table_from_data_matrix( 10 | "sample_table", 11 | ["a", "b", "c", "d", "e"], 12 | [[1, 1.1, "aaa", 1, 1], [2, 2.2, "bbb", 2.2, 2.2], [3, 3.3, "ccc", 3, "ccc"]], 13 | ) 14 | 15 | print(con.select_as_dataframe(table_name="sample_table")) 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /sample/select/select_as_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from simplesqlite import SimpleSQLite 4 | 5 | 6 | def main() -> None: 7 | con = SimpleSQLite("sample.sqlite", "w", profile=True) 8 | 9 | con.create_table_from_data_matrix( 10 | "sample_table", 11 | ["a", "b", "c", "d", "e"], 12 | [[1, 1.1, "aaa", 1, 1], [2, 2.2, "bbb", 2.2, 2.2], [3, 3.3, "ccc", 3, "ccc"]], 13 | ) 14 | 15 | result = con.select_as_dict(table_name="sample_table") 16 | assert result 17 | for record in result: 18 | print(record) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import os.path 6 | from typing import Final 7 | 8 | import setuptools 9 | 10 | 11 | MODULE_NAME: Final = "SimpleSQLite" 12 | REPOSITORY_URL: Final = f"https://github.com/thombashi/{MODULE_NAME:s}" 13 | REQUIREMENT_DIR: Final = "requirements" 14 | ENCODING: Final = "utf8" 15 | 16 | pkg_info: dict[str, str] = {} 17 | 18 | 19 | def get_release_command_class() -> dict[str, type[setuptools.Command]]: 20 | try: 21 | from releasecmd import ReleaseCommand 22 | except ImportError: 23 | return {} 24 | 25 | return {"release": ReleaseCommand} 26 | 27 | 28 | with open(os.path.join(MODULE_NAME.lower(), "__version__.py")) as f: 29 | exec(f.read(), pkg_info) 30 | 31 | with open("README.rst", encoding=ENCODING) as fp: 32 | long_description = fp.read() 33 | 34 | with open(os.path.join("docs", "pages", "introduction", "summary.txt"), encoding=ENCODING) as f: 35 | summary = f.read().strip() 36 | 37 | with open(os.path.join(REQUIREMENT_DIR, "requirements.txt")) as f: 38 | install_requires = [line.strip() for line in f if line.strip()] 39 | 40 | with open(os.path.join(REQUIREMENT_DIR, "test_requirements.txt")) as f: 41 | tests_requires = [line.strip() for line in f if line.strip()] 42 | 43 | with open(os.path.join(REQUIREMENT_DIR, "docs_requirements.txt")) as f: 44 | docs_requires = [line.strip() for line in f if line.strip()] 45 | 46 | setuptools.setup( 47 | name=MODULE_NAME, 48 | version=pkg_info["__version__"], 49 | url=REPOSITORY_URL, 50 | author=pkg_info["__author__"], 51 | author_email=pkg_info["__email__"], 52 | description=summary, 53 | include_package_data=True, 54 | keywords=["SQLite", "CSV", "Google Sheets", "JSON"], 55 | license=pkg_info["__license__"], 56 | long_description=long_description, 57 | long_description_content_type="text/x-rst", 58 | packages=setuptools.find_packages(exclude=["test*"]), 59 | package_data={MODULE_NAME: ["py.typed"]}, 60 | project_urls={ 61 | "Changelog": f"{REPOSITORY_URL:s}/releases", 62 | "Documentation": f"https://{MODULE_NAME:s}.rtfd.io/", 63 | "Source": REPOSITORY_URL, 64 | "Tracker": f"{REPOSITORY_URL:s}/issues", 65 | }, 66 | python_requires=">=3.9", 67 | install_requires=install_requires, 68 | extras_require={ 69 | "docs": docs_requires, 70 | "logging": ["loguru>=0.4.1,<1"], 71 | "test": tests_requires, 72 | }, 73 | classifiers=[ 74 | "Development Status :: 5 - Production/Stable", 75 | "Intended Audience :: Developers", 76 | "License :: OSI Approved :: MIT License", 77 | "Operating System :: Microsoft :: Windows", 78 | "Operating System :: POSIX", 79 | "Operating System :: POSIX :: Linux", 80 | "Programming Language :: Python :: 3", 81 | "Programming Language :: Python :: 3.9", 82 | "Programming Language :: Python :: 3.10", 83 | "Programming Language :: Python :: 3.11", 84 | "Programming Language :: Python :: 3.12", 85 | "Programming Language :: Python :: 3.13", 86 | "Programming Language :: Python :: 3 :: Only", 87 | "Programming Language :: Python :: Implementation :: CPython", 88 | "Programming Language :: Python :: Implementation :: PyPy", 89 | "Topic :: Database", 90 | "Topic :: Software Development :: Libraries", 91 | "Topic :: Software Development :: Libraries :: Python Modules", 92 | "Typing :: Typed", 93 | ], 94 | cmdclass=get_release_command_class(), 95 | zip_safe=False, 96 | ) 97 | -------------------------------------------------------------------------------- /simplesqlite/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import simplesqlite.query # noqa: F401 6 | 7 | from .__version__ import __author__, __copyright__, __email__, __license__, __version__ 8 | from ._func import append_table, copy_table 9 | from ._logger import set_log_level, set_logger 10 | from ._sanitizer import SQLiteTableDataSanitizer 11 | from .core import SQLITE_SYSTEM_TABLES, SimpleSQLite, connect_memdb 12 | from .error import ( 13 | AttributeNotFoundError, 14 | DatabaseError, 15 | NameValidationError, 16 | NullDatabaseConnectionError, 17 | OperationalError, 18 | SqlSyntaxError, 19 | TableNotFoundError, 20 | ) 21 | 22 | 23 | __all__ = ( 24 | "__author__", 25 | "__copyright__", 26 | "__email__", 27 | "__license__", 28 | "__version__", 29 | "AttributeNotFoundError", 30 | "DatabaseError", 31 | "NameValidationError", 32 | "NullDatabaseConnectionError", 33 | "OperationalError", 34 | "SqlSyntaxError", 35 | "TableNotFoundError", 36 | "SimpleSQLite", 37 | "SQLITE_SYSTEM_TABLES", 38 | "SQLiteTableDataSanitizer", 39 | "append_table", 40 | "connect_memdb", 41 | "copy_table", 42 | "query", 43 | "set_log_level", 44 | "set_logger", 45 | ) 46 | -------------------------------------------------------------------------------- /simplesqlite/__version__.py: -------------------------------------------------------------------------------- 1 | from typing import Final 2 | 3 | 4 | __author__: Final = "Tsuyoshi Hombashi" 5 | __copyright__: Final = f"Copyright 2016-2025, {__author__}" 6 | __license__: Final = "MIT License" 7 | __version__ = "1.5.4" 8 | __maintainer__: Final = __author__ 9 | __email__: Final = "tsuyoshi.hombashi@gmail.com" 10 | -------------------------------------------------------------------------------- /simplesqlite/_column.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import warnings 3 | from typing import Any, Optional 4 | 5 | from typepy.type import AbstractType 6 | 7 | 8 | class Column(metaclass=abc.ABCMeta): 9 | @property 10 | @abc.abstractmethod 11 | def sqlite_datatype(self) -> str: 12 | return "" 13 | 14 | @property 15 | @abc.abstractmethod 16 | def typepy_class(self) -> type[AbstractType]: 17 | raise NotImplementedError 18 | 19 | @property 20 | def not_null(self) -> bool: 21 | return self.__not_null 22 | 23 | def __init__( 24 | self, 25 | attr_name: Optional[str] = None, 26 | not_null: bool = False, 27 | primary_key: bool = False, 28 | unique: bool = False, 29 | autoincrement: bool = False, 30 | default: Any = None, 31 | ) -> None: 32 | self.__column_name = attr_name 33 | self.__not_null = not_null 34 | self.__primary_key = primary_key 35 | self.__unique = unique 36 | self.__autoincrement = autoincrement 37 | self.__default_value = None if self.__not_null else default 38 | 39 | def get_header(self) -> str: 40 | warnings.warn( 41 | "get_header() is deprecated. Use get_column_name() instead.", DeprecationWarning 42 | ) 43 | assert self.__column_name 44 | return self.__column_name 45 | 46 | def get_column_name(self) -> str: 47 | assert self.__column_name 48 | return self.__column_name 49 | 50 | def _set_column_name_if_uninitialized(self, header_name: str) -> None: 51 | self.__column_name = header_name 52 | 53 | def get_desc(self) -> str: 54 | from .query import Value 55 | 56 | constraints = [self.sqlite_datatype] 57 | 58 | if self.__primary_key: 59 | constraints.append("PRIMARY KEY") 60 | else: 61 | if self.__not_null: 62 | constraints.append("NOT NULL") 63 | if self.__unique: 64 | constraints.append("UNIQUE") 65 | 66 | if self.__autoincrement and self.sqlite_datatype == "INTEGER": 67 | constraints.append("AUTOINCREMENT") 68 | 69 | if self.__default_value is not None: 70 | constraints.append(f"DEFAULT {Value(self.__default_value)}") 71 | 72 | return " ".join(constraints) 73 | -------------------------------------------------------------------------------- /simplesqlite/_common.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from typing import TYPE_CHECKING, Final, Optional 3 | 4 | from dataproperty.typing import TypeHint 5 | from sqliteschema import SchemaHeader 6 | from typepy import Integer, RealNumber, String 7 | 8 | 9 | if TYPE_CHECKING: 10 | from simplesqlite import SimpleSQLite # noqa 11 | 12 | _sqlitetype_to_typepy: Final = { 13 | "INTEGER": Integer, 14 | "REAL": RealNumber, 15 | "TEXT": String, 16 | } 17 | 18 | 19 | def extract_table_metadata( 20 | con: "SimpleSQLite", table_name: str 21 | ) -> tuple[Optional[str], list[str], dict[str, TypeHint]]: 22 | primary_key = None 23 | index_attrs = [] 24 | type_hints = OrderedDict() 25 | 26 | for attr in con.schema_extractor.fetch_table_schema(table_name).as_dict()[table_name]: 27 | attr_name = attr[SchemaHeader.ATTR_NAME] 28 | 29 | if attr[SchemaHeader.KEY] == "PRI": 30 | primary_key = attr_name 31 | elif attr[SchemaHeader.INDEX]: 32 | index_attrs.append(attr_name) 33 | 34 | type_hints[attr_name] = _sqlitetype_to_typepy.get(attr[SchemaHeader.DATA_TYPE]) 35 | 36 | return (primary_key, index_attrs, type_hints) 37 | -------------------------------------------------------------------------------- /simplesqlite/_func.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | from textwrap import dedent 6 | from typing import TYPE_CHECKING 7 | 8 | from pathvalidate.error import ErrorReason, ValidationError 9 | 10 | from ._common import extract_table_metadata 11 | from ._logger import logger 12 | from ._validator import validate_sqlite_attr_name, validate_sqlite_table_name 13 | from .error import NameValidationError 14 | 15 | 16 | if TYPE_CHECKING: 17 | from simplesqlite import SimpleSQLite # noqa 18 | 19 | 20 | def validate_table_name(name: str) -> None: 21 | """ 22 | :param str name: Table name to validate. 23 | :raises NameValidationError: |raises_validate_table_name| 24 | """ 25 | 26 | try: 27 | validate_sqlite_table_name(name) 28 | except ValidationError as e: 29 | if e.reason == ErrorReason.RESERVED_NAME and e.reusable_name: 30 | pass 31 | else: 32 | raise NameValidationError(e) 33 | 34 | 35 | def validate_attr_name(name: str) -> None: 36 | """ 37 | :param str name: Name to validate. 38 | :raises NameValidationError: |raises_validate_attr_name| 39 | """ 40 | 41 | try: 42 | validate_sqlite_attr_name(name) 43 | except ValidationError as e: 44 | raise NameValidationError(e) 45 | 46 | 47 | def append_table(src_con: "SimpleSQLite", dst_con: "SimpleSQLite", table_name: str) -> bool: 48 | """ 49 | Append a table from source database to destination database. 50 | 51 | :param SimpleSQLite src_con: Connection to the source database. 52 | :param SimpleSQLite dst_con: Connection to the destination database. 53 | :param str table_name: Table name to append. 54 | :return: |True| if the append operation succeed. 55 | :rtype: bool 56 | :raises simplesqlite.TableNotFoundError: 57 | |raises_verify_table_existence| 58 | :raises ValueError: 59 | If attributes of the table are different from each other. 60 | """ 61 | 62 | logger.debug( 63 | "append table: src={src_db}.{src_tbl}, dst={dst_db}.{dst_tbl}".format( 64 | src_db=src_con.database_path, 65 | src_tbl=table_name, 66 | dst_db=dst_con.database_path, 67 | dst_tbl=table_name, 68 | ) 69 | ) 70 | 71 | src_con.verify_table_existence(table_name) 72 | dst_con.validate_access_permission(["w", "a"]) 73 | 74 | if dst_con.has_table(table_name): 75 | src_attrs = src_con.fetch_attr_names(table_name) 76 | dst_attrs = dst_con.fetch_attr_names(table_name) 77 | if src_attrs != dst_attrs: 78 | raise ValueError( 79 | dedent( 80 | """ 81 | source and destination attribute is different from each other 82 | src: {} 83 | dst: {} 84 | """.format(src_attrs, dst_attrs) 85 | ) 86 | ) 87 | 88 | primary_key, index_attrs, type_hints = extract_table_metadata(src_con, table_name) 89 | 90 | dst_con.create_table_from_tabledata( 91 | src_con.select_as_tabledata(table_name, type_hints=type_hints), 92 | primary_key=primary_key, 93 | index_attrs=index_attrs, 94 | ) 95 | 96 | return True 97 | 98 | 99 | def copy_table( 100 | src_con: "SimpleSQLite", 101 | dst_con: "SimpleSQLite", 102 | src_table_name: str, 103 | dst_table_name: str, 104 | is_overwrite: bool = True, 105 | ) -> bool: 106 | """ 107 | Copy a table from source to destination. 108 | 109 | :param SimpleSQLite src_con: Connection to the source database. 110 | :param SimpleSQLite dst_con: Connection to the destination database. 111 | :param str src_table_name: Source table name to copy. 112 | :param str dst_table_name: Destination table name. 113 | :param bool is_overwrite: If |True|, overwrite existing table. 114 | :return: |True| if the copy operation succeed. 115 | :rtype: bool 116 | :raises simplesqlite.TableNotFoundError: 117 | |raises_verify_table_existence| 118 | :raises ValueError: 119 | If attributes of the table are different from each other. 120 | """ 121 | 122 | logger.debug( 123 | "copy table: src={src_db}.{src_tbl}, dst={dst_db}.{dst_tbl}".format( 124 | src_db=src_con.database_path, 125 | src_tbl=src_table_name, 126 | dst_db=dst_con.database_path, 127 | dst_tbl=dst_table_name, 128 | ) 129 | ) 130 | 131 | src_con.verify_table_existence(src_table_name) 132 | dst_con.validate_access_permission(["w", "a"]) 133 | 134 | if dst_con.has_table(dst_table_name): 135 | if is_overwrite: 136 | dst_con.drop_table(dst_table_name) 137 | else: 138 | logger.error( 139 | "failed to copy table: the table already exists " 140 | "(src_table={}, dst_table={})".format(src_table_name, dst_table_name) 141 | ) 142 | return False 143 | 144 | primary_key, index_attrs, _ = extract_table_metadata(src_con, src_table_name) 145 | 146 | result = src_con.select(select="*", table_name=src_table_name) 147 | if result is None: 148 | return False 149 | 150 | dst_con.create_table_from_data_matrix( 151 | dst_table_name, 152 | src_con.fetch_attr_names(src_table_name), 153 | result.fetchall(), 154 | primary_key=primary_key, 155 | index_attrs=index_attrs, 156 | ) 157 | 158 | return True 159 | -------------------------------------------------------------------------------- /simplesqlite/_logger/__init__.py: -------------------------------------------------------------------------------- 1 | from ._logger import logger, set_log_level, set_logger 2 | 3 | 4 | __all__ = ("logger", "set_log_level", "set_logger") 5 | -------------------------------------------------------------------------------- /simplesqlite/_logger/_logger.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | from typing import Final 6 | 7 | import sqliteschema 8 | import tabledata 9 | 10 | from ._null_logger import NullLogger # type: ignore 11 | 12 | 13 | MODULE_NAME: Final = "simplesqlite" 14 | 15 | 16 | try: 17 | from loguru import logger 18 | 19 | logger.disable(MODULE_NAME) 20 | except ImportError: 21 | logger = NullLogger() 22 | 23 | 24 | def set_logger(is_enable: bool, propagation_depth: int = 2) -> None: 25 | if is_enable: 26 | logger.enable(MODULE_NAME) 27 | else: 28 | logger.disable(MODULE_NAME) 29 | 30 | if propagation_depth <= 0: 31 | return 32 | 33 | tabledata.set_logger(is_enable, propagation_depth - 1) 34 | sqliteschema.set_logger(is_enable, propagation_depth - 1) 35 | 36 | try: 37 | import pytablereader 38 | 39 | pytablereader.set_logger(is_enable, propagation_depth - 1) 40 | except (ImportError, TypeError): 41 | pass 42 | 43 | 44 | def set_log_level(log_level): # type: ignore 45 | # deprecated 46 | logger.disable(MODULE_NAME) 47 | -------------------------------------------------------------------------------- /simplesqlite/_logger/_null_logger.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | 3 | 4 | class NullLogger: 5 | level_name = None 6 | 7 | def remove(self, handler_id=None): # pragma: no cover 8 | pass 9 | 10 | def add(self, sink, **kwargs): # pragma: no cover 11 | pass 12 | 13 | def disable(self, name): # pragma: no cover 14 | pass 15 | 16 | def enable(self, name): # pragma: no cover 17 | pass 18 | 19 | def critical(self, __message, *args, **kwargs): # pragma: no cover 20 | pass 21 | 22 | def debug(self, __message, *args, **kwargs): # pragma: no cover 23 | pass 24 | 25 | def error(self, __message, *args, **kwargs): # pragma: no cover 26 | pass 27 | 28 | def exception(self, __message, *args, **kwargs): # pragma: no cover 29 | pass 30 | 31 | def info(self, __message, *args, **kwargs): # pragma: no cover 32 | pass 33 | 34 | def log(self, __level, __message, *args, **kwargs): # pragma: no cover 35 | pass 36 | 37 | def success(self, __message, *args, **kwargs): # pragma: no cover 38 | pass 39 | 40 | def trace(self, __message, *args, **kwargs): # pragma: no cover 41 | pass 42 | 43 | def warning(self, __message, *args, **kwargs): # pragma: no cover 44 | pass 45 | -------------------------------------------------------------------------------- /simplesqlite/_sanitizer.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | from collections import Counter 6 | from collections.abc import Sequence 7 | from typing import Final, Optional 8 | 9 | import pathvalidate as pv 10 | import typepy 11 | from dataproperty.typing import TypeHint 12 | from pathvalidate.error import ErrorReason, ValidationError 13 | from pathvalidate.handler import raise_error 14 | from tabledata import ( 15 | DataError, 16 | InvalidHeaderNameError, 17 | InvalidTableNameError, 18 | TableData, 19 | convert_idx_to_alphabet, 20 | ) 21 | from tabledata.normalizer import AbstractTableDataNormalizer 22 | 23 | from ._validator import validate_sqlite_attr_name, validate_sqlite_table_name 24 | from .converter import RecordConvertor 25 | from .error import NameValidationError 26 | from .query import Attr, AttrList 27 | 28 | 29 | class SQLiteTableDataSanitizer(AbstractTableDataNormalizer): 30 | __RENAME_TEMPLATE: Final = "rename_{:s}" 31 | 32 | @property 33 | def _type_hints(self) -> list[TypeHint]: 34 | if self.__is_type_inference: 35 | return self._tabledata.dp_extractor.column_type_hints 36 | 37 | if self._tabledata.dp_extractor.column_type_hints: 38 | return [typepy.String for _ in self._tabledata.dp_extractor.column_type_hints] 39 | 40 | if self.__upper_headers: 41 | return [typepy.String for _ in self.__upper_headers] 42 | 43 | return [] 44 | 45 | def __init__( 46 | self, 47 | table_data: TableData, 48 | dup_col_handler: str = "error", 49 | is_type_inference: bool = True, 50 | max_workers: Optional[int] = None, 51 | ) -> None: 52 | table_data.max_workers = max_workers 53 | 54 | super().__init__(table_data) 55 | 56 | if typepy.is_null_string(table_data.table_name): 57 | raise NameValidationError("table_name is empty") 58 | 59 | self.__upper_headers = [] 60 | for header in self._tabledata.headers: 61 | if not header: 62 | continue 63 | try: 64 | header = header.upper() 65 | except AttributeError: 66 | header = str(header).upper() 67 | 68 | self.__upper_headers.append(header) 69 | 70 | self.__dup_col_handler = dup_col_handler 71 | self.__is_type_inference = is_type_inference 72 | 73 | def _preprocess_table_name(self) -> str: 74 | if self._tabledata.table_name is None: 75 | raise NameValidationError("table name must not be None") 76 | 77 | try: 78 | new_name = pv.sanitize_filename( 79 | self._tabledata.table_name, replacement_text="_", null_value_handler=raise_error 80 | ) 81 | except TypeError: 82 | raise NameValidationError( 83 | f"table name must be a string: actual='{self._tabledata.table_name}'" 84 | ) 85 | 86 | new_name = pv.replace_unprintable_char(new_name, replacement_text="") 87 | new_name = pv.replace_symbol( 88 | new_name, 89 | replacement_text="_", 90 | is_replace_consecutive_chars=True, 91 | is_strip=True, 92 | ) 93 | 94 | return new_name 95 | 96 | def _validate_table_name(self, table_name: str) -> None: 97 | try: 98 | validate_sqlite_table_name(table_name) 99 | except ValidationError as e: 100 | if ( 101 | e.reason == ErrorReason.RESERVED_NAME and e.reusable_name is False 102 | ) or e.reason == ErrorReason.INVALID_CHARACTER: 103 | raise InvalidTableNameError(e) 104 | elif e.reason == ErrorReason.RESERVED_NAME: 105 | pass 106 | else: 107 | raise 108 | 109 | def _normalize_table_name(self, table_name: str) -> str: 110 | return self.__RENAME_TEMPLATE.format(table_name) 111 | 112 | def _preprocess_header(self, col_idx: int, header: Optional[str]) -> str: 113 | if typepy.is_null_string(header): 114 | return self.__get_default_header(col_idx) 115 | 116 | assert header 117 | if is_multibyte_str(header): 118 | return header 119 | 120 | return Attr.sanitize(header) 121 | 122 | def _validate_headers(self) -> None: 123 | if typepy.is_empty_sequence(self._tabledata.headers): 124 | raise ValueError("attribute name list is empty") 125 | 126 | for header in self._tabledata.headers: 127 | self._validate_header(header) 128 | 129 | def _validate_header(self, header: str) -> None: 130 | try: 131 | validate_sqlite_attr_name(header) 132 | except ValidationError as e: 133 | if e.reason in (ErrorReason.NULL_NAME, ErrorReason.RESERVED_NAME): 134 | pass 135 | elif e.reason == ErrorReason.INVALID_CHARACTER: 136 | raise InvalidHeaderNameError(e) 137 | else: 138 | raise 139 | 140 | def _normalize_header(self, header: str) -> str: 141 | return self.__RENAME_TEMPLATE.format(header) 142 | 143 | def _normalize_headers(self) -> list[str]: 144 | if typepy.is_empty_sequence(self._tabledata.headers): 145 | try: 146 | return [ 147 | self.__get_default_header(col_idx) 148 | for col_idx in range(len(self._tabledata.rows[0])) 149 | ] 150 | except IndexError: 151 | raise DataError("header list and data body are empty") 152 | 153 | attr_name_list = AttrList.sanitize(super()._normalize_headers()) 154 | 155 | try: 156 | for attr_name in attr_name_list: 157 | validate_sqlite_attr_name(attr_name) 158 | except ValidationError as e: 159 | if e.reason == ErrorReason.RESERVED_NAME: 160 | pass 161 | else: 162 | raise 163 | 164 | # duplicated attribute name handling --- 165 | for key, count in Counter(attr_name_list).most_common(): 166 | if count <= 1: 167 | continue 168 | 169 | if self.__dup_col_handler == "error": 170 | raise ValueError(f"duplicate column name: {key}") 171 | 172 | # rename duplicate headers 173 | rename_target_idx_list = [i for i, attr in enumerate(attr_name_list) if attr == key][1:] 174 | suffix_count = 0 175 | for rename_target_idx in rename_target_idx_list: 176 | while True: 177 | suffix_count += 1 178 | attr_name_candidate = f"{key:s}_{suffix_count:d}" 179 | if attr_name_candidate in attr_name_list: 180 | continue 181 | 182 | attr_name_list[rename_target_idx] = attr_name_candidate 183 | break 184 | 185 | return attr_name_list 186 | 187 | def _normalize_rows(self, normalize_headers: Sequence[str]) -> list: 188 | return RecordConvertor.to_records(normalize_headers, self._tabledata.rows) 189 | 190 | def __get_default_header(self, col_idx: int) -> str: 191 | i = 0 192 | while True: 193 | header = convert_idx_to_alphabet(col_idx + i) 194 | if header not in self.__upper_headers: 195 | return header 196 | 197 | i += 1 198 | 199 | 200 | def is_multibyte_str(text: str) -> bool: 201 | from mbstrdecoder import MultiByteStrDecoder 202 | from typepy import StrictLevel, String 203 | 204 | if not String(text, strict_level=StrictLevel.MIN).is_type(): 205 | return False 206 | 207 | try: 208 | unicode_text = MultiByteStrDecoder(text).unicode_str 209 | except ValueError: 210 | return False 211 | 212 | try: 213 | unicode_text.encode("ascii") 214 | except UnicodeEncodeError: 215 | return True 216 | 217 | return False 218 | -------------------------------------------------------------------------------- /simplesqlite/_validator.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Final 3 | 4 | from pathvalidate import unprintable_ascii_chars 5 | from pathvalidate.error import ErrorReason, ValidationError 6 | 7 | 8 | __SQLITE_VALID_RESERVED_KEYWORDS: Final = [ 9 | "ABORT", 10 | "ACTION", 11 | "AFTER", 12 | "ANALYZE", 13 | "ASC", 14 | "ATTACH", 15 | "BEFORE", 16 | "BEGIN", 17 | "BY", 18 | "CASCADE", 19 | "CAST", 20 | "COLUMN", 21 | "CONFLICT", 22 | "CROSS", 23 | "CURRENT_DATE", 24 | "CURRENT_TIME", 25 | "CURRENT_TIMESTAMP", 26 | "DATABASE", 27 | "DEFERRED", 28 | "DESC", 29 | "DETACH", 30 | "EACH", 31 | "END", 32 | "EXCLUSIVE", 33 | "EXPLAIN", 34 | "FAIL", 35 | "FOR", 36 | "FULL", 37 | "GLOB", 38 | "IGNORE", 39 | "IMMEDIATE", 40 | "INDEXED", 41 | "INITIALLY", 42 | "INNER", 43 | "INSTEAD", 44 | "KEY", 45 | "LEFT", 46 | "LIKE", 47 | "MATCH", 48 | "NATURAL", 49 | "NO", 50 | "OF", 51 | "OFFSET", 52 | "OUTER", 53 | "PLAN", 54 | "PRAGMA", 55 | "QUERY", 56 | "RAISE", 57 | "RECURSIVE", 58 | "REGEXP", 59 | "REINDEX", 60 | "RELEASE", 61 | "RENAME", 62 | "REPLACE", 63 | "RESTRICT", 64 | "RIGHT", 65 | "ROLLBACK", 66 | "ROW", 67 | "SAVEPOINT", 68 | "TEMP", 69 | "TEMPORARY", 70 | "TRIGGER", 71 | "VACUUM", 72 | "VIEW", 73 | "VIRTUAL", 74 | "WITH", 75 | "WITHOUT", 76 | ] 77 | __SQLITE_INVALID_RESERVED_KEYWORDS: Final = [ 78 | "ADD", 79 | "ALL", 80 | "ALTER", 81 | "AND", 82 | "AS", 83 | "AUTOINCREMENT", 84 | "BETWEEN", 85 | "CASE", 86 | "CHECK", 87 | "COLLATE", 88 | "COMMIT", 89 | "CONSTRAINT", 90 | "CREATE", 91 | "DEFAULT", 92 | "DEFERRABLE", 93 | "DELETE", 94 | "DISTINCT", 95 | "DROP", 96 | "ELSE", 97 | "ESCAPE", 98 | "EXCEPT", 99 | "EXISTS", 100 | "FOREIGN", 101 | "FROM", 102 | "GROUP", 103 | "HAVING", 104 | "IN", 105 | "INDEX", 106 | "INSERT", 107 | "INTERSECT", 108 | "INTO", 109 | "IS", 110 | "ISNULL", 111 | "JOIN", 112 | "LIMIT", 113 | "NOT", 114 | "NOTNULL", 115 | "NULL", 116 | "ON", 117 | "OR", 118 | "ORDER", 119 | "PRIMARY", 120 | "REFERENCES", 121 | "SELECT", 122 | "SET", 123 | "TABLE", 124 | "THEN", 125 | "TO", 126 | "TRANSACTION", 127 | "UNION", 128 | "UNIQUE", 129 | "UPDATE", 130 | "USING", 131 | "VALUES", 132 | "WHEN", 133 | "WHERE", 134 | ] 135 | 136 | __SQLITE_VALID_RESERVED_KEYWORDS_TABLE: Final = __SQLITE_VALID_RESERVED_KEYWORDS 137 | __SQLITE_INVALID_RESERVED_KEYWORDS_TABLE: Final = __SQLITE_INVALID_RESERVED_KEYWORDS + ["IF"] 138 | 139 | __SQLITE_VALID_RESERVED_KEYWORDS_ATTR: Final = __SQLITE_VALID_RESERVED_KEYWORDS + ["IF"] 140 | __SQLITE_INVALID_RESERVED_KEYWORDS_ATTR: Final = __SQLITE_INVALID_RESERVED_KEYWORDS 141 | 142 | __RE_INVALID_CHARS: Final = re.compile( 143 | "[{:s}]".format(re.escape("".join(unprintable_ascii_chars))), re.UNICODE 144 | ) 145 | 146 | 147 | def validate_sqlite_table_name(name: str) -> None: 148 | """ 149 | :param str name: Name to validate. 150 | :raises pathvalidate.ValidationError: 151 | - If the ``name`` includes unprintable character(s). 152 | - |raises_sqlite_keywords| 153 | """ 154 | 155 | if not name: 156 | raise ValidationError(["null name"], reason=ErrorReason.NULL_NAME) 157 | 158 | if __RE_INVALID_CHARS.search(name): 159 | raise ValidationError(["unprintable character found"], reason=ErrorReason.INVALID_CHARACTER) 160 | 161 | name = name.upper() 162 | 163 | if name in __SQLITE_INVALID_RESERVED_KEYWORDS_TABLE: 164 | raise ValidationError( 165 | [f"'{name}' is a reserved keyword by sqlite"], 166 | reason=ErrorReason.RESERVED_NAME, 167 | reusable_name=False, 168 | ) 169 | 170 | if name in __SQLITE_VALID_RESERVED_KEYWORDS_TABLE: 171 | raise ValidationError( 172 | [f"'{name}' is a reserved keyword by sqlite"], 173 | reason=ErrorReason.RESERVED_NAME, 174 | reusable_name=True, 175 | ) 176 | 177 | 178 | def validate_sqlite_attr_name(name: str) -> None: 179 | """ 180 | :param str name: Name to validate. 181 | :raises pathvalidate.ValidationError: 182 | - If the ``name`` includes unprintable character(s). 183 | - |raises_sqlite_keywords| 184 | """ 185 | 186 | if not name: 187 | raise ValidationError(["null name"], reason=ErrorReason.NULL_NAME) 188 | 189 | if __RE_INVALID_CHARS.search(name): 190 | raise ValidationError(["unprintable character found"], reason=ErrorReason.INVALID_CHARACTER) 191 | 192 | name = name.upper() 193 | 194 | if name in __SQLITE_INVALID_RESERVED_KEYWORDS_ATTR: 195 | raise ValidationError( 196 | [f"'{name}' is a reserved keyword by sqlite"], 197 | reason=ErrorReason.RESERVED_NAME, 198 | reusable_name=False, 199 | ) 200 | 201 | if name in __SQLITE_VALID_RESERVED_KEYWORDS_ATTR: 202 | raise ValidationError( 203 | [f"'{name}' is a reserved keyword by sqlite"], 204 | reason=ErrorReason.RESERVED_NAME, 205 | reusable_name=True, 206 | ) 207 | -------------------------------------------------------------------------------- /simplesqlite/converter.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | from collections.abc import Sequence 6 | from datetime import datetime 7 | from decimal import Decimal 8 | from typing import Any, Callable, Union 9 | 10 | from ._logger import logger 11 | 12 | 13 | def default_datetime_converter(value: datetime) -> str: 14 | return value.strftime("%Y-%m-%d %H:%M:%S%z") 15 | 16 | 17 | class RecordConvertor: 18 | @staticmethod 19 | def __to_sqlite_element( 20 | value: Any, attr: Union[int, str], datetime_converter: Callable[[datetime], str] 21 | ) -> Any: 22 | if isinstance(value, Decimal): 23 | return float(value) 24 | 25 | if isinstance(value, int): 26 | # INTEGER. The value is a signed integer, 27 | # stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value. 28 | # https://www.sqlite.org/datatype3.html 29 | if not (-9223372036854775808 < value < 9223372036854775807): 30 | raise OverflowError(attr) 31 | 32 | if isinstance(value, datetime): 33 | # TODO: add an interface to specify datetime_converter 34 | return datetime_converter(value) 35 | 36 | return value 37 | 38 | @classmethod 39 | def to_record(cls, attr_names: Sequence[str], values: Union[Sequence, dict]) -> list: 40 | """ 41 | Convert values to a record to be inserted into a database. 42 | 43 | :param list attr_names: 44 | List of attributes for the converting record. 45 | :param values: Values to be converted. 46 | :type values: |dict|/|namedtuple|/|list|/|tuple| 47 | :raises ValueError: If the ``values`` is invalid. 48 | """ 49 | 50 | try: 51 | # from a namedtuple to a dict 52 | values = values._asdict() # type: ignore 53 | except AttributeError: 54 | pass 55 | 56 | datetime_converter = default_datetime_converter 57 | 58 | if isinstance(values, dict): 59 | return [ 60 | cls.__to_sqlite_element(values.get(attr_name), attr_name, datetime_converter) 61 | for attr_name in attr_names 62 | ] 63 | 64 | if isinstance(values, (tuple, list)): 65 | return [ 66 | cls.__to_sqlite_element(value, col, datetime_converter) 67 | for col, value in enumerate(values) 68 | ] 69 | 70 | raise TypeError(f"cannot convert from {type(values)} to list") 71 | 72 | @classmethod 73 | def to_records(cls, attr_names: Sequence[str], value_matrix: Sequence) -> list: 74 | """ 75 | Convert a value matrix to records to be inserted into a database. 76 | 77 | :param list attr_names: 78 | List of attributes for the converting records. 79 | :param value_matrix: Values to be converted. 80 | :type value_matrix: list of |dict|/|namedtuple|/|list|/|tuple| 81 | 82 | .. seealso:: :py:meth:`.to_record` 83 | """ 84 | 85 | records = [] 86 | error_msgs = [] 87 | 88 | for row_idx, record in enumerate(value_matrix): 89 | try: 90 | records.append(cls.to_record(attr_names, record)) 91 | except OverflowError as e: 92 | try: 93 | if isinstance(e.args[0], int): 94 | col_idx = e.args[0] 95 | col = f"{attr_names[col_idx]} ({col_idx})" 96 | else: 97 | col = e.args[0] 98 | except IndexError as e: 99 | logger.error(e) 100 | continue 101 | 102 | error_msgs.append(f" overflow int found: row={row_idx}, col={col}") 103 | 104 | if error_msgs: 105 | raise OverflowError("failed to convert:\n" + "\n".join(error_msgs)) 106 | 107 | return records 108 | -------------------------------------------------------------------------------- /simplesqlite/error.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import sqlite3 6 | from typing import Any, Optional 7 | 8 | from tabledata import NameValidationError # noqa: F401 9 | 10 | 11 | class DatabaseError(sqlite3.DatabaseError): 12 | """ 13 | Exception raised for errors that are related to the database. 14 | 15 | .. seealso:: 16 | - `sqlite3.DatabaseError `__ 17 | """ 18 | 19 | 20 | class NullDatabaseConnectionError(DatabaseError): 21 | """ 22 | Exception raised when executing an operation of 23 | :py:class:`~simplesqlite.SimpleSQLite` instance without connection to 24 | a SQLite database file. 25 | """ 26 | 27 | 28 | class TableNotFoundError(DatabaseError): 29 | """ 30 | Exception raised when accessed the table that not exists in the database. 31 | """ 32 | 33 | 34 | class AttributeNotFoundError(DatabaseError): 35 | """ 36 | Exception raised when accessed the attribute that not exists in the table. 37 | """ 38 | 39 | 40 | class SqlSyntaxError(Exception): 41 | """ 42 | Exception raised when a SQLite query syntax is invalid. 43 | """ 44 | 45 | 46 | class OperationalError(sqlite3.OperationalError): 47 | """ 48 | Exception raised when failed to execute a query. 49 | """ 50 | 51 | @property 52 | def message(self) -> Optional[str]: 53 | return self.__message 54 | 55 | def __init__(self, *args: Any, **kwargs: Any) -> None: 56 | self.__message = kwargs.pop("message", None) 57 | 58 | super().__init__(*args) 59 | 60 | def __str__(self) -> str: 61 | if not self.message: 62 | return "" 63 | 64 | return str(self.message) 65 | -------------------------------------------------------------------------------- /simplesqlite/model.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import re 6 | import warnings 7 | from collections import OrderedDict 8 | from collections.abc import Generator, Sequence 9 | from sqlite3 import Cursor 10 | from typing import Any, Optional, Union, cast 11 | 12 | import typepy 13 | from sqliteschema import SQLiteTableSchema 14 | from typepy.type import AbstractType 15 | 16 | from ._column import Column 17 | from .core import SimpleSQLite 18 | from .error import DatabaseError, TableNotFoundError 19 | from .query import Attr, AttrList 20 | from .query import Set as SetQuery 21 | from .query import WhereQuery 22 | 23 | 24 | __all__ = ("Integer", "Real", "Text", "Blob", "Model", "Column") 25 | 26 | 27 | def dict_factory(cursor: Cursor, row: Sequence) -> dict: 28 | record = {} 29 | 30 | for idx, col in enumerate(cursor.description): 31 | record[col[0]] = row[idx] 32 | 33 | return record 34 | 35 | 36 | class Integer(Column): 37 | @property 38 | def sqlite_datatype(self) -> str: 39 | return "INTEGER" 40 | 41 | @property 42 | def typepy_class(self) -> type[AbstractType]: 43 | return typepy.Integer 44 | 45 | 46 | class Real(Column): 47 | @property 48 | def sqlite_datatype(self) -> str: 49 | return "REAL" 50 | 51 | @property 52 | def typepy_class(self) -> type[AbstractType]: 53 | return typepy.RealNumber 54 | 55 | 56 | class Text(Column): 57 | @property 58 | def sqlite_datatype(self) -> str: 59 | return "TEXT" 60 | 61 | @property 62 | def typepy_class(self) -> type[AbstractType]: 63 | return typepy.String 64 | 65 | 66 | class Blob(Column): 67 | @property 68 | def sqlite_datatype(self) -> str: 69 | return "BLOB" 70 | 71 | @property 72 | def typepy_class(self) -> type[AbstractType]: 73 | return typepy.Bytes 74 | 75 | 76 | class Model: 77 | __connection: SimpleSQLite 78 | __is_hidden = False 79 | __table_name: Optional[str] = None 80 | __attr_names: list[str] = [] 81 | 82 | @classmethod 83 | def attach(cls, database_src: SimpleSQLite, is_hidden: bool = False) -> None: 84 | cls.__connection = SimpleSQLite(database_src) 85 | cls.__is_hidden = is_hidden 86 | 87 | @classmethod 88 | def get_table_name(cls) -> str: 89 | if cls.__table_name: 90 | return cls.__table_name 91 | 92 | table_name = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", cls.__name__) 93 | table_name = re.sub(r"([a-z\d])([A-Z])", r"\1_\2", table_name) 94 | table_name = table_name.replace("-", "_").lower() 95 | 96 | if cls.__is_hidden: 97 | table_name = f"_{table_name:s}_" 98 | 99 | cls.__table_name = table_name 100 | 101 | return cls.__table_name 102 | 103 | @classmethod 104 | def get_attr_names(cls) -> list[str]: 105 | if cls.__attr_names: 106 | return cls.__attr_names 107 | 108 | cls.__attr_names = [attr_name for attr_name in cls.__dict__ if cls.__is_attr(attr_name)] 109 | for attr_name in cls.__attr_names: 110 | col = cls._get_col(attr_name, validate_name=False) 111 | col._set_column_name_if_uninitialized(attr_name) 112 | 113 | return cls.__attr_names 114 | 115 | @classmethod 116 | def create(cls) -> None: 117 | cls.__validate_connection() 118 | assert cls.__connection # to avoid type check error 119 | 120 | attr_descs = [] 121 | 122 | for attr_name in cls.get_attr_names(): 123 | col = cls._get_col(attr_name, validate_name=False) 124 | attr_descs.append( 125 | "{attr} {constraints}".format( 126 | attr=Attr(col.get_column_name()), constraints=col.get_desc() 127 | ) 128 | ) 129 | 130 | cls.__connection.create_table(cls.get_table_name(), attr_descs) 131 | 132 | @classmethod 133 | def select(cls, where: Optional[WhereQuery] = None, extra: Optional[str] = None) -> Generator: 134 | cls.__validate_connection() 135 | assert cls.__connection # to avoid type check error 136 | assert cls.__connection.connection # to avoid type check error 137 | 138 | stash_row_factory = cls.__connection.connection.row_factory 139 | 140 | try: 141 | cls.__connection.set_row_factory(dict_factory) 142 | 143 | result = cls.__connection.select( 144 | select=AttrList( 145 | [ 146 | cls._get_col(attr_name, validate_name=False).get_column_name() 147 | for attr_name in cls.get_attr_names() 148 | ] 149 | ), 150 | table_name=cls.get_table_name(), 151 | where=where, 152 | extra=extra, 153 | ) 154 | assert result # to avoid type check error 155 | for record in result.fetchall(): 156 | yield cls(**record) 157 | finally: 158 | cls.__connection.set_row_factory(stash_row_factory) 159 | 160 | @classmethod 161 | def insert(cls, model_obj: "Model") -> None: 162 | cls.__validate_connection() 163 | assert cls.__connection # to avoid type check error 164 | 165 | if type(model_obj).__name__ != cls.__name__: 166 | raise TypeError( 167 | "unexpected type: expected={}, actual={}".format( 168 | cls.__name__, type(model_obj).__name__ 169 | ) 170 | ) 171 | 172 | record = {} 173 | 174 | for attr_name in cls.get_attr_names(): 175 | if attr_name in model_obj.__no_value_columns: 176 | continue 177 | 178 | value = getattr(model_obj, attr_name) 179 | cls.__validate_value(attr_name, value) 180 | 181 | record[cls._get_col(attr_name, validate_name=False).get_column_name()] = value 182 | 183 | try: 184 | cls.__connection.insert(cls.get_table_name(), record, list(record.keys())) 185 | except TableNotFoundError as e: 186 | raise RuntimeError(f"{e}: execute 'create' method before insert") 187 | 188 | @classmethod 189 | def update( 190 | cls, set_query: Union[str, Sequence[SetQuery]], where: Optional[WhereQuery] = None 191 | ) -> None: 192 | cls.__connection.update(cls.get_table_name(), set_query=set_query, where=where) 193 | 194 | @classmethod 195 | def commit(cls) -> None: 196 | cls.__validate_connection() 197 | assert cls.__connection # to avoid type check error 198 | cls.__connection.commit() 199 | 200 | @classmethod 201 | def delete(cls, where: Optional[WhereQuery]) -> Optional[Cursor]: 202 | cls.__validate_connection() 203 | return cls.__connection.delete(cls.get_table_name(), where=where) 204 | 205 | @classmethod 206 | def fetch_schema(cls) -> SQLiteTableSchema: 207 | cls.__validate_connection() 208 | assert cls.__connection # to avoid type check error 209 | return cls.__connection.schema_extractor.fetch_table_schema(cls.get_table_name()) 210 | 211 | @classmethod 212 | def fetch_num_records(cls, where: None = None) -> int: 213 | assert cls.__connection # to avoid type check error 214 | return cast(int, cls.__connection.fetch_num_records(cls.get_table_name(), where=where)) 215 | 216 | @classmethod 217 | def attr_to_header(cls, attr_name: str) -> str: 218 | warnings.warn( 219 | "attr_to_header() is deprecated. Use attr_to_column() instead.", DeprecationWarning 220 | ) 221 | return cls._get_col(attr_name).get_column_name() 222 | 223 | @classmethod 224 | def attr_to_column(cls, attr_name: str) -> str: 225 | return cls._get_col(attr_name).get_column_name() 226 | 227 | def as_dict(self) -> dict: 228 | record = OrderedDict() 229 | for attr_name in self.get_attr_names(): 230 | value = getattr(self, attr_name) 231 | if value is None: 232 | continue 233 | 234 | record[self.attr_to_column(attr_name)] = value 235 | 236 | return record 237 | 238 | def __init__(self, **kwargs: Any) -> None: 239 | for attr_name in self.get_attr_names(): 240 | value = kwargs.get(attr_name) 241 | if value is None: 242 | value = kwargs.get(self.attr_to_column(attr_name)) 243 | 244 | setattr(self, attr_name, value) 245 | 246 | self.__update_no_value_columns() 247 | 248 | def __setattr__(self, name: str, value: Any) -> None: 249 | if name == "_Model__no_value_columns": 250 | # avoid infinite recursion 251 | super().__setattr__(name, value) 252 | return 253 | 254 | super().__setattr__(name, value) 255 | self.__update_no_value_columns() 256 | 257 | def __eq__(self, other: Any) -> bool: 258 | if not isinstance(other, type(self)): 259 | return False 260 | 261 | return self.__dict__ == other.__dict__ 262 | 263 | def __ne__(self, other: Any) -> bool: 264 | if not isinstance(other, type(self)): 265 | return True 266 | 267 | return self.__dict__ != other.__dict__ 268 | 269 | def __repr__(self) -> str: 270 | return "{name:s} ({attributes:s})".format( 271 | name=type(self).__name__, 272 | attributes=", ".join([f"{key}={value}" for key, value in self.as_dict().items()]), 273 | ) 274 | 275 | def __update_no_value_columns(self) -> None: 276 | self.__no_value_columns: set[str] = set() 277 | 278 | for attr_name in self.get_attr_names(): 279 | value = getattr(self, attr_name) 280 | if value is None: 281 | value = getattr(self, self.attr_to_column(attr_name)) 282 | if value is None: 283 | self.__no_value_columns.add(attr_name) 284 | 285 | @classmethod 286 | def __validate_connection(cls) -> None: 287 | if cls.__connection is None: 288 | raise DatabaseError("SimpleSQLite connection required. you need to call attach first") 289 | 290 | @classmethod 291 | def __validate_value(cls, attr_name: str, value: Any) -> None: 292 | column = cls._get_col(attr_name) 293 | 294 | if value is None and not column.not_null: 295 | return 296 | 297 | column.typepy_class(value, strict_level=typepy.StrictLevel.MIN).validate() 298 | 299 | @classmethod 300 | def __is_attr(cls, attr_name: str) -> bool: 301 | private_var_regexp = re.compile(f"^_{Model.__name__}__[a-zA-Z]+") 302 | 303 | return ( 304 | not attr_name.startswith("__") 305 | and private_var_regexp.search(attr_name) is None 306 | and not callable(cls.__dict__.get(attr_name)) 307 | ) 308 | 309 | @classmethod 310 | def _get_col(cls, attr_name: str, validate_name: bool = True) -> Column: 311 | if validate_name and attr_name not in cls.get_attr_names(): 312 | raise ValueError(f"invalid attribute: {attr_name}") 313 | 314 | return cls.__dict__[attr_name] 315 | -------------------------------------------------------------------------------- /simplesqlite/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thombashi/SimpleSQLite/aca1601933ab2984a436d504fab951f9f9ed7cfe/simplesqlite/py.typed -------------------------------------------------------------------------------- /simplesqlite/sqlquery.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | from collections.abc import Sequence 6 | from typing import Optional, Union 7 | 8 | import typepy 9 | 10 | from ._func import validate_table_name 11 | from .query import And, Attr, Or, Set, Table, Value, Where, WhereQuery 12 | 13 | 14 | class SqlQuery: 15 | """ 16 | Support class for making SQLite query. 17 | """ 18 | 19 | @classmethod 20 | def make_update( 21 | cls, table: str, set_query: Union[str, Sequence[Set]], where: Optional[WhereQuery] = None 22 | ) -> str: 23 | """ 24 | Make UPDATE query. 25 | 26 | :param str table: Table name of executing the query. 27 | :param str set_query: SET part of the UPDATE query. 28 | :param str where: 29 | Add a WHERE clause to execute query, 30 | if the value is not |None|. 31 | :return: Query of SQLite. 32 | :rtype: str 33 | :raises ValueError: If ``set_query`` is empty string. 34 | :raises simplesqlite.NameValidationError: 35 | |raises_validate_table_name| 36 | """ 37 | 38 | if isinstance(set_query, str): 39 | norm_set_query = set_query 40 | else: 41 | norm_set_query = ", ".join([query.to_query() for query in set_query]) 42 | 43 | validate_table_name(table) 44 | if typepy.is_null_string(norm_set_query): 45 | raise ValueError("SET query is null") 46 | 47 | query_list = [f"UPDATE {Table(table):s}", f"SET {norm_set_query:s}"] 48 | if where and isinstance(where, (str, Where, And, Or)): 49 | query_list.append(f"WHERE {where:s}") 50 | 51 | return " ".join(query_list) 52 | 53 | @classmethod 54 | def make_where_in(cls, key: str, value_list: Sequence[str]) -> str: 55 | """ 56 | Make part of WHERE IN query. 57 | 58 | :param str key: Attribute name of the key. 59 | :param Sequence[str] value_list: 60 | List of values that the right hand side associated with the key. 61 | :return: Part of WHERE query of SQLite. 62 | :rtype: str 63 | 64 | :Examples: 65 | >>> from simplesqlite.sqlquery import SqlQuery 66 | >>> SqlQuery.make_where_in("key", ["hoge", "foo", "bar"]) 67 | "key IN ('hoge', 'foo', 'bar')" 68 | """ 69 | 70 | return "{:s} IN ({:s})".format( 71 | Attr(key), ", ".join([Value(value).to_query() for value in value_list]) 72 | ) 73 | 74 | @classmethod 75 | def make_where_not_in(cls, key: str, value_list: Sequence[str]) -> str: 76 | """ 77 | Make part of WHERE NOT IN query. 78 | 79 | :param str key: Attribute name of the key. 80 | :param Sequence[str] value_list: 81 | List of values that the right hand side associated with the key. 82 | :return: Part of WHERE query of SQLite. 83 | :rtype: str 84 | 85 | :Example: 86 | >>> from simplesqlite.sqlquery import SqlQuery 87 | >>> SqlQuery.make_where_not_in("key", ["hoge", "foo", "bar"]) 88 | "key NOT IN ('hoge', 'foo', 'bar')" 89 | """ 90 | 91 | return "{:s} NOT IN ({:s})".format( 92 | Attr(key), ", ".join([Value(value).to_query() for value in value_list]) 93 | ) 94 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thombashi/SimpleSQLite/aca1601933ab2984a436d504fab951f9f9ed7cfe/test/__init__.py -------------------------------------------------------------------------------- /test/_common.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import sys 6 | 7 | 8 | def print_test_result(expected, actual, error=None): 9 | print(f"[expected]\n{expected}\n") 10 | print(f"[actual]\n{actual}\n") 11 | 12 | if error: 13 | print(error, file=sys.stderr) 14 | -------------------------------------------------------------------------------- /test/fixture.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import pytest 6 | 7 | from simplesqlite import SimpleSQLite 8 | 9 | 10 | TEST_TABLE_NAME = "test_table" 11 | 12 | 13 | @pytest.fixture 14 | def con(tmpdir): 15 | p = tmpdir.join("tmp.db") 16 | con = SimpleSQLite(str(p), "w") 17 | 18 | con.create_table_from_data_matrix(TEST_TABLE_NAME, ["attr_a", "attr_b"], [[1, 2], [3, 4]]) 19 | con.execute_query(f"CREATE VIEW view1 AS SELECT attr_a, attr_b FROM {TEST_TABLE_NAME}") 20 | con.commit() 21 | 22 | return con 23 | 24 | 25 | @pytest.fixture 26 | def con_mix(tmpdir): 27 | p = tmpdir.join("tmp_mixed_data.db") 28 | con = SimpleSQLite(str(p), "w") 29 | 30 | con.create_table_from_data_matrix( 31 | TEST_TABLE_NAME, ["attr_i", "attr_f", "attr_s"], [[1, 2.2, "aa"], [3, 4.4, "bb"]] 32 | ) 33 | con.execute_query(f"CREATE VIEW view1 AS SELECT attr_a, attr_b FROM {TEST_TABLE_NAME}") 34 | con.commit() 35 | 36 | return con 37 | 38 | 39 | @pytest.fixture 40 | def con_ro(tmpdir): 41 | p = tmpdir.join("tmp_readonly.db") 42 | con = SimpleSQLite(str(p), "w") 43 | 44 | con.create_table_from_data_matrix(TEST_TABLE_NAME, ["attr_a", "attr_b"], [[1, 2], [3, 4]]) 45 | con.close() 46 | con.connect(str(p), "r") 47 | 48 | return con 49 | 50 | 51 | @pytest.fixture 52 | def con_profile(tmpdir): 53 | p = tmpdir.join("tmp_profile.db") 54 | con = SimpleSQLite(str(p), "w", profile=True) 55 | 56 | con.create_table_from_data_matrix(TEST_TABLE_NAME, ["attr_a", "attr_b"], [[1, 2], [3, 4]]) 57 | con.commit() 58 | 59 | return con 60 | 61 | 62 | @pytest.fixture 63 | def con_index(tmpdir): 64 | p = tmpdir.join("tmp.db") 65 | con = SimpleSQLite(str(p), "w") 66 | 67 | con.create_table_from_data_matrix( 68 | TEST_TABLE_NAME, ["attr_a", "attr_b"], [[1, 2], [3, 4]], index_attrs=["attr_a"] 69 | ) 70 | 71 | return con 72 | 73 | 74 | @pytest.fixture 75 | def con_null(tmpdir): 76 | p = tmpdir.join("tmp_null.db") 77 | con = SimpleSQLite(str(p), "w") 78 | con.close() 79 | 80 | return con 81 | 82 | 83 | @pytest.fixture 84 | def con_empty(tmpdir): 85 | p = tmpdir.join("tmp_empty.db") 86 | return SimpleSQLite(str(p), "w") 87 | -------------------------------------------------------------------------------- /test/test_convertor.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | from collections import namedtuple 6 | 7 | import pytest 8 | 9 | from simplesqlite.converter import RecordConvertor 10 | 11 | 12 | attrs_2 = ["attr_a", "attr_b"] 13 | attrs_3 = ["attr_a", "attr_b", "attr_c"] 14 | 15 | NamedTuple2 = namedtuple("NamedTuple2", " ".join(attrs_2)) 16 | NamedTuple3 = namedtuple("NamedTuple3", " ".join(attrs_3)) 17 | 18 | 19 | class Test_RecordConvertor_to_record: 20 | @pytest.mark.parametrize( 21 | ["attr_names", "value", "expected"], 22 | [ 23 | [attrs_2, [5, 6], [5, 6]], 24 | [attrs_2, (5, 6), [5, 6]], 25 | [attrs_2, {"attr_a": 5, "attr_b": 6}, [5, 6]], 26 | [attrs_2, {"attr_a": 5, "attr_b": 6, "not_exist_attr": 100}, [5, 6]], 27 | [attrs_2, {"attr_a": 5}, [5, None]], 28 | [attrs_2, {"attr_b": 6}, [None, 6]], 29 | [attrs_2, {}, [None, None]], 30 | [attrs_2, NamedTuple2(5, 6), [5, 6]], 31 | [attrs_2, NamedTuple2(5, None), [5, None]], 32 | [attrs_2, NamedTuple2(None, 6), [None, 6]], 33 | [attrs_2, NamedTuple2(None, None), [None, None]], 34 | [attrs_2, NamedTuple3(5, 6, 7), [5, 6]], 35 | [attrs_3, NamedTuple3(5, 6, 7), [5, 6, 7]], 36 | ], 37 | ) 38 | def test_normal(self, attr_names, value, expected): 39 | assert RecordConvertor.to_record(attr_names, value) == expected 40 | 41 | @pytest.mark.parametrize( 42 | ["attr_names", "value", "expected"], 43 | [ 44 | [attrs_2, None, TypeError], 45 | [None, None, TypeError], 46 | ], 47 | ) 48 | def test_exception(self, attr_names, value, expected): 49 | with pytest.raises(expected): 50 | RecordConvertor.to_record(attr_names, value) 51 | 52 | 53 | class Test_RecordConvertor_to_records: 54 | @pytest.mark.parametrize( 55 | ["attr_names", "value", "expected"], 56 | [ 57 | [ 58 | attrs_2, 59 | [ 60 | [1, 2], 61 | (3, 4), 62 | {"attr_a": 5, "attr_b": 6}, 63 | {"attr_a": 7, "attr_b": 8, "not_exist_attr": 100}, 64 | {"attr_a": 9}, 65 | {"attr_b": 10}, 66 | {}, 67 | NamedTuple2(11, None), 68 | ], 69 | [[1, 2], [3, 4], [5, 6], [7, 8], [9, None], [None, 10], [None, None], [11, None]], 70 | ] 71 | ], 72 | ) 73 | def test_normal(self, attr_names, value, expected): 74 | assert RecordConvertor.to_records(attr_names, value) == expected 75 | 76 | @pytest.mark.parametrize( 77 | ["attr_names", "value", "expected"], 78 | [ 79 | [None, [5, 6], TypeError], 80 | [attrs_2, None, TypeError], 81 | [None, None, TypeError], 82 | ], 83 | ) 84 | def test_exception(self, attr_names, value, expected): 85 | with pytest.raises(expected): 86 | RecordConvertor.to_records(attr_names, value) 87 | 88 | @pytest.mark.parametrize( 89 | ["attr_names", "value"], 90 | [ 91 | [["a", "b"], [[5, 9223372036854775808]]], 92 | [["a", "b"], [{"a": -9223372036854775809, "b": 0}]], 93 | ], 94 | ) 95 | def test_exception_value(self, attr_names, value): 96 | with pytest.raises(OverflowError): 97 | RecordConvertor.to_records(attr_names, value) 98 | -------------------------------------------------------------------------------- /test/test_error.py: -------------------------------------------------------------------------------- 1 | from simplesqlite.error import OperationalError 2 | 3 | 4 | def test_operational_error_str_with_message(): 5 | error_message = "Test error message" 6 | error = OperationalError(message=error_message) 7 | assert str(error) == error_message 8 | 9 | 10 | def test_operational_error_str_without_message(): 11 | error = OperationalError() 12 | assert str(error) == "" 13 | -------------------------------------------------------------------------------- /test/test_from_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import os 6 | 7 | import pytest 8 | 9 | from simplesqlite import SimpleSQLite, SQLiteTableDataSanitizer 10 | 11 | 12 | class Test_SimpleSQLite_create_table_from_tabledata: 13 | @pytest.mark.parametrize( 14 | ["filename"], 15 | [ 16 | ["python - Wiktionary.html"], 17 | ], 18 | ) 19 | def test_smoke(self, tmpdir, filename): 20 | try: 21 | import pytablereader as ptr 22 | except ImportError: 23 | pytest.skip("requires pytablereader") 24 | 25 | p = tmpdir.join("tmp.db") 26 | con = SimpleSQLite(str(p), "w") 27 | 28 | test_data_file_path = os.path.join(os.path.dirname(__file__), "data", filename) 29 | loader = ptr.TableFileLoader(test_data_file_path) 30 | 31 | success_count = 0 32 | 33 | for table_data in loader.load(): 34 | if table_data.is_empty(): 35 | continue 36 | 37 | try: 38 | from pytablewriter import dumps_tabledata 39 | 40 | print(dumps_tabledata(table_data)) 41 | except ImportError: 42 | pass 43 | 44 | try: 45 | con.create_table_from_tabledata(SQLiteTableDataSanitizer(table_data).normalize()) 46 | success_count += 1 47 | except ValueError as e: 48 | print(e) 49 | 50 | con.commit() 51 | 52 | assert success_count > 0 53 | -------------------------------------------------------------------------------- /test/test_func.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import pytest 6 | 7 | from simplesqlite import ( 8 | NameValidationError, 9 | NullDatabaseConnectionError, 10 | append_table, 11 | connect_memdb, 12 | copy_table, 13 | ) 14 | from simplesqlite._func import validate_attr_name, validate_table_name 15 | 16 | from .fixture import ( # noqa: W0611 17 | TEST_TABLE_NAME, 18 | con_empty, 19 | con_mix, 20 | con_null, 21 | con_profile, 22 | con_ro, 23 | ) 24 | 25 | 26 | class Test_validate_table_name: 27 | @pytest.mark.parametrize(["value"], [["valid_table_name"], ["table_"], ["%CPU"]]) 28 | def test_normal(self, value): 29 | validate_table_name(value) 30 | 31 | @pytest.mark.parametrize( 32 | ["value", "expected"], 33 | [ 34 | [None, NameValidationError], 35 | ["", NameValidationError], 36 | ["table", NameValidationError], 37 | ["TABLE", NameValidationError], 38 | ["Table", NameValidationError], 39 | ], 40 | ) 41 | def test_exception(self, value, expected): 42 | with pytest.raises(expected): 43 | validate_table_name(value) 44 | 45 | 46 | class Test_validate_attr_name: 47 | @pytest.mark.parametrize(["value"], [["valid_attr_name"], ["attr_"], ["%CPU"]]) 48 | def test_normal(self, value): 49 | validate_attr_name(value) 50 | 51 | @pytest.mark.parametrize( 52 | ["value", "expected"], 53 | [ 54 | [None, NameValidationError], 55 | ["", NameValidationError], 56 | ["table", NameValidationError], 57 | ["TABLE", NameValidationError], 58 | ["Table", NameValidationError], 59 | ], 60 | ) 61 | def test_exception(self, value, expected): 62 | with pytest.raises(expected): 63 | validate_attr_name(value) 64 | 65 | 66 | class Test_append_table: 67 | def test_normal(self, con_mix, con_empty): 68 | assert append_table(src_con=con_mix, dst_con=con_empty, table_name=TEST_TABLE_NAME) 69 | 70 | result = con_mix.select(select="*", table_name=TEST_TABLE_NAME) 71 | src_data_matrix = result.fetchall() 72 | result = con_empty.select(select="*", table_name=TEST_TABLE_NAME) 73 | dst_data_matrix = result.fetchall() 74 | 75 | assert src_data_matrix == dst_data_matrix 76 | assert append_table(src_con=con_mix, dst_con=con_empty, table_name=TEST_TABLE_NAME) 77 | 78 | result = con_mix.select(select="*", table_name=TEST_TABLE_NAME) 79 | src_data_matrix = result.fetchall() 80 | result = con_empty.select(select="*", table_name=TEST_TABLE_NAME) 81 | dst_data_matrix = result.fetchall() 82 | 83 | assert src_data_matrix * 2 == dst_data_matrix 84 | 85 | def test_exception_mismatch_schema(self, con_mix, con_profile): 86 | with pytest.raises(ValueError): 87 | append_table(src_con=con_mix, dst_con=con_profile, table_name=TEST_TABLE_NAME) 88 | 89 | def test_exception_null_connection(self, con_mix, con_null): 90 | with pytest.raises(NullDatabaseConnectionError): 91 | append_table(src_con=con_mix, dst_con=con_null, table_name=TEST_TABLE_NAME) 92 | 93 | def test_exception_permission(self, con_mix, con_ro): 94 | with pytest.raises(IOError): 95 | append_table(src_con=con_mix, dst_con=con_ro, table_name=TEST_TABLE_NAME) 96 | 97 | 98 | class Test_copy_table: 99 | def test_normal(self, con_mix, con_empty): 100 | assert copy_table( 101 | src_con=con_mix, dst_con=con_empty, src_table_name=TEST_TABLE_NAME, dst_table_name="dst" 102 | ) 103 | 104 | result = con_mix.select(select="*", table_name=TEST_TABLE_NAME) 105 | src_data_matrix = result.fetchall() 106 | result = con_empty.select(select="*", table_name="dst") 107 | dst_data_matrix = result.fetchall() 108 | 109 | assert src_data_matrix == dst_data_matrix 110 | 111 | assert not copy_table( 112 | src_con=con_mix, 113 | dst_con=con_empty, 114 | src_table_name=TEST_TABLE_NAME, 115 | dst_table_name="dst", 116 | is_overwrite=False, 117 | ) 118 | assert copy_table( 119 | src_con=con_mix, 120 | dst_con=con_empty, 121 | src_table_name=TEST_TABLE_NAME, 122 | dst_table_name="dst", 123 | is_overwrite=True, 124 | ) 125 | 126 | 127 | class Test_connect_sqlite_db_mem: 128 | def test_normal(self): 129 | con_mem = connect_memdb() 130 | assert con_mem is not None 131 | assert con_mem.database_path == ":memory:" 132 | -------------------------------------------------------------------------------- /test/test_logger.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import pytest 6 | 7 | from simplesqlite import set_logger 8 | from simplesqlite._logger._null_logger import NullLogger 9 | 10 | 11 | class Test_set_logger: 12 | @pytest.mark.parametrize(["value"], [[True], [False]]) 13 | def test_smoke(self, value): 14 | set_logger(value) 15 | 16 | 17 | class Test_NullLogger: 18 | @pytest.mark.parametrize(["value"], [[True], [False]]) 19 | def test_smoke(self, value, monkeypatch): 20 | monkeypatch.setattr("simplesqlite._logger._logger.logger", NullLogger()) 21 | set_logger(value) 22 | -------------------------------------------------------------------------------- /test/test_orm.py: -------------------------------------------------------------------------------- 1 | from simplesqlite import connect_memdb 2 | from simplesqlite.model import Blob, Integer, Model, Real, Text 3 | from simplesqlite.query import Where 4 | 5 | 6 | class Hoge(Model): 7 | hoge_id = Integer() 8 | name = Text() 9 | 10 | 11 | class Foo(Model): 12 | foo_id = Integer(not_null=True) 13 | name = Text(not_null=True) 14 | value = Real(not_null=True) 15 | blob = Blob() 16 | 17 | 18 | def test_orm(): 19 | con = connect_memdb() 20 | 21 | Hoge.attach(con, is_hidden=True) 22 | Hoge.create() 23 | assert Hoge.fetch_num_records() == 0 24 | hoge_inputs = [Hoge(hoge_id=10, name="a"), Hoge(hoge_id=20, name="b")] 25 | for hoge_input in hoge_inputs: 26 | Hoge.insert(hoge_input) 27 | 28 | Foo.attach(con) 29 | Foo.create() 30 | foo_inputs = [Foo(foo_id=11, name="aq", value=0.1), Foo(foo_id=22, name="bb", value=1.11)] 31 | for foo_input in foo_inputs: 32 | Foo.insert(foo_input) 33 | 34 | assert Hoge.fetch_num_records() == 2 35 | for record, hoge_input in zip(Hoge.select(), hoge_inputs): 36 | assert record == hoge_input 37 | 38 | for record, foo_input in zip(Foo.select(), foo_inputs): 39 | assert record == foo_input 40 | 41 | result = Hoge.select(where=Where("hoge_id", 999)) 42 | assert len(list(result)) == 0 43 | -------------------------------------------------------------------------------- /test/test_pandas.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import pytest 6 | 7 | from simplesqlite import connect_memdb 8 | 9 | from ._common import print_test_result 10 | 11 | 12 | try: 13 | import pandas as pd 14 | 15 | PANDAS_IMPORT = True 16 | except ImportError: 17 | PANDAS_IMPORT = False 18 | 19 | 20 | @pytest.mark.skipif(not PANDAS_IMPORT, reason="required package not found") 21 | class Test_fromto_pandas_dataframe: 22 | def test_normal(self): 23 | con = connect_memdb() 24 | dataframe = pd.DataFrame( 25 | [[0, 0.1, "a"], [1, 1.1, "bb"], [2, 2.2, "ccc"]], columns=["id", "value", "name"] 26 | ) 27 | table_name = "tablename" 28 | 29 | con.create_table_from_dataframe(dataframe, table_name) 30 | 31 | actual_all = con.select_as_dataframe(table_name=table_name) 32 | print_test_result(expected=dataframe, actual=actual_all) 33 | 34 | assert actual_all.equals(dataframe) 35 | 36 | select_columns = ["value", "name"] 37 | actual_part = con.select_as_dataframe(table_name=table_name, columns=select_columns) 38 | assert actual_part.equals( 39 | pd.DataFrame([[0.1, "a"], [1.1, "bb"], [2.2, "ccc"]], columns=select_columns) 40 | ) 41 | 42 | def test_normal_include_datetime(self): 43 | con = connect_memdb() 44 | table_name = "table_w_datetime" 45 | 46 | dataframe = pd.DataFrame( 47 | [ 48 | ["2020-03-25 15:24:00-04:00", 0, 0.1, "a"], 49 | ["2020-03-25 15:25:00-04:00", 1, 1.1, "bb"], 50 | ["2020-03-25 15:30:00-04:00", 2, 2.2, "ccc"], 51 | ], 52 | columns=["timestamp", "id", "value", "name"], 53 | ) 54 | dataframe["timestamp"] = pd.to_datetime(dataframe["timestamp"]) 55 | con.create_table_from_dataframe(dataframe, table_name=table_name) 56 | 57 | actual_all = con.select_as_dataframe(table_name=table_name) 58 | print_test_result(expected=dataframe, actual=actual_all) 59 | -------------------------------------------------------------------------------- /test/test_sanitizer.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import pytest 6 | from tabledata import TableData 7 | from typepy import String 8 | 9 | from simplesqlite import NameValidationError, SQLiteTableDataSanitizer, connect_memdb 10 | from simplesqlite._sanitizer import is_multibyte_str 11 | 12 | from ._common import print_test_result 13 | 14 | 15 | nan = float("nan") 16 | inf = float("inf") 17 | 18 | 19 | class Test_is_multibyte_str: 20 | @pytest.mark.parametrize( 21 | ["value", "expected"], 22 | [ 23 | ["吾輩は猫である", True], 24 | ["abcdef", False], 25 | [ 26 | "RKBTqn1G9HIZ9onY9mCklj3+8ye7WBmu0xKMqp3ORT3pMgR5m73VXAR/5YrTZTGer" 27 | "nMYLCPYdwIMewFY+6xOZmWwCrXjfw3sO2dYLubh9EIMrc/XEvAhMFd969G2yQkyFT" 28 | "Nf9M8Ag94QCuBk51yQLSbxgmxJTqEw6bdC4gNTI44=", 29 | False, 30 | ], 31 | [None, False], 32 | ["", False], 33 | [True, False], 34 | [[], False], 35 | [1, False], 36 | ], 37 | ) 38 | def test_normal(self, value, expected): 39 | assert is_multibyte_str(value) == expected 40 | 41 | 42 | class Test_SQLiteTableDataSanitizer: 43 | @pytest.mark.parametrize( 44 | ["table_name", "headers", "records", "expected"], 45 | [ 46 | [ 47 | "normal", 48 | ["a", "b_c"], 49 | [[1, 2], [3, 4]], 50 | TableData("normal", ["a", "b_c"], [[1, 2], [3, 4]]), 51 | ], 52 | [ 53 | "underscore_char", 54 | ["data", "_data", "data_", "_data_"], 55 | [[1, 2, 3, 4], [11, 12, 13, 14]], 56 | TableData( 57 | "underscore_char", 58 | ["data", "_data", "data_", "_data_"], 59 | [[1, 2, 3, 4], [11, 12, 13, 14]], 60 | ), 61 | ], 62 | [ 63 | "OFFSET", 64 | ["abort", "ASC"], 65 | [[1, 2], [3, 4]], 66 | TableData("OFFSET", ["abort", "ASC"], [[1, 2], [3, 4]]), 67 | ], 68 | [ 69 | "missing_all_header", 70 | [], 71 | [[1, 2], [3, 4]], 72 | TableData("missing_all_header", ["A", "B"], [[1, 2], [3, 4]]), 73 | ], 74 | [ 75 | "num_header", 76 | [1, 123456789], 77 | [[1, 2], [3, 4]], 78 | TableData("num_header", ["1", "123456789"], [[1, 2], [3, 4]]), 79 | ], 80 | [ 81 | "missing_part_of_header", 82 | ["", "bb", None], 83 | [[1, 2, 3]], 84 | TableData("missing_part_of_header", ["A", "bb", "C"], [[1, 2, 3]]), 85 | ], 86 | [ 87 | "avoid_duplicate_default_header_0", 88 | ["", "a", None], 89 | [[1, 2, 3]], 90 | TableData("avoid_duplicate_default_header_0", ["B", "a", "C"], [[1, 2, 3]]), 91 | ], 92 | [ 93 | "avoid_duplicate_default_header_1", 94 | ["", "A", "B", "c", ""], 95 | [[1, 2, 3, 4, 5]], 96 | TableData( 97 | "avoid_duplicate_default_header_1", ["D", "A", "B", "c", "E"], [[1, 2, 3, 4, 5]] 98 | ), 99 | ], 100 | [ 101 | r"@a!b\c#d$e%f&g'h(i)j_", 102 | [r"a!bc#d$e%f&g'h(i)j", r"k@l[m]n{o}p;q:r,s.t/u", "a b"], 103 | [[1, 2, 3], [11, 12, 13]], 104 | TableData( 105 | "a_b_c_d_e_f_g_h_i_j", 106 | ["a!bc#d$e%f&g_h(i)j", "k@l[m]n{o}p;q:r_s.t/u", "a b"], 107 | [[1, 2, 3], [11, 12, 13]], 108 | ), 109 | ], 110 | [ 111 | # SQLite reserved keywords 112 | "ALL", 113 | ["and", "Index"], 114 | [[1, 2], [3, 4]], 115 | TableData("rename_ALL", ["and", "Index"], [[1, 2], [3, 4]]), 116 | ], 117 | [ 118 | "invalid'tn", 119 | ["in'valid", "ALL"], 120 | [[1, 2], [3, 4]], 121 | TableData("invalid_tn", ["in_valid", "ALL"], [[1, 2], [3, 4]]), 122 | ], 123 | [ 124 | "Python (programming language) - Wikipedia, the free encyclopedia.html", 125 | ["a b", "c d"], 126 | [[1, 2], [3, 4]], 127 | TableData( 128 | "Python_programming_language_Wikipedia_the_free_encyclopedia_html", 129 | ["a b", "c d"], 130 | [[1, 2], [3, 4]], 131 | ), 132 | ], 133 | [ 134 | "multibyte csv", 135 | ["姓", "名", "生年月日", "郵便番号", "住所", "電話番号"], 136 | [ 137 | [ 138 | "山田", 139 | "太郎", 140 | "2001/1/1", 141 | "100-0002", 142 | "東京都千代田区皇居外苑", 143 | "03-1234-5678", 144 | ], 145 | [ 146 | "山田", 147 | "次郎", 148 | "2001/1/2", 149 | "251-0036", 150 | "神奈川県藤沢市江の島1丁目", 151 | "03-9999-9999", 152 | ], 153 | ], 154 | TableData( 155 | "multibyte_csv", 156 | ["姓", "名", "生年月日", "郵便番号", "住所", "電話番号"], 157 | [ 158 | [ 159 | "山田", 160 | "太郎", 161 | "2001/1/1", 162 | "100-0002", 163 | "東京都千代田区皇居外苑", 164 | "03-1234-5678", 165 | ], 166 | [ 167 | "山田", 168 | "次郎", 169 | "2001/1/2", 170 | "251-0036", 171 | "神奈川県藤沢市江の島1丁目", 172 | "03-9999-9999", 173 | ], 174 | ], 175 | ), 176 | ], 177 | ], 178 | ) 179 | def test_normal(self, table_name, headers, records, expected): 180 | new_tabledata = SQLiteTableDataSanitizer( 181 | TableData(table_name, headers, records) 182 | ).normalize() 183 | 184 | try: 185 | from pytablewriter import dumps_tabledata 186 | 187 | print_test_result( 188 | expected=dumps_tabledata(expected), actual=dumps_tabledata(new_tabledata) 189 | ) 190 | except ImportError: 191 | pass 192 | 193 | con = connect_memdb() 194 | con.create_table_from_tabledata(new_tabledata) 195 | assert con.select_as_tabledata(new_tabledata.table_name) == expected 196 | 197 | assert new_tabledata.equals(expected) 198 | 199 | @pytest.mark.parametrize( 200 | ["table_name", "headers", "records", "type_hints", "expecte_col_types", "expecte_data"], 201 | [ 202 | [ 203 | "w/ type inference", 204 | ["a", "b_c"], 205 | [ 206 | [1, 2], 207 | [3, 4], 208 | ], 209 | [String], 210 | ["STRING", "INTEGER"], 211 | TableData( 212 | "w_type_inference", 213 | ["a", "b_c"], 214 | [ 215 | ["1", 2], 216 | ["3", 4], 217 | ], 218 | ), 219 | ] 220 | ], 221 | ) 222 | def test_normal_type_hints( 223 | self, table_name, headers, records, type_hints, expecte_col_types, expecte_data 224 | ): 225 | new_tabledata = SQLiteTableDataSanitizer( 226 | TableData(table_name, headers, records, type_hints=type_hints) 227 | ).normalize() 228 | 229 | actual_col_types = [col_dp.typename for col_dp in new_tabledata.column_dp_list] 230 | assert actual_col_types == expecte_col_types 231 | 232 | con = connect_memdb() 233 | con.create_table_from_tabledata(new_tabledata) 234 | assert con.select_as_tabledata(new_tabledata.table_name) == expecte_data 235 | 236 | @pytest.mark.parametrize( 237 | [ 238 | "table_name", 239 | "headers", 240 | "records", 241 | "is_type_inference", 242 | "expecte_col_types", 243 | "expecte_data", 244 | ], 245 | [ 246 | [ 247 | "w/ type inference", 248 | ["a", "b_c"], 249 | [ 250 | ["1", "2"], 251 | ["3", "4"], 252 | ], 253 | True, 254 | ["INTEGER", "INTEGER"], 255 | TableData( 256 | "w_type_inference", 257 | ["a", "b_c"], 258 | [ 259 | [1, 2], 260 | [3, 4], 261 | ], 262 | ), 263 | ], 264 | [ 265 | "w/o type inference", 266 | ["a", "b_c"], 267 | [ 268 | ["1", "2"], 269 | ["3", "4"], 270 | ], 271 | False, 272 | ["STRING", "STRING"], 273 | TableData( 274 | "w_o_type_inference", 275 | ["a", "b_c"], 276 | [ 277 | ["1", "2"], 278 | ["3", "4"], 279 | ], 280 | ), 281 | ], 282 | ], 283 | ) 284 | def test_normal_type_inference( 285 | self, table_name, headers, records, is_type_inference, expecte_col_types, expecte_data 286 | ): 287 | new_tabledata = SQLiteTableDataSanitizer( 288 | TableData(table_name, headers, records), is_type_inference=is_type_inference 289 | ).normalize() 290 | 291 | actual_col_types = [col_dp.typename for col_dp in new_tabledata.column_dp_list] 292 | print(is_type_inference, expecte_col_types, actual_col_types) 293 | assert actual_col_types == expecte_col_types 294 | 295 | con = connect_memdb() 296 | con.create_table_from_tabledata(new_tabledata) 297 | assert con.select_as_tabledata(new_tabledata.table_name) == expecte_data 298 | 299 | @pytest.mark.parametrize( 300 | ["table_name", "headers", "records", "expected"], 301 | [ 302 | ["", ["a", "b"], [], NameValidationError], 303 | [None, ["a", "b"], [], NameValidationError], 304 | ["dummy", [], [], ValueError], 305 | ], 306 | ) 307 | def test_exception_invalid_data(self, table_name, headers, records, expected): 308 | with pytest.raises(expected): 309 | SQLiteTableDataSanitizer(TableData(table_name, headers, records)).normalize() 310 | 311 | 312 | class Test_SQLiteTableDataSanitizer_dup_col_handler: 313 | @pytest.mark.parametrize( 314 | ["table_name", "headers", "dup_col_handler", "expected"], 315 | [ 316 | [ 317 | "all attrs are duplicated", 318 | ["A", "A", "A", "A", "A"], 319 | "rename", 320 | TableData("all_attrs_are_duplicated", ["A", "A_1", "A_2", "A_3", "A_4"], []), 321 | ], 322 | [ 323 | "recursively duplicated attrs", 324 | ["A", "A", "A_1", "A_1", "A_2", "A_1_1", "A_1_1"], 325 | "recursively_duplicated_attrs", 326 | TableData( 327 | "recursively_duplicated_attrs", 328 | ["A", "A_3", "A_1", "A_1_2", "A_2", "A_1_1", "A_1_1_1"], 329 | [], 330 | ), 331 | ], 332 | ], 333 | ) 334 | def test_normal_(self, table_name, headers, dup_col_handler, expected): 335 | new_tabledata = SQLiteTableDataSanitizer( 336 | TableData(table_name, headers, []), dup_col_handler=dup_col_handler 337 | ).normalize() 338 | 339 | try: 340 | from pytablewriter import dumps_tabledata 341 | 342 | print_test_result( 343 | expected=dumps_tabledata(expected), actual=dumps_tabledata(new_tabledata) 344 | ) 345 | except ImportError: 346 | pass 347 | 348 | assert new_tabledata.equals(expected) 349 | 350 | @pytest.mark.parametrize( 351 | ["table_name", "headers", "expected"], 352 | [ 353 | ["duplicate columns", ["a", "a"], ValueError], 354 | ["duplicate columns", ["AA", "b", "AA"], ValueError], 355 | ], 356 | ) 357 | def test_exception(self, table_name, headers, expected): 358 | with pytest.raises(expected): 359 | SQLiteTableDataSanitizer( 360 | TableData(table_name, headers, []), dup_col_handler="error" 361 | ).normalize() 362 | -------------------------------------------------------------------------------- /test/test_sqlquery.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. codeauthor:: Tsuyoshi Hombashi 3 | """ 4 | 5 | import pytest 6 | 7 | from simplesqlite.query import And, Or, Set, Where 8 | from simplesqlite.sqlquery import SqlQuery 9 | 10 | 11 | nan = float("nan") 12 | inf = float("inf") 13 | 14 | 15 | class Test_SqlQuery_make_update: 16 | @pytest.mark.parametrize( 17 | ["table", "set_query", "where", "expected"], 18 | [ 19 | ["A", "B=1", None, "UPDATE A SET B=1"], 20 | ["A", "B=1", Where("C", 1, ">").to_query(), "UPDATE A SET B=1 WHERE C > 1"], 21 | ["A", "B=1", Where("C", 1, ">"), "UPDATE A SET B=1 WHERE C > 1"], 22 | [ 23 | "A", 24 | "B=1", 25 | And([Where("C", 1, ">"), Where("D", 10)]), 26 | "UPDATE A SET B=1 WHERE C > 1 AND D = 10", 27 | ], 28 | [ 29 | "A", 30 | "B=1", 31 | Or([Where("C", 1, ">"), Where("D", 10)]), 32 | "UPDATE A SET B=1 WHERE C > 1 OR D = 10", 33 | ], 34 | [ 35 | "A", 36 | [Set("B1", 10), Set("B2", 20)], 37 | Where("D", 10), 38 | "UPDATE A SET [B1] = 10, [B2] = 20 WHERE D = 10", 39 | ], 40 | ], 41 | ) 42 | def test_normal(self, table, set_query, where, expected): 43 | assert SqlQuery.make_update(table, set_query, where) == expected 44 | 45 | @pytest.mark.parametrize( 46 | ["table", "set_query", "where", "expected"], 47 | [ 48 | [None, "B=1", None, ValueError], 49 | ["", "B=1", None, ValueError], 50 | ["A", "", None, ValueError], 51 | ], 52 | ) 53 | def test_exception(self, table, set_query, where, expected): 54 | with pytest.raises(expected): 55 | SqlQuery.make_update(table, set_query, where) 56 | 57 | 58 | class Test_SqlQuery_make_where_in: 59 | @pytest.mark.parametrize( 60 | ["key", "value", "expected"], [["key", ["attr_a", "attr_b"], "key IN ('attr_a', 'attr_b')"]] 61 | ) 62 | def test_normal(self, key, value, expected): 63 | assert SqlQuery.make_where_in(key, value) == expected 64 | 65 | @pytest.mark.parametrize( 66 | ["key", "value", "expected"], 67 | [ 68 | ["key", None, TypeError], 69 | ["key", 1, TypeError], 70 | [None, ["attr_a", "attr_b"], TypeError], 71 | [None, None, TypeError], 72 | ], 73 | ) 74 | def test_exception(self, key, value, expected): 75 | with pytest.raises(expected): 76 | SqlQuery.make_where_in(key, value) 77 | 78 | 79 | class Test_SqlQuery_make_where_not_in: 80 | @pytest.mark.parametrize( 81 | ["key", "value", "expected"], 82 | [["key", ["attr_a", "attr_b"], "key NOT IN ('attr_a', 'attr_b')"]], 83 | ) 84 | def test_normal(self, key, value, expected): 85 | assert SqlQuery.make_where_not_in(key, value) == expected 86 | 87 | @pytest.mark.parametrize( 88 | ["key", "value", "expected"], 89 | [ 90 | ["key", None, TypeError], 91 | ["key", 1, TypeError], 92 | [None, ["attr_a", "attr_b"], TypeError], 93 | [None, None, TypeError], 94 | ], 95 | ) 96 | def test_exception(self, key, value, expected): 97 | with pytest.raises(expected): 98 | SqlQuery.make_where_not_in(key, value) 99 | -------------------------------------------------------------------------------- /test/test_validator.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | import pytest 4 | from pathvalidate import unprintable_ascii_chars 5 | from pathvalidate.error import ErrorReason, ValidationError 6 | 7 | from simplesqlite._validator import validate_sqlite_attr_name, validate_sqlite_table_name 8 | 9 | 10 | __SQLITE_VALID_RESERVED_KEYWORDS = [ 11 | "ABORT", 12 | "ACTION", 13 | "AFTER", 14 | "ANALYZE", 15 | "ASC", 16 | "ATTACH", 17 | "BEFORE", 18 | "BEGIN", 19 | "BY", 20 | "CASCADE", 21 | "CAST", 22 | "COLUMN", 23 | "CONFLICT", 24 | "CROSS", 25 | "CURRENT_DATE", 26 | "CURRENT_TIME", 27 | "CURRENT_TIMESTAMP", 28 | "DATABASE", 29 | "DEFERRED", 30 | "DESC", 31 | "DETACH", 32 | "EACH", 33 | "END", 34 | "EXCLUSIVE", 35 | "EXPLAIN", 36 | "FAIL", 37 | "FOR", 38 | "FULL", 39 | "GLOB", 40 | "IGNORE", 41 | "IMMEDIATE", 42 | "INDEXED", 43 | "INITIALLY", 44 | "INNER", 45 | "INSTEAD", 46 | "KEY", 47 | "LEFT", 48 | "LIKE", 49 | "MATCH", 50 | "NATURAL", 51 | "NO", 52 | "OF", 53 | "OFFSET", 54 | "OUTER", 55 | "PLAN", 56 | "PRAGMA", 57 | "QUERY", 58 | "RAISE", 59 | "RECURSIVE", 60 | "REGEXP", 61 | "REINDEX", 62 | "RELEASE", 63 | "RENAME", 64 | "REPLACE", 65 | "RESTRICT", 66 | "RIGHT", 67 | "ROLLBACK", 68 | "ROW", 69 | "SAVEPOINT", 70 | "TEMP", 71 | "TEMPORARY", 72 | "TRIGGER", 73 | "VACUUM", 74 | "VIEW", 75 | "VIRTUAL", 76 | "WITH", 77 | "WITHOUT", 78 | ] 79 | __SQLITE_INVALID_RESERVED_KEYWORDS = [ 80 | "ADD", 81 | "ALL", 82 | "ALTER", 83 | "AND", 84 | "AS", 85 | "AUTOINCREMENT", 86 | "BETWEEN", 87 | "CASE", 88 | "CHECK", 89 | "COLLATE", 90 | "COMMIT", 91 | "CONSTRAINT", 92 | "CREATE", 93 | "DEFAULT", 94 | "DEFERRABLE", 95 | "DELETE", 96 | "DISTINCT", 97 | "DROP", 98 | "ELSE", 99 | "ESCAPE", 100 | "EXCEPT", 101 | "EXISTS", 102 | "FOREIGN", 103 | "FROM", 104 | "GROUP", 105 | "HAVING", 106 | "IN", 107 | "INDEX", 108 | "INSERT", 109 | "INTERSECT", 110 | "INTO", 111 | "IS", 112 | "ISNULL", 113 | "JOIN", 114 | "LIMIT", 115 | "NOT", 116 | "NOTNULL", 117 | "NULL", 118 | "ON", 119 | "OR", 120 | "ORDER", 121 | "PRIMARY", 122 | "REFERENCES", 123 | "SELECT", 124 | "SET", 125 | "TABLE", 126 | "THEN", 127 | "TO", 128 | "TRANSACTION", 129 | "UNION", 130 | "UNIQUE", 131 | "UPDATE", 132 | "USING", 133 | "VALUES", 134 | "WHEN", 135 | "WHERE", 136 | ] 137 | 138 | VALID_RESERVED_KEYWORDS_TABLE_UPPER = __SQLITE_VALID_RESERVED_KEYWORDS 139 | INVALID_RESERVED_KEYWORDS_TABLE_UPPER = __SQLITE_INVALID_RESERVED_KEYWORDS + ["IF"] 140 | VALID_RESERVED_KEYWORDS_TABLE_LOWER = [ 141 | keyword.lower() for keyword in VALID_RESERVED_KEYWORDS_TABLE_UPPER 142 | ] 143 | INVALID_RESERVED_KEYWORDS_TABLE_LOWER = [ 144 | keyword.lower() for keyword in INVALID_RESERVED_KEYWORDS_TABLE_UPPER 145 | ] 146 | 147 | VALID_RESERVED_KEYWORDS_ATTR_UPPER = __SQLITE_VALID_RESERVED_KEYWORDS + ["IF"] 148 | INVALID_RESERVED_KEYWORDS_ATTR_UPPER = __SQLITE_INVALID_RESERVED_KEYWORDS 149 | VALID_RESERVED_KEYWORDS_ATTR_LOWER = [ 150 | keyword.lower() for keyword in VALID_RESERVED_KEYWORDS_ATTR_UPPER 151 | ] 152 | INVALID_RESERVED_KEYWORDS_ATTR_LOWER = [ 153 | keyword.lower() for keyword in INVALID_RESERVED_KEYWORDS_ATTR_UPPER 154 | ] 155 | 156 | UTF8_WORDS = [["あいうえお"], ["属性"]] 157 | 158 | 159 | class Test_validate_sqlite_table_name: 160 | @pytest.mark.parametrize( 161 | ["value"], 162 | [ 163 | [f"{keyword}a"] 164 | for keyword in ( 165 | VALID_RESERVED_KEYWORDS_TABLE_UPPER 166 | + INVALID_RESERVED_KEYWORDS_TABLE_UPPER 167 | + VALID_RESERVED_KEYWORDS_ATTR_UPPER 168 | + INVALID_RESERVED_KEYWORDS_ATTR_UPPER 169 | ) 170 | ], 171 | ) 172 | def test_normal_ascii(self, value): 173 | validate_sqlite_table_name(value) 174 | 175 | @pytest.mark.parametrize(["value"], UTF8_WORDS) 176 | def test_normal_utf8(self, value): 177 | validate_sqlite_table_name(value) 178 | 179 | @pytest.mark.parametrize( 180 | ["value"], [[first_char + "hoge123"] for first_char in string.digits + "%#!-*"] 181 | ) 182 | def test_normal_non_alphabet_first_char(self, value): 183 | validate_sqlite_table_name(value) 184 | 185 | @pytest.mark.parametrize( 186 | ["value"], 187 | [[f"a{invalid_c}b"] for invalid_c in unprintable_ascii_chars] 188 | + [[f"テ{invalid_c}!!スト"] for invalid_c in unprintable_ascii_chars], 189 | ) 190 | def test_exception_invalid_win_char(self, value): 191 | try: 192 | validate_sqlite_table_name(value) 193 | except ValidationError as e: 194 | assert e.reason == ErrorReason.INVALID_CHARACTER 195 | 196 | @pytest.mark.parametrize( 197 | ["value", "expected"], 198 | [[None, ValidationError], ["", ValidationError], [1, TypeError], [True, TypeError]], 199 | ) 200 | def test_exception_type(self, value, expected): 201 | with pytest.raises(expected): 202 | validate_sqlite_table_name(value) 203 | 204 | @pytest.mark.parametrize( 205 | ["value"], 206 | [ 207 | [reserved_keyword] 208 | for reserved_keyword in VALID_RESERVED_KEYWORDS_TABLE_UPPER 209 | + VALID_RESERVED_KEYWORDS_TABLE_LOWER 210 | ], 211 | ) 212 | def test_exception_reserved_valid(self, value): 213 | try: 214 | validate_sqlite_table_name(value) 215 | except ValidationError as e: 216 | assert e.reason == ErrorReason.RESERVED_NAME 217 | assert e.reusable_name 218 | 219 | @pytest.mark.parametrize( 220 | ["value"], 221 | [ 222 | [reserved_keyword] 223 | for reserved_keyword in INVALID_RESERVED_KEYWORDS_TABLE_UPPER 224 | + INVALID_RESERVED_KEYWORDS_TABLE_LOWER 225 | ], 226 | ) 227 | def test_exception_reserved_invalid_name(self, value): 228 | try: 229 | validate_sqlite_table_name(value) 230 | except ValidationError as e: 231 | assert e.reason == ErrorReason.RESERVED_NAME 232 | assert e.reusable_name is False 233 | 234 | 235 | class Test_validate_sqlite_attr_name: 236 | @pytest.mark.parametrize( 237 | ["value"], 238 | [ 239 | [f"{keyword}a"] 240 | for keyword in ( 241 | VALID_RESERVED_KEYWORDS_TABLE_UPPER 242 | + INVALID_RESERVED_KEYWORDS_TABLE_UPPER 243 | + VALID_RESERVED_KEYWORDS_ATTR_UPPER 244 | + INVALID_RESERVED_KEYWORDS_ATTR_UPPER 245 | + ["_"] 246 | ) 247 | ], 248 | ) 249 | def test_normal_ascii(self, value): 250 | validate_sqlite_attr_name(value) 251 | 252 | @pytest.mark.parametrize(["value"], UTF8_WORDS) 253 | def test_normal_utf8(self, value): 254 | validate_sqlite_attr_name(value) 255 | 256 | @pytest.mark.parametrize( 257 | ["value"], [[first_char + "hoge123"] for first_char in string.digits + "%#!-*"] 258 | ) 259 | def test_normal_non_alphabet_first_char(self, value): 260 | validate_sqlite_attr_name(value) 261 | 262 | @pytest.mark.parametrize( 263 | ["value"], 264 | [[f"a{invalid_c}b"] for invalid_c in unprintable_ascii_chars] 265 | + [[f"テ{invalid_c}!!スト"] for invalid_c in unprintable_ascii_chars], 266 | ) 267 | def test_exception_invalid_win_char(self, value): 268 | try: 269 | validate_sqlite_table_name(value) 270 | except ValidationError as e: 271 | assert e.reason == ErrorReason.INVALID_CHARACTER 272 | 273 | @pytest.mark.parametrize( 274 | ["value", "expected"], 275 | [[None, ValidationError], ["", ValidationError], [1, TypeError], [True, TypeError]], 276 | ) 277 | def test_exception_type(self, value, expected): 278 | with pytest.raises(expected): 279 | validate_sqlite_attr_name(value) 280 | 281 | @pytest.mark.parametrize( 282 | ["value"], 283 | [ 284 | [reserved_keyword] 285 | for reserved_keyword in VALID_RESERVED_KEYWORDS_ATTR_UPPER 286 | + VALID_RESERVED_KEYWORDS_ATTR_LOWER 287 | ], 288 | ) 289 | def test_exception_reserved_valid(self, value): 290 | try: 291 | validate_sqlite_attr_name(value) 292 | except ValidationError as e: 293 | assert e.reason == ErrorReason.RESERVED_NAME 294 | assert e.reusable_name 295 | 296 | @pytest.mark.parametrize( 297 | ["value"], 298 | [ 299 | [reserved_keyword] 300 | for reserved_keyword in INVALID_RESERVED_KEYWORDS_ATTR_UPPER 301 | + INVALID_RESERVED_KEYWORDS_ATTR_LOWER 302 | ], 303 | ) 304 | def test_exception_reserved_invalid_name(self, value): 305 | try: 306 | validate_sqlite_attr_name(value) 307 | except ValidationError as e: 308 | assert e.reason == ErrorReason.RESERVED_NAME 309 | assert e.reusable_name is False 310 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{39,310,311,312,313} 4 | pypy3 5 | build 6 | cov 7 | docs 8 | lint 9 | fmt 10 | readme 11 | 12 | [testenv] 13 | passenv = * 14 | extras = 15 | test 16 | commands = 17 | pytest {posargs} 18 | 19 | [testenv:build] 20 | deps = 21 | build>=1 22 | twine 23 | wheel 24 | commands = 25 | python -m build 26 | twine check dist/*.whl dist/*.tar.gz 27 | 28 | [testenv:clean] 29 | skip_install = true 30 | deps = 31 | cleanpy>=0.4 32 | commands = 33 | cleanpy --all --exclude-envs . 34 | 35 | [testenv:cov] 36 | extras = 37 | test 38 | deps = 39 | coverage[toml]>=5 40 | commands = 41 | coverage run -m pytest {posargs:-vv} 42 | coverage report -m 43 | 44 | [testenv:docs] 45 | extras = 46 | docs 47 | commands = 48 | sphinx-build docs/ docs/_build 49 | 50 | [testenv:fmt] 51 | skip_install = true 52 | deps = 53 | autoflake>=2 54 | isort>=5 55 | ruff>=0.8 56 | commands = 57 | autoflake --in-place --recursive --remove-all-unused-imports . 58 | isort . 59 | ruff format 60 | 61 | [testenv:lint] 62 | extras = 63 | logging 64 | deps = 65 | codespell>=2 66 | pandas 67 | pyright>=1.1 68 | pytablereader 69 | releasecmd 70 | ruff>=0.8 71 | commands = 72 | pyright simplesqlite setup.py 73 | codespell simplesqlite docs/pages test -q2 --check-filenames --ignore-words-list te -x "test/data/python - Wiktionary.html" 74 | ruff format --check setup.py test simplesqlite 75 | ruff check setup.py simplesqlite 76 | 77 | [testenv:lint-examples] 78 | extras = 79 | logging 80 | deps = 81 | codespell>=2 82 | pandas 83 | pyright>=1.1 84 | pytablereader 85 | ruff>=0.8 86 | XlsxWriter 87 | commands = 88 | pyright sample 89 | codespell sample -q2 --check-filenames 90 | ruff format --check sample 91 | ruff check sample 92 | 93 | [testenv:readme] 94 | changedir = docs 95 | skip_install = true 96 | deps = 97 | path 98 | readmemaker>=1.1.0 99 | commands = 100 | python make_readme.py 101 | 102 | [testenv:release] 103 | deps = 104 | releasecmd 105 | commands = 106 | python setup.py release --sign --skip-uploading --verbose 107 | --------------------------------------------------------------------------------