diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a274dca..c257837 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,4 +1,4 @@ -name: "release" +name: "Release" on: release: types: [published] @@ -6,9 +6,9 @@ on: permissions: contents: read jobs: - build-ubuntu: - name: Build ubuntu-20.04 - runs-on: ubuntu-20.04 + build-ubuntu-extension: + name: Build ubuntu + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - uses: actions/cache@v3 @@ -29,7 +29,25 @@ jobs: with: name: sqlite-regex-ubuntu path: dist/release/regex0.so - build-macos: + build-ubuntu-python: + runs-on: ubuntu-18.04 + needs: [build-ubuntu-extension] + steps: + - uses: actions/checkout@v3 + - name: Download workflow artifacts + uses: actions/download-artifact@v3 + with: + name: sqlite-regex-ubuntu + path: dist/release/ + - uses: actions/setup-python@v3 + - run: pip install wheel + - run: make python-release + - run: make datasette-release + - uses: actions/upload-artifact@v3 + with: + name: sqlite-regex-ubuntu-wheels + path: dist/release/wheels/*.whl + build-macos-extension: name: Build macos-latest runs-on: macos-latest steps: @@ -52,7 +70,25 @@ jobs: with: name: sqlite-regex-macos path: dist/release/regex0.dylib - build-macos-arm: + build-macos-python: + runs-on: macos-latest + needs: [build-macos-extension] + steps: + - uses: actions/checkout@v3 + - name: Download workflow artifacts + uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos + path: dist/release/ + - uses: actions/setup-python@v3 + - run: pip install wheel + - run: make python-release + - run: make datasette-release + - uses: actions/upload-artifact@v3 + with: + name: sqlite-regex-macos-wheels + path: dist/release/wheels/*.whl + build-macos-arm-extension: name: Build macos-latest with arm runs-on: macos-latest steps: @@ -70,14 +106,31 @@ jobs: with: toolchain: stable - run: rustup target add aarch64-apple-darwin - - run: make target=aarch64-apple-darwin loadable - #- run: make test + - run: make loadable-release target=aarch64-apple-darwin - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: sqlite-regex-macos-arm - path: dist/debug/regex0.dylib - build-windows: + path: dist/release/regex0.dylib + build-macos-arm-python: + runs-on: macos-latest + needs: [build-macos-arm-extension] + steps: + - uses: actions/checkout@v3 + - name: Download workflow artifacts + uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos-arm + path: dist/release/ + - uses: actions/setup-python@v3 + - run: pip install wheel + - run: make python-release IS_MACOS_ARM=1 + - run: make datasette-release + - uses: actions/upload-artifact@v3 + with: + name: sqlite-regex-macos-arm-wheels + path: dist/release/wheels/*.whl + build-windows-extension: name: Build windows-latest runs-on: windows-latest steps: @@ -100,9 +153,33 @@ jobs: with: name: sqlite-regex-windows path: dist/release/regex0.dll - upload: - name: upload - needs: [build-macos, build-macos-arm, build-ubuntu, build-windows] + build-windows-python: + runs-on: windows-latest + needs: [build-windows-extension] + steps: + - uses: actions/checkout@v3 + - name: Download workflow artifacts + uses: actions/download-artifact@v3 + with: + name: sqlite-regex-windows + path: dist/release/ + - uses: actions/setup-python@v3 + - run: pip install wheel + - run: make python-release + - run: make datasette-release + - uses: actions/upload-artifact@v3 + with: + name: sqlite-regex-windows-wheels + path: dist/release/wheels/*.whl + upload-release-assets: + name: Upload release assets + needs: + [ + build-macos-extension, + build-macos-arm-extension, + build-ubuntu-extension, + build-windows-extension, + ] permissions: contents: write runs-on: ubuntu-latest @@ -110,9 +187,40 @@ jobs: - uses: actions/checkout@v2 - name: Download workflow artifacts uses: actions/download-artifact@v2 + - run: npm install tar-fs - uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const script = require('.github/workflows/upload.js') await script({github, context}) + upload_pypi: + needs: + [ + build-ubuntu-python, + build-macos-python, + build-macos-arm-python, + build-windows-python, + ] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-windows-wheels + path: dist + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-ubuntu-wheels + path: dist + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos-wheels + path: dist + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos-arm-wheels + path: dist + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + skip_existing: true diff --git a/.github/workflows/rename-wheels.py b/.github/workflows/rename-wheels.py new file mode 100644 index 0000000..131c72e --- /dev/null +++ b/.github/workflows/rename-wheels.py @@ -0,0 +1,36 @@ +# This file is a small utility that rename all .whl files in a given directory +# and "generalizes" them. The wheels made by python/sqlite_ulid contain the +# pre-compiled sqlite extension, but those aren't bound by a specfic Python +# runtime or version, that other wheels might be. So, this file will rename +# those wheels to be "generalized", like replacing "c37-cp37" to "py3-none". +import sys +import os +from pathlib import Path + +wheel_dir = sys.argv[1] + +is_macos_arm_build = '--is-macos-arm' in sys.argv + +for filename in os.listdir(wheel_dir): + filename = Path(wheel_dir, filename) + if not filename.suffix == '.whl': + continue + new_filename = (filename.name + .replace('cp37-cp37', 'py3-none') + .replace('cp38-cp38', 'py3-none') + .replace('cp39-cp39', 'py3-none') + .replace('cp310-cp310', 'py3-none') + .replace('cp311-cp311', 'py3-none') + .replace('linux_x86_64', 'manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64') + + + ) + if is_macos_arm_build: + new_filename = new_filename.replace('macosx_12_0_universal2', 'macosx_11_0_arm64') + else: + new_filename = (new_filename + .replace('macosx_12_0_universal2', 'macosx_10_6_x86_64') + .replace('macosx_12_0_x86_64', 'macosx_10_6_x86_64') + ) + + os.rename(filename, Path(wheel_dir, new_filename)) \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e997a6d..fe7439b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,14 +1,15 @@ -name: "test build" +name: "build" on: push: branches: - main + - pip-install permissions: contents: read jobs: - test-ubuntu: - name: Testing ubuntu-20.04 - runs-on: ubuntu-20.04 + build-ubuntu-extension: + name: Building ubuntu + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 - uses: actions/cache@v3 @@ -29,8 +30,42 @@ jobs: with: name: sqlite-regex-ubuntu path: dist/debug/regex0.so - test-macos: - name: Testing macos-latest + build-ubuntu-python: + runs-on: ubuntu-18.04 + needs: [build-ubuntu-extension] + steps: + - uses: actions/checkout@v3 + - name: Download workflow artifacts + uses: actions/download-artifact@v3 + with: + name: sqlite-regex-ubuntu + path: dist/debug/ + - uses: actions/setup-python@v3 + - run: pip install wheel + - run: make python + - run: make datasette + - uses: actions/upload-artifact@v3 + with: + name: sqlite-regex-ubuntu-wheels + path: dist/debug/wheels/*.whl + test-ubuntu: + runs-on: ubuntu-20.04 + needs: [build-ubuntu-extension, build-ubuntu-python] + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-ubuntu + path: dist/debug/ + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-ubuntu-wheels + path: dist/debug/ + - run: pip install --find-links dist/debug/ sqlite_regex + - run: make test-loadable + - run: make test-python + build-macos-extension: + name: Building macos-latest runs-on: macos-latest steps: - uses: actions/checkout@v3 @@ -47,14 +82,48 @@ jobs: with: toolchain: stable - run: make loadable - #- run: make test - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: sqlite-regex-macos path: dist/debug/regex0.dylib - test-macos-arm: - name: Testing macos-latest with arm + build-macos-python: + runs-on: macos-latest + needs: [build-macos-extension] + steps: + - uses: actions/checkout@v3 + - name: Download workflow artifacts + uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos + path: dist/debug/ + - uses: actions/setup-python@v3 + - run: pip install wheel + - run: make python + - run: make datasette + - uses: actions/upload-artifact@v3 + with: + name: sqlite-regex-macos-wheels + path: dist/debug/wheels/*.whl + test-macos: + runs-on: macos-latest + needs: [build-macos-extension, build-macos-python] + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos + path: dist/debug/ + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos-wheels + path: dist/debug/ + - run: brew install python + - run: /usr/local/opt/python@3/libexec/bin/pip install --find-links dist/debug/ sqlite_regex + - run: make test-loadable python=/usr/local/opt/python@3/libexec/bin/python + - run: make test-python python=/usr/local/opt/python@3/libexec/bin/python + build-macos-arm-extension: + name: Building macos arm extension runs-on: macos-latest steps: - uses: actions/checkout@v3 @@ -71,15 +140,32 @@ jobs: with: toolchain: stable - run: rustup target add aarch64-apple-darwin - - run: make target=aarch64-apple-darwin loadable - #- run: make test + - run: make loadable target=aarch64-apple-darwin - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: sqlite-regex-macos-arm path: dist/debug/regex0.dylib - test-windows: - name: Testing windows-latest + build-macos-arm-python: + runs-on: macos-latest + needs: [build-macos-arm-extension] + steps: + - uses: actions/checkout@v3 + - name: Download workflow artifacts + uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos-arm + path: dist/debug/ + - uses: actions/setup-python@v3 + - run: pip install wheel + - run: make python IS_MACOS_ARM=1 + - run: make datasette + - uses: actions/upload-artifact@v3 + with: + name: sqlite-regex-macos-arm-wheels + path: dist/debug/wheels/*.whl + build-windows-extension: + name: Building windows extension runs-on: windows-latest steps: - uses: actions/checkout@v3 @@ -96,9 +182,68 @@ jobs: with: toolchain: stable - run: make loadable - #- run: make test - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: sqlite-regex-windows path: dist/debug/regex0.dll + build-windows-python: + runs-on: windows-latest + needs: [build-windows-extension] + steps: + - uses: actions/checkout@v3 + - name: Download workflow artifacts + uses: actions/download-artifact@v3 + with: + name: sqlite-regex-windows + path: dist/debug/ + - uses: actions/setup-python@v3 + - run: pip install wheel + - run: make python + - run: make datasette + - uses: actions/upload-artifact@v3 + with: + name: sqlite-regex-windows-wheels + path: dist/debug/wheels/*.whl + test-windows: + runs-on: windows-latest + needs: [build-windows-extension, build-windows-python] + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-windows + path: dist/debug/ + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-windows-wheels + path: dist/debug/ + - run: pip install --find-links dist/debug/ sqlite_regex + - run: make test-loadable + - run: make test-python + upload_test_pypi: + if: ${{ contains(github.event.head_commit.message, '@test_pypi') }} + needs: [test-ubuntu, test-macos, test-windows, build-macos-arm-python] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-windows-wheels + path: dist + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-ubuntu-wheels + path: dist + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos-wheels + path: dist + - uses: actions/download-artifact@v3 + with: + name: sqlite-regex-macos-arm-wheels + path: dist + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + skip_existing: true diff --git a/.gitignore b/.gitignore index f0c5d30..c1e2d4d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ benchmarks/sqlite3-re.c benchmarks/re.c benchmarks/re.h *.db +*.dylib +*.so +*.dll diff --git a/Makefile b/Makefile index 4a7ae35..d73d40a 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ + ifeq ($(shell uname -s),Darwin) CONFIG_DARWIN=y else ifeq ($(OS),Windows_NT) @@ -23,41 +24,78 @@ endif prefix=dist TARGET_LOADABLE=$(prefix)/debug/regex0.$(LOADABLE_EXTENSION) -TARGET_STATIC=$(prefix)/debug/regex0.a - TARGET_LOADABLE_RELEASE=$(prefix)/release/regex0.$(LOADABLE_EXTENSION) + +TARGET_STATIC=$(prefix)/debug/regex0.a TARGET_STATIC_RELEASE=$(prefix)/release/regex0.a +TARGET_WHEELS=$(prefix)/debug/wheels +TARGET_WHEELS_RELEASE=$(prefix)/release/wheels + +INTERMEDIATE_PYPACKAGE_EXTENSION=python/sqlite_regex/sqlite_regex/regex0.$(LOADABLE_EXTENSION) ifdef target CARGO_TARGET=--target=$(target) BUILT_LOCATION=target/$(target)/debug/$(LIBRARY_PREFIX)sqlite_regex.$(LOADABLE_EXTENSION) +BUILT_LOCATION_RELEASE=target/$(target)/release/$(LIBRARY_PREFIX)sqlite_regex.$(LOADABLE_EXTENSION) else CARGO_TARGET= BUILT_LOCATION=target/debug/$(LIBRARY_PREFIX)sqlite_regex.$(LOADABLE_EXTENSION) +BUILT_LOCATION_RELEASE=target/release/$(LIBRARY_PREFIX)sqlite_regex.$(LOADABLE_EXTENSION) endif +ifdef python +PYTHON=$(python) +else +PYTHON=python3 +endif + +ifdef IS_MACOS_ARM +RENAME_WHEELS_ARGS=--is-macos-arm +else +RENAME_WHEELS_ARGS= +endif $(prefix): mkdir -p $(prefix)/debug mkdir -p $(prefix)/release +$(TARGET_WHEELS): $(prefix) + mkdir -p $(TARGET_WHEELS) + +$(TARGET_WHEELS_RELEASE): $(prefix) + mkdir -p $(TARGET_WHEELS_RELEASE) + $(TARGET_LOADABLE): $(prefix) $(shell find . -type f -name '*.rs') cargo build $(CARGO_TARGET) cp $(BUILT_LOCATION) $@ -$(TARGET_STATIC): $(prefix) $(shell find . -type f -name '*.rs') - cargo build - cp target/debug/$(LIBRARY_PREFIX)sqlite_regex.a $@ +$(TARGET_LOADABLE_RELEASE): $(prefix) $(shell find . -type f -name '*.rs') + cargo build --release $(CARGO_TARGET) + cp $(BUILT_LOCATION_RELEASE) $@ + +python: $(TARGET_WHEELS) $(TARGET_LOADABLE) python/sqlite_regex/setup.py python/sqlite_regex/sqlite_regex/__init__.py .github/workflows/rename-wheels.py + cp $(TARGET_LOADABLE) $(INTERMEDIATE_PYPACKAGE_EXTENSION) + rm $(TARGET_WHEELS)/sqlite_regex* || true + pip3 wheel python/sqlite_regex/ -w $(TARGET_WHEELS) + python3 .github/workflows/rename-wheels.py $(TARGET_WHEELS) $(RENAME_WHEELS_ARGS) +python-release: $(TARGET_LOADABLE_RELEASE) $(TARGET_WHEELS_RELEASE) python/sqlite_regex/setup.py python/sqlite_regex/sqlite_regex/__init__.py .github/workflows/rename-wheels.py + cp $(TARGET_LOADABLE_RELEASE) $(INTERMEDIATE_PYPACKAGE_EXTENSION) + rm $(TARGET_WHEELS_RELEASE)/sqlite_regex* || true + pip3 wheel python/sqlite_regex/ -w $(TARGET_WHEELS_RELEASE) + python3 .github/workflows/rename-wheels.py $(TARGET_WHEELS_RELEASE) $(RENAME_WHEELS_ARGS) -$(TARGET_LOADABLE_RELEASE): $(prefix) $(shell find . -type f -name '*.rs') - cargo build --release - cp target/release/$(LIBRARY_PREFIX)sqlite_regex.$(LOADABLE_EXTENSION) $@ +datasette: $(TARGET_WHEELS) python/datasette_sqlite_regex/setup.py python/datasette_sqlite_regex/datasette_sqlite_regex/__init__.py + rm $(TARGET_WHEELS)/datasette* || true + pip3 wheel python/datasette_sqlite_regex/ --no-deps -w $(TARGET_WHEELS) + +datasette-release: $(TARGET_WHEELS_RELEASE) python/datasette_sqlite_regex/setup.py python/datasette_sqlite_regex/datasette_sqlite_regex/__init__.py + rm $(TARGET_WHEELS_RELEASE)/datasette* || true + pip3 wheel python/datasette_sqlite_regex/ --no-deps -w $(TARGET_WHEELS_RELEASE) -$(TARGET_STATIC_RELEASE): $(prefix) $(shell find . -type f -name '*.rs') - cargo build - cp target/debug/$(LIBRARY_PREFIX)sqlite_regex.a $@ +format: + cargo fmt sqlite-regex.h: cbindgen.toml rustup run nightly cbindgen --config $< -o $@ @@ -66,13 +104,31 @@ release: $(TARGET_LOADABLE_RELEASE) $(TARGET_STATIC_RELEASE) loadable: $(TARGET_LOADABLE) loadable-release: $(TARGET_LOADABLE_RELEASE) + static: $(TARGET_STATIC) +static-release: $(TARGET_STATIC_RELEASE) + +debug: loadable static python datasette +release: loadable-release static-release python-release datasette-release clean: rm dist/* cargo clean -test: - python3 tests/test-loadable.py +test-loadable: + $(PYTHON) tests/test-loadable.py -.PHONY: clean test loadable static \ No newline at end of file +test-python: + $(PYTHON) tests/test-python.py + +test: + make test-loadable + make test-python + +.PHONY: clean \ + test test-loadable test-python \ + loadable loadable-release \ + python python-release \ + datasette datasette-release \ + static static-release \ + debug release \ No newline at end of file diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 0000000..6769e21 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..a1ee200 --- /dev/null +++ b/python/README.md @@ -0,0 +1,6 @@ +# `sqlite-regex` Python Packages + +The `sqlite-regex` project offers two python packages for easy distribution. They are: + +1. The [`sqlite-regex` Python package](https://pypi.org/project/sqlite-regex/), source in [`sqlite_regex/`](./sqlite_regex/README.md) +2. The [`datasette-sqlite-regex` Python package](https://pypi.org/project/sqlite-regex/), a [Datasette](https://datasette.io/) plugin,which is a light wrapper around the `sqlite-regex` package, source in [`datasette_sqlite_regex/`](./datasette_sqlite_regex/README.md) diff --git a/python/datasette_sqlite_regex/README.md b/python/datasette_sqlite_regex/README.md new file mode 100644 index 0000000..9e9b299 --- /dev/null +++ b/python/datasette_sqlite_regex/README.md @@ -0,0 +1,16 @@ +# The `datasette-sqlite-regex` Datasette Plugin + +`datasette-sqlite-regex` is a [Datasette plugin](https://docs.datasette.io/en/stable/plugins.html) that loads the [`sqlite-regex`](https://github.com/asg017/sqlite-regex) extension in Datasette instances, allowing you to generate and work with [regexs](https://github.com/regex/spec) in SQL. + +``` +datasette install datasette-sqlite-regex +``` + +See [`docs.md`](../../docs.md) for a full API reference for the TODO SQL functions. + +Alternatively, when publishing Datasette instances, you can use the `--install` option to install the plugin. + +``` +datasette publish cloudrun data.db --service=my-service --install=datasette-sqlite-regex + +``` diff --git a/python/datasette_sqlite_regex/datasette_sqlite_regex/__init__.py b/python/datasette_sqlite_regex/datasette_sqlite_regex/__init__.py new file mode 100644 index 0000000..7417218 --- /dev/null +++ b/python/datasette_sqlite_regex/datasette_sqlite_regex/__init__.py @@ -0,0 +1,8 @@ +from datasette import hookimpl +import sqlite_regex + +@hookimpl +def prepare_connection(conn): + conn.enable_load_extension(True) + sqlite_regex.load(conn) + conn.enable_load_extension(False) \ No newline at end of file diff --git a/python/datasette_sqlite_regex/setup.py b/python/datasette_sqlite_regex/setup.py new file mode 100644 index 0000000..87a45d3 --- /dev/null +++ b/python/datasette_sqlite_regex/setup.py @@ -0,0 +1,24 @@ +from setuptools import setup + +VERSION = "0.1.1-alpha.3" + +setup( + name="datasette-sqlite-regex", + description="", + long_description="", + long_description_content_type="text/markdown", + author="Alex Garcia", + url="https://github.com/asg017/sqlite-regex", + project_urls={ + "Issues": "https://github.com/asg017/sqlite-regex/issues", + "CI": "https://github.com/asg017/sqlite-regex/actions", + "Changelog": "https://github.com/asg017/sqlite-regex/releases", + }, + license="MIT License, Apache License, Version 2.0", + version=VERSION, + packages=["datasette_sqlite_regex"], + entry_points={"datasette": ["sqlite_regex = datasette_sqlite_regex"]}, + install_requires=["datasette", "sqlite-regex"], + extras_require={"test": ["pytest"]}, + python_requires=">=3.7", +) \ No newline at end of file diff --git a/python/datasette_sqlite_regex/tests/test_sqlite_regex.py b/python/datasette_sqlite_regex/tests/test_sqlite_regex.py new file mode 100644 index 0000000..510532b --- /dev/null +++ b/python/datasette_sqlite_regex/tests/test_sqlite_regex.py @@ -0,0 +1,20 @@ +from datasette.app import Datasette +import pytest + + +@pytest.mark.asyncio +async def test_plugin_is_installed(): + datasette = Datasette(memory=True) + response = await datasette.client.get("/-/plugins.json") + assert response.status_code == 200 + installed_plugins = {p["name"] for p in response.json()} + assert "datasette-sqlite-regex" in installed_plugins + +@pytest.mark.asyncio +async def test_sqlite_regex_functions(): + datasette = Datasette(memory=True) + response = await datasette.client.get("/_memory.json?sql=select+regex_version(),regex()") + assert response.status_code == 200 + regex_version, regex = response.json()["rows"][0] + assert regex_version[0] == "v" + assert len(regex) == 26 \ No newline at end of file diff --git a/python/sqlite_regex/README.md b/python/sqlite_regex/README.md new file mode 100644 index 0000000..18d0a4e --- /dev/null +++ b/python/sqlite_regex/README.md @@ -0,0 +1,67 @@ +# The `sqlite-regex` Python package + +`sqlite-regex` is also distributed on PyPi as a Python package, for use in Python applications. It works well with the builtin [`sqlite3`](https://docs.python.org/3/library/sqlite3.html) Python module. + +``` +pip install sqlite-regex +``` + +## Usage + +The `sqlite-regex` python package exports two functions: `loadable_path()`, which returns the full path to the loadable extension, and `load(conn)`, which loads the `sqlite-regex` extension into the given [sqlite3 Connection object](https://docs.python.org/3/library/sqlite3.html#connection-objects). + +```python +import sqlite_regex +print(sqlite_regex.loadable_path()) +# '/.../venv/lib/python3.9/site-packages/sqlite_regex/regex0' + +import sqlite3 +conn = sqlite3.connect(':memory:') +sqlite_regex.load(conn) +conn.execute('select regex_version(), regex()').fetchone() +# ('v0.1.0', '01gr7gwc5aq22ycea6j8kxq4s9') +``` + +See [the full API Reference](#api-reference) for the Python API, and [`docs.md`](../../docs.md) for documentation on the `sqlite-regex` SQL API. + +See [`datasette-sqlite-regex`](../datasette_sqlite_regex/) for a Datasette plugin that is a light wrapper around the `sqlite-regex` Python package. + +## Compatibility + +Currently the `sqlite-regex` Python package is only distributed on PyPi as pre-build wheels, it's not possible to install from the source distribution. This is because the underlying `sqlite-regex` extension requires a lot of build dependencies like `make`, `cc`, and `cargo`. + +If you get a `unsupported platform` error when pip installing `sqlite-regex`, you'll have to build the `sqlite-regex` manually and load in the dynamic library manually. + +## API Reference + +

loadable_path()

+ +Returns the full path to the locally-install `sqlite-regex` extension, without the filename. + +This can be directly passed to [`sqlite3.Connection.load_extension()`](https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.load_extension), but the [`sqlite_regex.load()`](#load) function is preferred. + +```python +import sqlite_regex +print(sqlite_regex.loadable_path()) +# '/.../venv/lib/python3.9/site-packages/sqlite_regex/regex0' +``` + +> Note: this extension path doesn't include the file extension (`.dylib`, `.so`, `.dll`). This is because [SQLite will infer the correct extension](https://www.sqlite.org/loadext.html#loading_an_extension). + +

load(connection)

+ +Loads the `sqlite-regex` extension on the given [`sqlite3.Connection`](https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection) object, calling [`Connection.load_extension()`](https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.load_extension). + +```python +import sqlite_regex +import sqlite3 +conn = sqlite3.connect(':memory:') + +conn.enable_load_extension(True) +sqlite_regex.load(conn) +conn.enable_load_extension(False) + +conn.execute('select regex_version(), regex()').fetchone() +# ('v0.1.0', '01gr7gwc5aq22ycea6j8kxq4s9') +TODO +``` diff --git a/python/sqlite_regex/noop.c b/python/sqlite_regex/noop.c new file mode 100644 index 0000000..e69de29 diff --git a/python/sqlite_regex/setup.py b/python/sqlite_regex/setup.py new file mode 100644 index 0000000..1b636fc --- /dev/null +++ b/python/sqlite_regex/setup.py @@ -0,0 +1,47 @@ +from setuptools import setup, Extension +import os +import platform + +VERSION = "0.1.1-alpha.3" + +system = platform.system() +machine = platform.machine() + +print(system, machine) + +if system == 'Darwin': + if machine not in ['x86_64', 'arm64']: + raise Exception("unsupported platform") +elif system == 'Linux': + if machine not in ['x86_64']: + raise Exception("unsupported platform") +elif system == 'Windows': + # TODO only 64 bit I think + pass +else: + raise Exception("unsupported platform") + +setup( + name="sqlite-regex", + description="", + long_description="", + long_description_content_type="text/markdown", + author="Alex Garcia", + url="https://github.com/asg017/sqlite-regex", + project_urls={ + "Issues": "https://github.com/asg017/sqlite-regex/issues", + "CI": "https://github.com/asg017/sqlite-regex/actions", + "Changelog": "https://github.com/asg017/sqlite-regex/releases", + }, + license="MIT License, Apache License, Version 2.0", + version=VERSION, + packages=["sqlite_regex"], + package_data={"sqlite_regex": ['*.so', '*.dylib', '*.dll']}, + install_requires=[], + # Adding an Extension makes `pip wheel` believe that this isn't a + # pure-python package. The noop.c was added since the windows build + # didn't seem to respect optional=True + ext_modules=[Extension("noop", ["noop.c"], optional=True)], + extras_require={"test": ["pytest"]}, + python_requires=">=3.7", +) \ No newline at end of file diff --git a/python/sqlite_regex/sqlite_regex/__init__.py b/python/sqlite_regex/sqlite_regex/__init__.py new file mode 100644 index 0000000..6f150a8 --- /dev/null +++ b/python/sqlite_regex/sqlite_regex/__init__.py @@ -0,0 +1,9 @@ +import os +import sqlite3 + +def loadable_path(): + loadable_path = os.path.join(os.path.dirname(__file__), "regex0") + return os.path.normpath(loadable_path) + +def load(conn: sqlite3.Connection) -> None: + conn.load_extension(loadable_path()) diff --git a/tests/test-python.py b/tests/test-python.py new file mode 100644 index 0000000..5fa189c --- /dev/null +++ b/tests/test-python.py @@ -0,0 +1,18 @@ +import unittest +import sqlite3 +import sqlite_regex + +class TestSqliteregexPython(unittest.TestCase): + def test_path(self): + db = sqlite3.connect(':memory:') + db.enable_load_extension(True) + + self.assertEqual(type(sqlite_regex.loadable_path()), str) + + sqlite_regex.load(db) + version, result = db.execute('select regex_version(), regexp("[abc]", "c")').fetchone() + self.assertEqual(version[0], "v") + self.assertEqual(result, 1) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file