-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit abd2a35
Showing
7 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
name: Publish Python Package | ||
|
||
on: | ||
release: | ||
types: [created] | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
python-version: [3.6, 3.7, 3.8] | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
- uses: actions/cache@v2 | ||
name: Configure pip caching | ||
with: | ||
path: ~/.cache/pip | ||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} | ||
restore-keys: | | ||
${{ runner.os }}-pip- | ||
- name: Install dependencies | ||
run: | | ||
pip install -e '.[test]' | ||
- name: Run tests | ||
run: | | ||
pytest | ||
deploy: | ||
runs-on: ubuntu-latest | ||
needs: [test] | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Set up Python | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: '3.8' | ||
- uses: actions/cache@v2 | ||
name: Configure pip caching | ||
with: | ||
path: ~/.cache/pip | ||
key: ${{ runner.os }}-publish-pip-${{ hashFiles('**/setup.py') }} | ||
restore-keys: | | ||
${{ runner.os }}-publish-pip- | ||
- name: Install dependencies | ||
run: | | ||
pip install setuptools wheel twine | ||
- name: Publish | ||
env: | ||
TWINE_USERNAME: __token__ | ||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} | ||
run: | | ||
python setup.py sdist bdist_wheel | ||
twine upload dist/* | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
name: Test | ||
|
||
on: [push] | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
python-version: [3.6, 3.7, 3.8] | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
- uses: actions/cache@v2 | ||
name: Configure pip caching | ||
with: | ||
path: ~/.cache/pip | ||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} | ||
restore-keys: | | ||
${{ runner.os }}-pip- | ||
- name: Install dependencies | ||
run: | | ||
pip install -e '.[test]' | ||
- name: Run tests | ||
run: | | ||
pytest | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.venv | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
venv | ||
.eggs | ||
.pytest_cache | ||
*.egg-info | ||
.DS_Store | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# datasette-dateutil | ||
|
||
[![PyPI](https://img.shields.io/pypi/v/datasette-dateutil.svg)](https://pypi.org/project/datasette-dateutil/) | ||
[![Changelog](https://img.shields.io/github/v/release/simonw/datasette-dateutil?include_prereleases&label=changelog)](https://github.com/simonw/datasette-dateutil/releases) | ||
[![Tests](https://github.com/simonw/datasette-dateutil/workflows/Test/badge.svg)](https://github.com/simonw/datasette-dateutil/actions?query=workflow%3ATest) | ||
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-dateutil/blob/main/LICENSE) | ||
|
||
dateutil functions for Datasette | ||
|
||
## Installation | ||
|
||
Install this plugin in the same environment as Datasette. | ||
|
||
$ datasette install datasette-dateutil | ||
|
||
## Usage | ||
|
||
This function adds custom SQL functions that expose functionality from the [dateutil](https://dateutil.readthedocs.io/) Python library. | ||
|
||
Once installed, the following SQL functions become available: | ||
|
||
- `dateutil_parse(text)` - returns an ISO8601 date string parsed from the text, or `null` if the input could not be parsed. `dateutil_parse("10 october 2020 3pm")` returns `2020-10-10T15:00:00`. | ||
- `dateutil_parse_fuzzy(text)` - same as `dateutil_parse()` but this also works against strings that contain a date somewhere within them - that date will be returned, or `null` if no dates could be found. `dateutil_parse_fuzzy("This is due 10 september")` returns `2020-09-10T00:00:00` (but will start returning the 2021 version of that if the year is 2021). | ||
- `dateutil_easter(year)` - returns the date for Easter in that year, for example `dateutil_easter("2020")` returns `2020-04-12`. | ||
|
||
The `dateutil_parse()` and `dateutil_parse_fuzzy()` functions both follow the American convention of assuming that `1/2/2020` lists the month first, evaluating this example to the 2nd of January. | ||
|
||
If you want to assume that the day comes first, use these two functions instead: | ||
|
||
- `dateutil_parse_dayfirst(text)` | ||
- `dateutil_parse_fuzzy_dayfirst(text)` | ||
|
||
## Development | ||
|
||
To set up this plugin locally, first checkout the code. Then create a new virtual environment: | ||
|
||
cd datasette-dateutil | ||
python3 -mvenv venv | ||
source venv/bin/activate | ||
|
||
Or if you are using `pipenv`: | ||
|
||
pipenv shell | ||
|
||
Now install the dependencies and tests: | ||
|
||
pip install -e '.[test]' | ||
|
||
To run the tests: | ||
|
||
pytest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
from datasette import hookimpl | ||
from dateutil.parser import parse, ParserError | ||
from dateutil.easter import easter | ||
|
||
|
||
def _dateutil_parse_shared(s, **kwargs): | ||
if not kwargs.get("dayfirst"): | ||
kwargs["dayfirst"] = False | ||
if not s: | ||
return None | ||
try: | ||
return parse(s, **kwargs).isoformat() | ||
except ParserError: | ||
return None | ||
|
||
|
||
def dateutil_parse(s): | ||
return _dateutil_parse_shared(s) | ||
|
||
|
||
def dateutil_parse_fuzzy(s): | ||
return _dateutil_parse_shared(s, fuzzy=True) | ||
|
||
|
||
def dateutil_parse_dayfirst(s): | ||
return _dateutil_parse_shared(s, dayfirst=True) | ||
|
||
|
||
def dateutil_parse_fuzzy_dayfirst(s): | ||
return _dateutil_parse_shared(s, fuzzy=True, dayfirst=True) | ||
|
||
|
||
def dateutil_easter(year): | ||
year = str(year) if year else None | ||
if not year or not year.isdigit(): | ||
return None | ||
try: | ||
return easter(int(year)).isoformat() | ||
except Exception as e: | ||
return None | ||
|
||
|
||
@hookimpl | ||
def prepare_connection(conn): | ||
conn.create_function("dateutil_parse", 1, dateutil_parse) | ||
conn.create_function("dateutil_parse_fuzzy", 1, dateutil_parse_fuzzy) | ||
conn.create_function("dateutil_parse_dayfirst", 1, dateutil_parse_dayfirst) | ||
conn.create_function( | ||
"dateutil_parse_fuzzy_dayfirst", 1, dateutil_parse_fuzzy_dayfirst | ||
) | ||
conn.create_function("dateutil_easter", 1, dateutil_easter) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from setuptools import setup | ||
import os | ||
|
||
VERSION = "0.1" | ||
|
||
|
||
def get_long_description(): | ||
with open( | ||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "README.md"), | ||
encoding="utf8", | ||
) as fp: | ||
return fp.read() | ||
|
||
|
||
setup( | ||
name="datasette-dateutil", | ||
description="dateutil functions for Datasette", | ||
long_description=get_long_description(), | ||
long_description_content_type="text/markdown", | ||
author="Simon Willison", | ||
url="https://github.com/simonw/datasette-dateutil", | ||
project_urls={ | ||
"Issues": "https://github.com/simonw/datasette-dateutil/issues", | ||
"CI": "https://github.com/simonw/datasette-dateutil/actions", | ||
"Changelog": "https://github.com/simonw/datasette-dateutil/releases", | ||
}, | ||
license="Apache License, Version 2.0", | ||
version=VERSION, | ||
packages=["datasette_dateutil"], | ||
entry_points={"datasette": ["dateutil = datasette_dateutil"]}, | ||
install_requires=["datasette", "python-dateutil"], | ||
extras_require={"test": ["pytest", "pytest-asyncio", "httpx"]}, | ||
tests_require=["datasette-dateutil[test]"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from datasette.app import Datasette | ||
import pytest | ||
import httpx | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_plugin_is_installed(): | ||
app = Datasette([], memory=True).app() | ||
async with httpx.AsyncClient(app=app) as client: | ||
response = await client.get("http://localhost/-/plugins.json") | ||
assert 200 == response.status_code | ||
installed_plugins = {p["name"] for p in response.json()} | ||
assert "datasette-dateutil" in installed_plugins | ||
|
||
|
||
@pytest.mark.asyncio | ||
@pytest.mark.parametrize("sql,expected", [ | ||
("select dateutil_parse('1st october 2009')", "2009-10-01T00:00:00"), | ||
("select dateutil_parse('invalid')", None), | ||
("select dateutil_parse('due on 1st october 2009')", None), | ||
("select dateutil_parse_fuzzy('due on 1st october 2009')", "2009-10-01T00:00:00"), | ||
("select dateutil_parse_fuzzy('due on')", None), | ||
("select dateutil_parse_dayfirst('1/2/2020')", "2020-02-01T00:00:00"), | ||
("select dateutil_parse('1/2/2020')", "2020-01-02T00:00:00"), | ||
("select dateutil_parse_fuzzy('due on 1/2/2003')", "2003-01-02T00:00:00"), | ||
("select dateutil_parse_fuzzy_dayfirst('due on 1/2/2003')", "2003-02-01T00:00:00"), | ||
("select dateutil_easter(2020)", "2020-04-12"), | ||
("select dateutil_easter('invalid')", None), | ||
]) | ||
async def test_dateutil_sql_functions(sql, expected): | ||
app = Datasette([], memory=True).app() | ||
async with httpx.AsyncClient(app=app) as client: | ||
response = await client.get("http://localhost/:memory:.json", params={ | ||
"sql": sql, | ||
"_shape": "array", | ||
}) | ||
assert 200 == response.status_code | ||
actual = list(response.json()[0].values())[0] | ||
assert actual == expected |