From e5be0f2e1a94fa781597a55ae73daaf749bd883b Mon Sep 17 00:00:00 2001 From: Alex Parsons Date: Tue, 31 Oct 2023 20:57:40 +0000 Subject: [PATCH] Slimed down new commit --- .devcontainer/devcontainer.json | 51 ++++ .devcontainer/initializeCommand | 2 + .devcontainer/initializeCommand.cmd | 1 + .devcontainer/initializeCommand.ps1 | 7 + .devcontainer/postCreateCommand | 8 + .env-example | 1 + .gitattributes | 9 + .github/workflows/build_and_publish.yml | 99 ++++++ .github/workflows/test.yml | 31 ++ .gitignore | 12 + .gitmodules | 6 + .vscode/launch.json | 15 + .vscode/settings.json | 46 +++ Dockerfile | 36 +++ Dockerfile.dev | 15 + LICENSE | 21 ++ data/.gitinclude | 0 data/interim/.gitinclude | 0 .../public_whip_data/datapackage.yaml | 277 +++++++++++++++++ .../pw_division.resource.yaml | 107 +++++++ .../pw_dyn_dreammp.resource.yaml | 51 ++++ .../pw_dyn_dreamvote.resource.yaml | 54 ++++ .../pw_dyn_wiki_motion.resource.yaml | 90 ++++++ .../public_whip_data/pw_moffice.resource.yaml | 59 ++++ .../public_whip_data/pw_mp.resource.yaml | 106 +++++++ .../public_whip_data/pw_vote.resource.yaml | 40 +++ docker-compose.yml | 9 + docs/.gitinclude | 0 docs/Gemfile | 46 +++ docs/Gemfile.lock | 274 +++++++++++++++++ docs/data.json | 3 + docs/index.md | 8 + docs/sass/_bootstrap-compat.scss | 237 ++++++++++++++ docs/sass/_header.scss | 64 ++++ docs/sass/mysoc.scss | 44 +++ notebooks/_render_config/default.yaml | 16 + notebooks/example.ipynb | 274 +++++++++++++++++ pyproject.toml | 34 +++ readme.md | 12 + script/server | 4 + script/setup | 2 + script/test | 29 ++ script/update-from-template | 17 ++ src/publicwhip_data/__init__.py | 0 src/publicwhip_data/__main__.py | 20 ++ src/publicwhip_data/mysql2sqlite | 289 ++++++++++++++++++ src/publicwhip_data/process.py | 143 +++++++++ tests/test_publicwhip_data.py | 7 + 48 files changed, 2676 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/initializeCommand create mode 100644 .devcontainer/initializeCommand.cmd create mode 100644 .devcontainer/initializeCommand.ps1 create mode 100644 .devcontainer/postCreateCommand create mode 100644 .env-example create mode 100644 .gitattributes create mode 100644 .github/workflows/build_and_publish.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 Dockerfile create mode 100644 Dockerfile.dev create mode 100644 LICENSE create mode 100644 data/.gitinclude create mode 100644 data/interim/.gitinclude create mode 100644 data/packages/public_whip_data/datapackage.yaml create mode 100644 data/packages/public_whip_data/pw_division.resource.yaml create mode 100644 data/packages/public_whip_data/pw_dyn_dreammp.resource.yaml create mode 100644 data/packages/public_whip_data/pw_dyn_dreamvote.resource.yaml create mode 100644 data/packages/public_whip_data/pw_dyn_wiki_motion.resource.yaml create mode 100644 data/packages/public_whip_data/pw_moffice.resource.yaml create mode 100644 data/packages/public_whip_data/pw_mp.resource.yaml create mode 100644 data/packages/public_whip_data/pw_vote.resource.yaml create mode 100644 docker-compose.yml create mode 100644 docs/.gitinclude create mode 100644 docs/Gemfile create mode 100644 docs/Gemfile.lock create mode 100644 docs/data.json create mode 100644 docs/index.md create mode 100644 docs/sass/_bootstrap-compat.scss create mode 100644 docs/sass/_header.scss create mode 100644 docs/sass/mysoc.scss create mode 100644 notebooks/_render_config/default.yaml create mode 100644 notebooks/example.ipynb create mode 100644 pyproject.toml create mode 100644 readme.md create mode 100644 script/server create mode 100644 script/setup create mode 100644 script/test create mode 100644 script/update-from-template create mode 100644 src/publicwhip_data/__init__.py create mode 100644 src/publicwhip_data/__main__.py create mode 100644 src/publicwhip_data/mysql2sqlite create mode 100644 src/publicwhip_data/process.py create mode 100644 tests/test_publicwhip_data.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..467de222 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,51 @@ +{ + "name": "mysoc_notebook", + "dockerComposeFile": "../docker-compose.yml", + "service": "app", + "overrideCommand": true, + "initializeCommand": [ + ".devcontainer/initializeCommand" + ], + "postCreateCommand": ".devcontainer/postCreateCommand", + "workspaceFolder": "/workspaces/publicwhip_data", + "extensions": [ + "ms-vscode.test-adapter-converter", + "bungcip.better-toml", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-azuretools.vscode-docker", + "valentjn.vscode-ltex" + ], + "customizations": { + "codespaces": { + "repositories": { + "mysociety/data_common": { + "permissions": { + "contents": "write", + "actions": "write", + "deployments": "write", + "issues": "write", + "packages": "read", + "pull_requests": "write", + "repository_projects": "write", + "statuses": "write", + "workflows": "write" + } + }, + "mysociety/mysociety-docs-theme": { + "permissions": { + "contents": "write", + "actions": "write", + "deployments": "write", + "issues": "write", + "packages": "read", + "pull_requests": "write", + "repository_projects": "write", + "statuses": "write", + "workflows": "write" + } + } + } + } + } +} \ No newline at end of file diff --git a/.devcontainer/initializeCommand b/.devcontainer/initializeCommand new file mode 100644 index 00000000..8e72f729 --- /dev/null +++ b/.devcontainer/initializeCommand @@ -0,0 +1,2 @@ +#!/bin/sh +[ ! -d "src/data_common/src" ] && git submodule update --init || echo "Already exists" diff --git a/.devcontainer/initializeCommand.cmd b/.devcontainer/initializeCommand.cmd new file mode 100644 index 00000000..784fa880 --- /dev/null +++ b/.devcontainer/initializeCommand.cmd @@ -0,0 +1 @@ +powershell .devcontainer/initializeCommand.ps1 \ No newline at end of file diff --git a/.devcontainer/initializeCommand.ps1 b/.devcontainer/initializeCommand.ps1 new file mode 100644 index 00000000..82e005a9 --- /dev/null +++ b/.devcontainer/initializeCommand.ps1 @@ -0,0 +1,7 @@ +$Folder = 'src/data_common/src' +"Test to see if folder [$Folder] exists" +if (Test-Path -Path $Folder) { + echo "Submodule already exists" +} else { + git submodule update --init +} \ No newline at end of file diff --git a/.devcontainer/postCreateCommand b/.devcontainer/postCreateCommand new file mode 100644 index 00000000..a9f6c5a0 --- /dev/null +++ b/.devcontainer/postCreateCommand @@ -0,0 +1,8 @@ +#!/bin/bash + +echo "get submodule to appear as main rather than latest comment" +cd src/data_common +git checkout main +cd ../.. +cd docs/theme +git checkout main \ No newline at end of file diff --git a/.env-example b/.env-example new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.env-example @@ -0,0 +1 @@ + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..922ce3a4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +* text eol=lf +*.png binary +*.jpg binary +*.sqlite3 binary +*.sqlite binary +*.xlsx binary +*.xls binary +*.pdf binary +*.parquet binary \ No newline at end of file diff --git a/.github/workflows/build_and_publish.yml b/.github/workflows/build_and_publish.yml new file mode 100644 index 00000000..e409040f --- /dev/null +++ b/.github/workflows/build_and_publish.yml @@ -0,0 +1,99 @@ +name: Build datasets and publish + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + pages: write + id-token: write + +on: + push: + branches: ["main"] + workflow_dispatch: + schedule: + - cron : "0 8 * * *" + + +jobs: + + build: + runs-on: ubuntu-latest + steps: + + - name: Checkout repo content + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Run tests and try and build project + uses: mysociety/run-in-devcontainer@v1 + with: + run: | + export PATH="/root/.local/bin:$PATH" + script/test + dataset build --all + dataset version auto --auto-ban major --all --publish + dataset publish --all + + + - name: Push new data + id: auto-commit-action + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: "Update repo data based on source changes" + + - name: Send GitHub Action trigger data to Slack workflow + id: slack + if: steps.auto-commit-action.outputs.changes_detected == 'true' + uses: slackapi/slack-github-action@v1.19.0 + with: + payload: | + { + "repo_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.HAPPY_DATABOT_SLACK_WEBHOOK }} + + - name: Setup Pages + uses: actions/configure-pages@v1 + + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: docs + destination: docs/_site + + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: docs/_site + + - name: Send GitHub Action trigger data to Slack workflow (if failed) + if: ${{ failure() }} + id: slack-failed + uses: slackapi/slack-github-action@v1.19.0 + with: + payload: | + { + "repo_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SAD_DATABOT_SLACK_WEBHOOK }} + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 + + - uses: geekyeggo/delete-artifact@v1 + with: + name: github-pages \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..aef544a0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +name: Run project tests + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-tests + cancel-in-progress: true + +on: + push: + branches-ignore: ["main"] + pull_request: + workflow_dispatch: + workflow_call: + +jobs: + tests: + runs-on: ubuntu-latest + steps: + + - name: checkout repo content + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Run project tests + uses: mysociety/run-in-devcontainer@v1 + with: + run: | + export PATH="/root/.local/bin:$PATH" + script/test + dataset build --all + dataset version auto --auto-ban major --all \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d3fae294 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.pyc +secrets.yaml +.env +data/private/* +_render/_parts +_render/_papermills +docs/_site +data/raw +docs/_site +docs/data +docs/_* +*.parquet \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..789eee18 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "src/data_common"] + path = src/data_common + url = https://github.com/mysociety/data_common +[submodule "docs/theme"] + path = docs/theme + url = https://github.com/mysociety/mysociety-docs-theme/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..58a21d01 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal" + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..88a27e8b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,46 @@ +{ + "python.linting.pylintEnabled": true, + "python.defaultInterpreterPath": "/usr/local/bin/python", + "python.terminal.activateEnvironment": false, + "python.formatting.provider": "black", + "python.analysis.typeCheckingMode": "basic", + "python.analysis.stubPath": "src/data_common/typing", + "editor.formatOnSave": true, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/*.pyc": { + "when": "$(basename).py" + }, + "**/__pycache__": true + }, + "files.associations": { + "**/*.html": "html", + "**/templates/**/*.html": "django-html", + "**/templates/**/*": "django-txt", + "**/requirements{/**,*}.{txt,in}": "pip-requirements" + }, + "python.linting.pylintArgs": [ + "--max-line-length=88", + "--disable=C0103,E1101,W5101,E1123,E501,E203", + "--load-plugins=pylint_django" + ], + "jupyter.jupyterServerType": "local", + "ltex.language": "en-GB", + "ltex.ltex-ls.path": "/ltex/ltex-ls-15.2.0/", + "[markdown]": { + "editor.quickSuggestions": { + "comments": "on", + "strings": "on", + "other": "on" + } + }, + "python.testing.pytestArgs": [ + "tests/" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a22ee15c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# This dockerfile is used by binder. + +FROM ghcr.io/mysociety/data_common:sha-54e0768 + +# Make an empty project directory so the 'self' setup doesn't fail and scripts can be setup +# Override the .pth created at previous stages to point to where the working directory will land +COPY pyproject.toml poetry.loc[k] /setup/ +COPY src/data_common/pyproject.toml src/data_common/poetry.loc[k] /setup/src/data_common/ +RUN mkdir /setup/src/publicwhip_data \ + && touch /setup/src/publicwhip_data/__init__.py \ + && mkdir --parents /setup/src/data_common/src/data_common \ + && touch /setup/src/data_common/src/data_common/__init__.py \ + && export PATH="/root/.local/bin:$PATH" \ + && cd /setup/ && poetry install \ + && echo "/workspaces/publicwhip_data/src/" > /usr/local/lib/python3.10/site-packages/publicwhip_data.pth \ + && echo "/workspaces/publicwhip_data/src/data_common/src" > /usr/local/lib/python3.10/site-packages/data_common.pth + +# special binder instructions + +RUN pip install --no-cache-dir notebook + +ARG NB_USER=jovyan +ARG NB_UID=1000 +ENV USER ${NB_USER} +ENV NB_UID ${NB_UID} +ENV HOME /home/${NB_USER} + +RUN adduser --disabled-password \ + --gecos "Default user" \ + --uid ${NB_UID} \ + ${NB_USER} + +COPY . ${HOME} +USER root +RUN chown -R ${NB_UID} ${HOME} +USER ${NB_USER} \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 00000000..2397ab7d --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,15 @@ +FROM ghcr.io/mysociety/data_common:sha-d6eabdb + +# Make an empty project directory so the 'self' setup doesn't fail and scripts can be setup +# Override the .pth created at previous stages to point to where the working directory will land +COPY pyproject.toml poetry.loc[k] /setup/ +COPY src/data_common/pyproject.toml src/data_common/poetry.loc[k] /setup/src/data_common/ +ENV WORKSPACE_NAME publicwhip-data +RUN mkdir /setup/src/$WORKSPACE_NAME \ + && touch /setup/src/$WORKSPACE_NAME/__init__.py \ + && mkdir --parents /setup/src/data_common/src/data_common \ + && touch /setup/src/data_common/src/data_common/__init__.py \ + && export PATH="/root/.local/bin:$PATH" \ + && cd /setup/ && poetry install \ + && echo "/workspaces/$WORKSPACE_NAME/src/" > /usr/local/lib/python3.10/site-packages/$WORKSPACE_NAME.pth \ + && echo "/workspaces/$WORKSPACE_NAME/src/data_common/src" > /usr/local/lib/python3.10/site-packages/data_common.pth \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..fc80d7fa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 mySociety + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/data/.gitinclude b/data/.gitinclude new file mode 100644 index 00000000..e69de29b diff --git a/data/interim/.gitinclude b/data/interim/.gitinclude new file mode 100644 index 00000000..e69de29b diff --git a/data/packages/public_whip_data/datapackage.yaml b/data/packages/public_whip_data/datapackage.yaml new file mode 100644 index 00000000..6fe38e34 --- /dev/null +++ b/data/packages/public_whip_data/datapackage.yaml @@ -0,0 +1,277 @@ +name: public_whip_data +title: Public Whip data +description: "Reprocessed data dump from publicwhip.org.uk\n" +version: 0.1.0 +licenses: +- name: CC-BY-4.0 + path: https://creativecommons.org/licenses/by/4.0/ + title: Creative Commons Attribution 4.0 International License +contributors: +- title: mySociety + path: https://mysociety.org + role: author +custom: + build: publicwhip_data.process:fetch_and_move_pw + tests: + - test_public_whip_data + dataset_order: 0 + download_options: + gate: default + survey: default + header_text: default + formats: + csv: false + parquet: true + composite: + xlsx: + include: all + exclude: none + render: false + sqlite: + include: all + exclude: none + render: false + json: + include: all + exclude: none + render: false + change_log: + 0.1.0: '' + 0.2.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.3.0: 'Change in data for resource(s): pw_vote' + 0.4.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.5.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.5.1: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.6.0: 'Change in data for resource(s): pw_moffice,pw_mp,pw_vote' + 0.6.1: 'Minor change in data for resource(s): pw_moffice' + 0.6.2: 'Minor change in data for resource(s): pw_moffice' + 0.7.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.8.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.8.1: 'Minor change in data for resource(s): pw_moffice' + 0.9.0: 'Change in data for resource(s): pw_moffice,pw_vote' + 0.9.1: 'Minor change in data for resource(s): pw_moffice' + 0.9.2: 'Minor change in data for resource(s): pw_moffice' + 0.9.3: 'Minor change in data for resource(s): pw_moffice' + 0.9.4: 'Minor change in data for resource(s): pw_moffice' + 0.10.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.10.1: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.10.2: 'Minor change in data for resource(s): pw_moffice' + 0.10.3: 'Minor change in data for resource(s): pw_moffice' + 0.10.4: 'Minor change in data for resource(s): pw_moffice' + 0.10.5: 'Minor change in data for resource(s): pw_moffice' + 0.10.6: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.10.7: 'Minor change in data for resource(s): pw_moffice' + 0.11.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.11.1: 'Minor change in data for resource(s): pw_moffice' + 0.11.2: 'Minor change in data for resource(s): pw_moffice' + 0.11.3: 'Minor change in data for resource(s): pw_moffice' + 0.12.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.13.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.14.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.15.0: 'Change in data for resource(s): pw_vote' + 0.15.1: 'Minor change in data for resource(s): pw_moffice' + 0.15.2: 'Minor change in data for resource(s): pw_moffice' + 0.15.3: 'Minor change in data for resource(s): pw_moffice' + 0.15.4: 'Minor change in data for resource(s): pw_moffice' + 0.15.5: 'Minor change in data for resource(s): pw_moffice' + 0.15.6: 'Minor change in data for resource(s): pw_moffice' + 0.15.7: 'Minor change in data for resource(s): pw_moffice' + 0.15.8: 'Minor change in data for resource(s): pw_moffice' + 0.15.9: 'Minor change in data for resource(s): pw_moffice' + 0.15.10: 'Minor change in data for resource(s): pw_moffice' + 0.15.11: 'Minor change in data for resource(s): pw_moffice' + 0.15.12: 'Minor change in data for resource(s): pw_moffice' + 0.15.13: 'Minor change in data for resource(s): pw_moffice' + 0.15.14: 'Minor change in data for resource(s): pw_moffice' + 0.15.15: 'Minor change in data for resource(s): pw_moffice' + 0.15.16: 'Minor change in data for resource(s): pw_moffice' + 0.15.17: 'Minor change in data for resource(s): pw_moffice' + 0.15.18: 'Minor change in data for resource(s): pw_moffice' + 0.16.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.17.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.17.1: 'Minor change in data for resource(s): pw_moffice' + 0.17.2: 'Minor change in data for resource(s): pw_moffice' + 0.17.3: 'Minor change in data for resource(s): pw_moffice' + 0.18.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.19.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.20.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.21.0: 'Change in data for resource(s): pw_vote' + 0.21.1: 'Minor change in data for resource(s): pw_moffice' + 0.21.2: 'Minor change in data for resource(s): pw_moffice' + 0.21.3: 'Minor change in data for resource(s): pw_moffice' + 0.21.4: 'Minor change in data for resource(s): pw_moffice' + 0.22.0: 'Change in data for resource(s): pw_vote' + 0.22.1: 'Minor change in data for resource(s): pw_moffice' + 0.23.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.23.1: 'Minor change in data for resource(s): pw_moffice' + 0.23.2: 'Minor change in data for resource(s): pw_moffice' + 0.23.3: 'Minor change in data for resource(s): pw_moffice' + 0.23.4: 'Minor change in data for resource(s): pw_moffice' + 0.23.5: 'Minor change in data for resource(s): pw_moffice' + 0.23.6: 'Minor change in data for resource(s): pw_moffice' + 0.24.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.24.1: 'Minor change in data for resource(s): pw_moffice' + 0.24.2: 'Minor change in data for resource(s): pw_moffice' + 0.25.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.26.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.27.0: 'Change in data for resource(s): pw_vote' + 0.28.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.28.1: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.28.2: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.28.3: 'Minor change in data for resource(s): pw_moffice' + 0.28.4: 'Minor change in data for resource(s): pw_moffice' + 0.29.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.30.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.30.1: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.31.0: 'Change in data for resource(s): pw_vote' + 0.31.1: 'Minor change in data for resource(s): pw_moffice' + 0.31.2: 'Minor change in data for resource(s): pw_moffice' + 0.31.3: 'Minor change in data for resource(s): pw_moffice' + 0.31.4: 'Minor change in data for resource(s): pw_moffice' + 0.31.5: 'Minor change in data for resource(s): pw_moffice' + 0.31.6: 'Minor change in data for resource(s): pw_moffice' + 0.31.7: 'Minor change in data for resource(s): pw_moffice' + 0.31.8: 'Minor change in data for resource(s): pw_moffice' + 0.32.0: 'Change in data for resource(s): pw_vote' + 0.33.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.34.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.35.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.35.1: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.35.2: 'Minor change in data for resource(s): pw_moffice' + 0.35.3: 'Minor change in data for resource(s): pw_moffice' + 0.36.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.37.0: 'Change in data for resource(s): pw_vote' + 0.38.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.38.1: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.38.2: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.38.3: 'Minor change in data for resource(s): pw_moffice' + 0.38.4: 'Minor change in data for resource(s): pw_moffice' + 0.39.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.40.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.40.1: 'Minor change in data for resource(s): pw_moffice' + 0.41.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.41.1: 'Minor change in data for resource(s): pw_moffice' + 0.41.2: 'Minor change in data for resource(s): pw_moffice' + 0.41.3: 'Minor change in data for resource(s): pw_moffice' + 0.41.4: 'Minor change in data for resource(s): pw_moffice' + 0.42.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.43.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.43.1: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.43.2: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.43.3: 'Minor change in data for resource(s): pw_moffice' + 0.43.4: 'Minor change in data for resource(s): pw_moffice' + 0.44.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.45.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.46.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.46.1: 'Minor change in data for resource(s): pw_moffice' + 0.46.2: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.46.3: 'Minor change in data for resource(s): pw_moffice' + 0.46.4: 'Minor change in data for resource(s): pw_moffice' + 0.46.5: 'Minor change in data for resource(s): pw_moffice' + 0.47.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.48.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.49.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.49.1: 'Minor change in data for resource(s): pw_moffice' + 0.49.2: 'Minor change in data for resource(s): pw_moffice' + 0.49.3: 'Minor change in data for resource(s): pw_moffice' + 0.50.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.51.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.52.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.52.1: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.52.2: 'Minor change in data for resource(s): pw_moffice' + 0.52.3: 'Minor change in data for resource(s): pw_moffice' + 0.52.4: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.52.5: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.52.6: 'Minor change in data for resource(s): pw_moffice' + 0.52.7: 'Minor change in data for resource(s): pw_moffice' + 0.52.8: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.52.9: 'Minor change in data for resource(s): pw_moffice' + 0.52.10: 'Minor change in data for resource(s): pw_moffice' + 0.52.11: 'Minor change in data for resource(s): pw_moffice' + 0.52.12: 'Minor change in data for resource(s): pw_moffice' + 0.52.13: 'Minor change in data for resource(s): pw_moffice' + 0.52.14: 'Minor change in data for resource(s): pw_moffice' + 0.52.15: 'Minor change in data for resource(s): pw_moffice' + 0.52.16: 'Minor change in data for resource(s): pw_moffice' + 0.52.17: 'Minor change in data for resource(s): pw_moffice' + 0.52.18: 'Minor change in data for resource(s): pw_moffice' + 0.52.19: 'Minor change in data for resource(s): pw_moffice' + 0.52.20: 'Minor change in data for resource(s): pw_moffice' + 0.52.21: 'Minor change in data for resource(s): pw_moffice' + 0.52.22: 'Minor change in data for resource(s): pw_moffice' + 0.52.23: 'Minor change in data for resource(s): pw_moffice' + 0.52.24: 'Minor change in data for resource(s): pw_moffice' + 0.52.25: 'Minor change in data for resource(s): pw_moffice' + 0.52.26: 'Minor change in data for resource(s): pw_moffice' + 0.52.27: 'Minor change in data for resource(s): pw_moffice' + 0.52.28: 'Minor change in data for resource(s): pw_moffice' + 0.52.29: 'Minor change in data for resource(s): pw_moffice' + 0.52.30: 'Minor change in data for resource(s): pw_moffice' + 0.52.31: 'Minor change in data for resource(s): pw_moffice' + 0.52.32: 'Minor change in data for resource(s): pw_moffice' + 0.52.33: 'Minor change in data for resource(s): pw_moffice' + 0.52.34: 'Minor change in data for resource(s): pw_moffice' + 0.52.35: 'Minor change in data for resource(s): pw_moffice' + 0.52.36: 'Minor change in data for resource(s): pw_moffice' + 0.52.37: 'Minor change in data for resource(s): pw_moffice' + 0.52.38: 'Minor change in data for resource(s): pw_moffice' + 0.52.39: 'Minor change in data for resource(s): pw_moffice' + 0.52.40: 'Minor change in data for resource(s): pw_moffice' + 0.52.41: 'Minor change in data for resource(s): pw_moffice' + 0.52.42: 'Minor change in data for resource(s): pw_moffice' + 0.52.43: 'Minor change in data for resource(s): pw_moffice' + 0.53.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.54.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.55.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.56.0: 'Change in data for resource(s): pw_vote' + 0.56.1: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.56.2: 'Minor change in data for resource(s): pw_moffice' + 0.56.3: 'Minor change in data for resource(s): pw_moffice' + 0.57.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.58.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.59.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.60.0: 'Change in data for resource(s): pw_vote' + 0.60.1: 'Minor change in data for resource(s): pw_moffice' + 0.60.2: 'Minor change in data for resource(s): pw_moffice' + 0.60.3: 'Minor change in data for resource(s): pw_moffice' + 0.61.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.61.1: 'Minor change in data for resource(s): pw_moffice' + 0.61.2: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.61.3: 'Minor change in data for resource(s): pw_moffice' + 0.61.4: 'Minor change in data for resource(s): pw_moffice' + 0.61.5: 'Minor change in data for resource(s): pw_moffice' + 0.61.6: 'Minor change in data for resource(s): pw_moffice' + 0.61.7: 'Minor change in data for resource(s): pw_moffice' + 0.61.8: 'Minor change in data for resource(s): pw_moffice' + 0.61.9: 'Minor change in data for resource(s): pw_moffice' + 0.61.10: 'Minor change in data for resource(s): pw_moffice' + 0.61.11: 'Minor change in data for resource(s): pw_moffice' + 0.61.12: 'Minor change in data for resource(s): pw_moffice' + 0.61.13: 'Minor change in data for resource(s): pw_moffice' + 0.61.14: 'Minor change in data for resource(s): pw_moffice' + 0.61.15: 'Minor change in data for resource(s): pw_moffice' + 0.61.16: 'Minor change in data for resource(s): pw_moffice' + 0.61.17: 'Minor change in data for resource(s): pw_moffice' + 0.61.18: 'Minor change in data for resource(s): pw_moffice' + 0.61.19: 'Minor change in data for resource(s): pw_moffice' + 0.61.20: 'Minor change in data for resource(s): pw_moffice' + 0.61.21: 'Minor change in data for resource(s): pw_moffice' + 0.61.22: 'Minor change in data for resource(s): pw_moffice' + 0.61.23: 'Minor change in data for resource(s): pw_moffice' + 0.61.24: 'Minor change in data for resource(s): pw_moffice' + 0.61.25: 'Minor change in data for resource(s): pw_moffice' + 0.61.26: 'Minor change in data for resource(s): pw_moffice' + 0.61.27: 'Minor change in data for resource(s): pw_moffice' + 0.61.28: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.62.0: 'Change in data for resource(s): pw_division,pw_vote' + 0.63.0: 'Change in data for resource(s): pw_vote' + 0.63.1: 'Minor change in data for resource(s): pw_moffice' + 0.63.2: 'Minor change in data for resource(s): pw_moffice' + 0.63.3: 'Minor change in data for resource(s): pw_moffice' + 0.63.4: 'Minor change in data for resource(s): pw_moffice' + 0.63.5: 'Minor change in data for resource(s): pw_division,pw_moffice' + 0.63.6: 'Minor change in data for resource(s): pw_moffice' + 0.63.7: 'Minor change in data for resource(s): pw_moffice' + 0.63.8: 'Minor change in data for resource(s): pw_moffice' + 0.63.9: 'Minor change in data for resource(s): pw_moffice' + 0.63.10: 'Minor change in data for resource(s): pw_moffice' diff --git a/data/packages/public_whip_data/pw_division.resource.yaml b/data/packages/public_whip_data/pw_division.resource.yaml new file mode 100644 index 00000000..d1b5200b --- /dev/null +++ b/data/packages/public_whip_data/pw_division.resource.yaml @@ -0,0 +1,107 @@ +title: Divisions +description: Division data table +custom: + row_count: 12958 +path: pw_division.parquet +name: pw_division +profile: data-resource +scheme: file +format: parquet +hashing: md5 +encoding: utf-8 +schema: + fields: + - name: division_id + type: integer + description: ID of division + constraints: + unique: true + example: 10387 + - name: valid + type: integer + description: Validity of division (Always true) + constraints: + unique: false + enum: + - 1 + example: 1 + - name: division_date + type: string + description: Date of division + constraints: + unique: false + example: '1997-05-19' + - name: division_number + type: integer + description: Division number + constraints: + unique: false + example: 0 + - name: division_name + type: string + description: Division name + constraints: + unique: false + example: '"Duration of planning permission and consent' + - name: source_url + type: string + description: URL of source + constraints: + unique: false + example: '' + - name: motion + type: string + description: HTML text of motion + constraints: + unique: false + example: "

I beg to move amendment\ + \ No. 22, in page 3, line 20, at end insert—

\n\n

'(6A) In subsection (5) (motion for resolution\ + \ not to be moved unless certain conditions are satisfied), for \"Presiding\ + \ Officer in pursuance of a notice\" there is substituted \"Secretary of State\"\ + .'.

\n\n

Motion made, and Question\ + \ put, That the clause stand part of the Bill:—

\n\n

The Committee divided: Ayes 338, Noes 8.

" + - name: notes + type: string + description: Text of any associated database notes + constraints: + unique: false + enum: + - '' + example: '' + - name: debate_url + type: string + description: URL of debate + constraints: + unique: false + example: '' + - name: source_gid + type: string + description: GID of source + constraints: + unique: false + example: '' + - name: debate_gid + type: string + description: GID of debate + constraints: + unique: false + example: '' + - name: house + type: string + description: Which house the division was in + constraints: + unique: false + enum: + - commons + - lords + - scotland + example: commons + - name: clock_time + type: string + description: Time of division + constraints: + unique: false + example: '' +hash: 8b5cf7b11d26e443faf3d7369b446d61 diff --git a/data/packages/public_whip_data/pw_dyn_dreammp.resource.yaml b/data/packages/public_whip_data/pw_dyn_dreammp.resource.yaml new file mode 100644 index 00000000..09f68e53 --- /dev/null +++ b/data/packages/public_whip_data/pw_dyn_dreammp.resource.yaml @@ -0,0 +1,51 @@ +title: Dream MP data table +description: Datatable of 'dream MPs', user created to compare actual MPs to (powers + TWFY policy lines) +custom: + row_count: 6975 +path: pw_dyn_dreammp.parquet +name: pw_dyn_dreammp +profile: data-resource +scheme: file +format: parquet +hashing: md5 +encoding: utf-8 +schema: + fields: + - name: dream_id + type: integer + description: ID of dream MP + constraints: + unique: true + example: 1 + - name: name + type: string + description: Name of dream MP + constraints: + unique: false + example: ' unofficial Nationalist party' + - name: user_id + type: integer + description: ID of user who created dream MP + constraints: + unique: false + example: 1 + - name: description + type: string + description: Description of dream MP + constraints: + unique: false + example: "\r\nDefinition: This new left of centre party stands for Equality, Social\ + \ Justice, Trade Unionism, Solidarity, Internationalism and Progressivism. \r\ + \n" + - name: private + type: integer + description: Whether dream MP is private or visible on site + constraints: + unique: false + enum: + - 1 + - 2 + - 0 + example: 0 +hash: 22a9374b9f4b089a2ee0e606fbd0a89d diff --git a/data/packages/public_whip_data/pw_dyn_dreamvote.resource.yaml b/data/packages/public_whip_data/pw_dyn_dreamvote.resource.yaml new file mode 100644 index 00000000..f7e17b5a --- /dev/null +++ b/data/packages/public_whip_data/pw_dyn_dreamvote.resource.yaml @@ -0,0 +1,54 @@ +title: Dream vote data table +description: Connection of vote alignment to dream MP +custom: + row_count: 14829 +path: pw_dyn_dreamvote.parquet +name: pw_dyn_dreamvote +profile: data-resource +scheme: file +format: parquet +hashing: md5 +encoding: utf-8 +schema: + fields: + - name: division_date + type: string + description: Date of division + constraints: + unique: false + example: '1997-05-19' + - name: division_number + type: integer + description: Division number + constraints: + unique: false + example: 0 + - name: dream_id + type: integer + description: ID of dream MP + constraints: + unique: false + example: 1 + - name: vote + type: string + description: Vote of MP + constraints: + unique: false + enum: + - no3 + - both + - aye + - 'no' + - aye3 + example: aye + - name: house + type: string + description: House of MP + constraints: + unique: false + enum: + - commons + - lords + - scotland + example: commons +hash: 46cbcf912a0a3f3a55ec986ec00b4ad2 diff --git a/data/packages/public_whip_data/pw_dyn_wiki_motion.resource.yaml b/data/packages/public_whip_data/pw_dyn_wiki_motion.resource.yaml new file mode 100644 index 00000000..7ecb30c7 --- /dev/null +++ b/data/packages/public_whip_data/pw_dyn_wiki_motion.resource.yaml @@ -0,0 +1,90 @@ +title: Motion data table +description: Datatable of motions edited in wiki +custom: + row_count: 10603 +path: pw_dyn_wiki_motion.parquet +name: pw_dyn_wiki_motion +profile: data-resource +scheme: file +format: parquet +hashing: md5 +encoding: utf-8 +schema: + fields: + - name: wiki_id + type: integer + description: ID of wiki motion + constraints: + unique: true + example: 1 + - name: text_body + type: string + description: Text of motion + constraints: + unique: false + example: "\r\n\r\n--- MOTION EFFECT ---\r\n\r\nThe Aye-voters set to the timetable\ + \ for the Standing Committee to complete its scrutiny of the Road Safety Bill by Thursday 3rd February 2005.\r\n\r\nOnce it returns\ + \ to the House and its amendments have been accepted, there is a Third Reading\ + \ Debate, after which the Bill is sent to the House of Lords before it becomes\ + \ law.\r\n\r\n\ + \ Detailed documentation on this bill can be found at the Department for Transport.\ + \ \r\n\r\n\r\n--- COMMENTS AND NOTES ---\r\n\r\n

Motion\ + \ made, and Question put forthwith, pursuant to Standing Order No. 83A(6),

\r\ + \n\r\n

That the following provisions shall apply to the Road\ + \ Safety Bill:

\r\n\r\n

Committal

\r\n\r\n1. The Bill shall be committed to a Standing Committee.

\r\ + \n\r\n

Proceedings in Standing Committee

\r\n\r\n\ +

2. Proceedings in the Standing Committee shall (so far as\ + \ not previously concluded) be brought to a conclusion on Thursday 3rd February\ + \ 2005.

\r\n\r\n

3. The Standing Committee shall have\ + \ leave to sit twice on the first day on which it meets.

\r\n\r\n

Consideration and Third Reading

\r\n\r\n

4. Proceedings on consideration shall (so far as not previously concluded)\ + \ be brought to a conclusion one hour before the moment of interruption on the\ + \ day on which those proceedings are commenced.

\r\n\r\n

5. Proceedings on Third Reading shall (so far as not previously concluded)\ + \ be brought to a conclusion at the moment of interruption on that day.

\r\ + \n\r\n

Programming Committee

\r\n\r\n

6. Standing Order No. 83B (Programming committees) shall not apply\ + \ to proceedings on consideration and Third Reading.

\r\n\r\n

Other proceedings

\r\n\r\n

7. Any other\ + \ proceedings on the Bill (including any proceedings on consideration of Lords\ + \ Amendments or on any further messages from the Lords) may be programmed. —[Mr.\ + \ Watson.]

\r\n\r\n

The House divided: Ayes 330, Noes\ + \ 161.

\r\n\r\n" + - name: user_id + type: integer + description: ID of user who edited the motion + constraints: + unique: false + example: 1 + - name: edit_date + type: string + description: Date of edit + constraints: + unique: false + example: '2005-01-15 15:09:24' + - name: division_date + type: string + description: Date of division + constraints: + unique: false + example: '1997-05-19' + - name: division_number + type: integer + description: Number of division + constraints: + unique: false + example: 1 + - name: house + type: string + description: House of parliament + constraints: + unique: false + enum: + - commons + - lords + example: commons +hash: 4bb066cb217b4eca5f1b433759a724e0 diff --git a/data/packages/public_whip_data/pw_moffice.resource.yaml b/data/packages/public_whip_data/pw_moffice.resource.yaml new file mode 100644 index 00000000..e3de2f23 --- /dev/null +++ b/data/packages/public_whip_data/pw_moffice.resource.yaml @@ -0,0 +1,59 @@ +title: Ministerial office data table +description: Connection of ministerial office to person +custom: + row_count: 19738 +path: pw_moffice.parquet +name: pw_moffice +profile: data-resource +scheme: file +format: parquet +hashing: md5 +encoding: utf-8 +schema: + fields: + - name: moffice_id + type: integer + description: ID of ministerial office + constraints: + unique: true + example: 69428546 + - name: dept + type: string + description: Department of ministerial office + constraints: + unique: false + example: '' + - name: position + type: string + description: Position of ministerial office + constraints: + unique: false + example: ' Spokesperson for the Cabinet Office, Spokesperson for Constitutional + Affairs, Spokesperson for Scotland' + - name: from_date + type: string + description: Start date of ministerial office + constraints: + unique: false + example: '1947-01-01' + - name: to_date + type: string + description: End date of ministerial office + constraints: + unique: false + example: '1950-01-01' + - name: person + type: integer + description: ID of person + constraints: + unique: false + example: 10001 + - name: responsibility + type: string + description: Responsibility of ministerial office + constraints: + unique: false + enum: + - '' + example: '' +hash: 86a6cb5e8f5e1852eb089135d65a19e1 diff --git a/data/packages/public_whip_data/pw_mp.resource.yaml b/data/packages/public_whip_data/pw_mp.resource.yaml new file mode 100644 index 00000000..2863fdae --- /dev/null +++ b/data/packages/public_whip_data/pw_mp.resource.yaml @@ -0,0 +1,106 @@ +title: MP membership table +description: Datatable of MP information +custom: + row_count: 7230 +path: pw_mp.parquet +name: pw_mp +profile: data-resource +scheme: file +format: parquet +hashing: md5 +encoding: utf-8 +schema: + fields: + - name: mp_id + type: integer + description: ID of MP + constraints: + unique: true + example: 1 + - name: first_name + type: string + description: First name of MP + constraints: + unique: false + example: '' + - name: last_name + type: string + description: Last name of MP + constraints: + unique: false + example: '' + - name: title + type: string + description: Title of MP + constraints: + unique: false + example: '' + - name: constituency + type: string + description: Constituency of MP + constraints: + unique: false + example: '' + - name: party + type: string + description: Party of MP + constraints: + unique: false + example: Alba + - name: entered_house + type: string + description: Date of MP entering house + constraints: + unique: false + example: '1935-00-00' + - name: left_house + type: string + description: Date of MP leaving house + constraints: + unique: false + example: '1997-05-08' + - name: entered_reason + type: string + description: Reason for MP entering house + constraints: + unique: false + enum: + - general_election + - by_election + - changed_party + - reinstated + - unknown + - regional_election + - replaced_in_region + - became_presiding_officer + - appointed + example: appointed + - name: left_reason + type: string + description: Reason for MP leaving house + constraints: + unique: false + example: became_peer + - name: person + type: integer + description: ID of person + constraints: + unique: false + example: 10001 + - name: house + type: string + description: House of MP + constraints: + unique: false + enum: + - commons + - scotland + - lords + example: commons + - name: gid + type: string + description: ID of MP + constraints: + unique: true + example: uk.org.publicwhip/lord/100001 +hash: 80eebd3e6c0b07d8a3d68008bd5a00bf diff --git a/data/packages/public_whip_data/pw_vote.resource.yaml b/data/packages/public_whip_data/pw_vote.resource.yaml new file mode 100644 index 00000000..81c99937 --- /dev/null +++ b/data/packages/public_whip_data/pw_vote.resource.yaml @@ -0,0 +1,40 @@ +title: Vote data table +description: Datatable of votes +custom: + row_count: 4280371 +path: pw_vote.parquet +name: pw_vote +profile: data-resource +scheme: file +format: parquet +hashing: md5 +encoding: utf-8 +schema: + fields: + - name: division_id + type: integer + description: ID of division + constraints: + unique: false + example: 10387 + - name: mp_id + type: integer + description: ID of MP + constraints: + unique: false + example: 1 + - name: vote + type: string + description: Vote of MP + constraints: + unique: false + enum: + - 'no' + - aye + - tellno + - tellaye + - both + - abstention + - spoiled + example: abstention +hash: 43aeebf66bf5b310789be00ee48778cb diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..9b5b84f4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +services: + app: + image: mysociety/publicwhip-data:${TAG:-latest} + build: + context: . + dockerfile: Dockerfile.dev + working_dir: /workspaces/publicwhip_data + volumes: + - ./:/workspaces/publicwhip_data/ \ No newline at end of file diff --git a/docs/.gitinclude b/docs/.gitinclude new file mode 100644 index 00000000..e69de29b diff --git a/docs/Gemfile b/docs/Gemfile new file mode 100644 index 00000000..7ee04945 --- /dev/null +++ b/docs/Gemfile @@ -0,0 +1,46 @@ +source "https://rubygems.org" + +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! + +# needed for newer versions of ruby locally +gem 'webrick' + +gem "jekyll", "~> 3.9.2" + +# This is the default theme for new Jekyll sites. You may change this to anything you like. +gem "minima", "~> 2.0" + +# If you want to use GitHub Pages, remove the "gem "jekyll"" above and +# uncomment the line below. To upgrade, run `bundle update github-pages`. +# gem "github-pages", group: :jekyll_plugins + +# If you have any plugins, put them here! +group :jekyll_plugins do + gem "jekyll-feed", "~> 0.6" +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +# and associated library. +install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do + gem "tzinfo", "~> 1.2" + gem "tzinfo-data" +end + +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1.0", :install_if => Gem.win_platform? + +# kramdown v2 ships without the gfm parser by default. If you're using +# kramdown v1, comment out this line. +gem "kramdown-parser-gfm" + +# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem +# do not have a Java counterpart. +gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] +gem "github-pages", "~> 226", group: :jekyll_plugins \ No newline at end of file diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 00000000..a695c60d --- /dev/null +++ b/docs/Gemfile.lock @@ -0,0 +1,274 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (6.0.6.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.11.1) + colorator (1.1.0) + commonmarker (0.23.8) + concurrent-ruby (1.2.0) + dnsruby (1.61.9) + simpleidn (~> 0.1) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) + eventmachine (1.2.7) + execjs (2.8.1) + faraday (2.7.4) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) + ffi (1.15.5) + forwardable-extended (2.6.0) + gemoji (3.0.1) + github-pages (226) + github-pages-health-check (= 1.17.9) + jekyll (= 3.9.2) + jekyll-avatar (= 0.7.0) + jekyll-coffeescript (= 1.1.1) + jekyll-commonmark-ghpages (= 0.2.0) + jekyll-default-layout (= 0.1.4) + jekyll-feed (= 0.15.1) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.13.0) + jekyll-include-cache (= 0.2.1) + jekyll-mentions (= 1.6.0) + jekyll-optional-front-matter (= 0.3.2) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.16.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.3) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.8.0) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.12.0) + kramdown (= 2.3.2) + kramdown-parser-gfm (= 1.1.0) + liquid (= 4.0.3) + mercenary (~> 0.3) + minima (= 2.5.1) + nokogiri (>= 1.13.4, < 2.0) + rouge (= 3.26.0) + terminal-table (~> 1.4) + github-pages-health-check (1.17.9) + addressable (~> 2.3) + dnsruby (~> 1.60) + octokit (~> 4.0) + public_suffix (>= 3.0, < 5.0) + typhoeus (~> 1.3) + html-pipeline (2.14.3) + activesupport (>= 2) + nokogiri (>= 1.4) + http_parser.rb (0.8.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jekyll (3.9.2) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 0.7) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (>= 1.17, < 3) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-avatar (0.7.0) + jekyll (>= 3.0, < 5.0) + jekyll-coffeescript (1.1.1) + coffee-script (~> 2.2) + coffee-script-source (~> 1.11.1) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.2.0) + commonmarker (~> 0.23.4) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) + rouge (>= 2.0, < 4.0) + jekyll-default-layout (0.1.4) + jekyll (~> 3.0) + jekyll-feed (0.15.1) + jekyll (>= 3.7, < 5.0) + jekyll-gist (1.5.0) + octokit (~> 4.2) + jekyll-github-metadata (2.13.0) + jekyll (>= 3.4, < 5.0) + octokit (~> 4.0, != 4.4.0) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-mentions (1.6.0) + html-pipeline (~> 2.3) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.6.0) + jekyll (> 3.5, < 5.0) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + jemoji (0.12.0) + gemoji (~> 3.0) + html-pipeline (~> 2.2) + jekyll (>= 3.0, < 5.0) + kramdown (2.3.2) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.3) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.3.6) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.17.0) + nokogiri (1.14.2-x86_64-linux) + racc (~> 1.4) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (4.0.7) + racc (1.6.2) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.5) + rouge (3.26.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + safe_yaml (1.0.5) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + simpleidn (0.2.1) + unf (~> 0.1.4) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thread_safe (0.3.6) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (1.2.11) + thread_safe (~> 0.1) + tzinfo-data (1.2022.7) + tzinfo (>= 1.0.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) + unicode-display_width (1.8.0) + wdm (0.1.1) + webrick (1.8.1) + zeitwerk (2.6.7) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + github-pages (~> 226) + http_parser.rb (~> 0.6.0) + jekyll (~> 3.9.2) + jekyll-feed (~> 0.6) + kramdown-parser-gfm + minima (~> 2.0) + tzinfo (~> 1.2) + tzinfo-data + wdm (~> 0.1.0) + webrick + +BUNDLED WITH + 2.4.6 diff --git a/docs/data.json b/docs/data.json new file mode 100644 index 00000000..1df77e47 --- /dev/null +++ b/docs/data.json @@ -0,0 +1,3 @@ +--- +layout: datasets/data +--- diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..3b9eaf44 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,8 @@ +--- +title: "Download publicwhip_data" +layout: datasets/front +--- + +# Public whip data + +Public whip database dumps reformatted for ease of access through duckdb. \ No newline at end of file diff --git a/docs/sass/_bootstrap-compat.scss b/docs/sass/_bootstrap-compat.scss new file mode 100644 index 00000000..09777399 --- /dev/null +++ b/docs/sass/_bootstrap-compat.scss @@ -0,0 +1,237 @@ +// mysociety-docs-theme has never been fully combined with Bootstrap, +// and if you just include both, you end up with conflicts over selectors +// like `body`, `h1`, `.container` etc. + +// One day, we’ll need to bite the bullet and properly integrate Bootstrap +// into this site. But for now, we can make do with just approximating +// the Bootstrap components we really need, like `.nav-tabs`. + +.text-center { + @extend .text--center; +} + +.pull-left { + float: left !important; +} + +.pull-right { + float: right !important; +} + +.nav-tabs { + @extend .unstyled-list; + @include clearfix(); + margin: 1em -0.2em; + + li { + float: left; + margin: 0 0.2em; + } + + a { + display: block; + padding: 0.2em 0.5em; + border-radius: 3px; + + &:hover, + &:focus { + text-decoration: none; + background-color: $colour_off_white; + } + } + + .active { + a, + a:hover, + a:focus { + background-color: $colour_links; + color: #fff; + } + } +} + +.jumbotron { + padding: 1em; + margin: 1em 0; + border-radius: 3px; + background-color: $colour_off_white; + + @media (min-width: $medium-screen) { + padding: 2em; + } +} + +.col-sm-3, .col-sm-9,.col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} + +@media (min-width: 768px){ + +.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; +} + +.col-sm-3 { + width: 25%; +} + + +.col-sm-9 { + width: 75%; +} + + +.col-sm-4 { + width: 33.33333333%; + +} + +.col-sm-8 { + width: 66.66666667%; + } + +.col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9 { + float: left; +} +} + + +.media, .media-body { + zoom: 1; + overflow: hidden; +} + +.media { + margin-top: 15px; +} + +.media-left, .media-right, .media-body { + display: table-cell; + vertical-align: top; +} + +.media-left, .media>.pull-left { + padding-right: 10px; +} + + +.media-object { + display: block; +} + +.media-body { + width: 10000px; +} + +.panel { + margin-bottom: 21px; + background-color: #ffffff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.05); + box-shadow: 0 1px 1px rgba(0,0,0,0.05); +} + +.panel-default { + border-color: #ecf0f1; +} + +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} + +.panel-default>.panel-heading { + color: #2c3e50; + background-color: #ecf0f1; + border-color: #ecf0f1; +} + +.panel-body { + padding: 15px; +} + + +.form-control { + display: block; + width: 100%; + height: 45px; + padding: 10px 15px; + font-size: 15px; + line-height: 1.42857143; + color: #2c3e50; + background-color: #ffffff; + background-image: none; + border: 1px solid #dce4ec; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; +} + +.form-control, input { + border-width: 2px; + -webkit-box-shadow: none; + box-shadow: none; +} + + +.list-group { + margin-bottom: 20px; + padding-left: 0; + line-height: 1em; +} + +a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.active:focus { + border-color: #ecf0f1; +} + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #ffffff; + border: 1px solid #ecf0f1; + .badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: 700; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: middle; + background-color: #777; + border-radius: 10px; + } + > .badge { + float: right; + } +} + +.list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} + +.list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus { + z-index: 2; + color: #ffffff; + background-color: #2c3e50; + border-color: #2c3e50; +} + +.row { + box-sizing: border-box; + display: block; +} \ No newline at end of file diff --git a/docs/sass/_header.scss b/docs/sass/_header.scss new file mode 100644 index 00000000..f85e27f4 --- /dev/null +++ b/docs/sass/_header.scss @@ -0,0 +1,64 @@ +.site-header { + color: #fff; + + a { + color: inherit; + } + + h1 { + font-size: 2em; + margin-bottom: 0.2em; + + @media (min-width: 48em) { + max-width: 50%; + } + } +} + +a.site-header__research-home-link { + color: mix($colour_brand, #fff, 20%); + + &:hover, &:focus { + color: inherit; + } +} + +.nav-position { + @media (min-width: 48em) { + position: absolute; + top: 50%; + right: 160px; + margin-top: -1em; + } +} + +.site-nav { + ul { + margin-top: 0; + margin-bottom: 0; + @extend .unstyled-list; + } + + li { + @media (min-width: 47.5em) { + display: inline-block; + } + } + + a { + margin-right: 0.33em; + display: block; + padding: 0.33em; + border-top: 1px solid rgba(#fff, 0.2); + + @media (min-width: 47.5em) { + display: inline-block; + border-top: none; + } + + @media (min-width: $large-screen) { + font-size: 1.125em; + margin-right: 0.66em; + } + } +} diff --git a/docs/sass/mysoc.scss b/docs/sass/mysoc.scss new file mode 100644 index 00000000..ef0f98d0 --- /dev/null +++ b/docs/sass/mysoc.scss @@ -0,0 +1,44 @@ +--- +--- +$colour_brand: #4faded; // $colour_blue +$colour_links: #f3f1eb; // $colour_off_white +$colour_background: #ffffff; +$colour_yellow: #FFD836; +$colour_green: #62B356; +$colour_red: #E04B4B; +$colour_violet: #A94CA6; +$colour_orange: #F4A140; +$colour_green: #62B356; +$theme_dir: '../theme'; + + +@import "global"; +@import "bootstrap-compat"; +@import "header"; +@import "mysoc-download"; + +.main-content { + background: #fff; + padding: 1.6em 5%; + position: relative; + margin-bottom: $base-spacing-unit; + + h2 { + border-top: 0px; + padding-top: 1px; + } + + h3 { + padding-top: 10px; + } + + .lead+h2 { + border-top: none; + padding-top: 0; + } + + .reveal-on-click+h2 { + border-top: none; + } + +} \ No newline at end of file diff --git a/notebooks/_render_config/default.yaml b/notebooks/_render_config/default.yaml new file mode 100644 index 00000000..e96e9803 --- /dev/null +++ b/notebooks/_render_config/default.yaml @@ -0,0 +1,16 @@ +title: "{{page_title}}" +slug: "{{page_title.lower().replace(' ', '_')}}" +notebooks: + - example +parameters: + page_title: "{{settings.default_page_title}}" +context: + data_common.management.settings: + - settings +options: + rerun: true + hide_input: true +upload: + gdrive: + g_folder_id: blank + g_drive_id: blank \ No newline at end of file diff --git a/notebooks/example.ipynb b/notebooks/example.ipynb new file mode 100644 index 00000000..c1de8787 --- /dev/null +++ b/notebooks/example.ipynb @@ -0,0 +1,274 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from data_common.notebook import *\n", + "\n", + "notebook_setup()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following cell is an example of a parameters cells." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "tags": [ + "parameters" + ] + }, + "outputs": [], + "source": [ + "# default-params\n", + "page_title = \"Original title\"\n", + "# The new parameters will be injected as a new cell below this using papermill." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "## Original title" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "md(\"## \" + page_title)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example code\n", + "\n", + "Start the notebook by pulling in the general set of helpers. Start the codeblock with `#HIDE` to exclude it from being displayed in render, even when 'hide_input' is false (example at top of `readme.ipynb`)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from data_common.notebook import *\n", + "\n", + "notebook_setup()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example table\n", + "\n", + "This shows a table being rendered as markdown" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
VerticalHorizontaldouble_h
0A2856
1B55110
2C4386
3D91182
4E81162
5F53106
6G1938
7H87174
8I52104
\n", + "
" + ], + "text/plain": [ + " Vertical Horizontal double_h\n", + "0 A 28 56\n", + "1 B 55 110\n", + "2 C 43 86\n", + "3 D 91 182\n", + "4 E 81 162\n", + "5 F 53 106\n", + "6 G 19 38\n", + "7 H 87 174\n", + "8 I 52 104" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "source = pd.DataFrame(\n", + " {\n", + " \"Vertical\": [\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\"],\n", + " \"Horizontal\": [28, 55, 43, 91, 81, 53, 19, 87, 52],\n", + " }\n", + ")\n", + "\n", + "source[\"double_h\"] = source[\"Horizontal\"] * 2\n", + "\n", + "source" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example chart" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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OxdEB9dqFG2FFVZwW4ZrlzXftsBRyyMfBCAAAQiYIUCIAghAAAIQgAAEIAABCEAgNQIIkNRQUxAEIAABCEAAAhCAAAQggAAhBiAAAQhAAAIQgAAEIACB1AggQFJDTUEQgAAEIAABCEAAAhCAAAKEGIAABCAAAQhAAAIQgAAEUiOAAEkNNQVBAAIQgAAEIAABCEAAAhUFyLRp02zPPfc07ZM+cuRI6927d4nUp59+asOGDbM55pjDLrroosQJqmydZqy9/Ndee+1Y99cpyNq7fZlllrE//vGPse7FxRCAAAQgAAEIQAACEIBAfAJVR0AGDRrk9kW/+eabbdttty2VdMUVVzhxon3Rr7vuujZZIGGz66672vLLL2/HHntsq3s8+eSTtvrqq9vGG29sd999d5vuH1z0wAMP2Prrr29rrrmmPfLII7HuxcUQgAAEIAABCEAAAhCAQHwCVQXIWWedZUcccYTtvffedskll5RK2mmnnezGG2904kMipC3po48+coeFrbvuuiaREE7ffPONbbnlljZkyBAndOIkBEgcelwLAQhAAAIQgAAEIACB5AlUFSBvvPGGLbXUUk4ofPjhh67kGTNm2DzzzGOTJk2yzz//3GaffXb74osv7OKLL7bXX3/dFl98cdtvv/1srrnmcvmDKVC77LKLu+a+++6zxRZbzJ5++mk3ujHvvPPaWmut5fKuttpqdtBBB5WuCU+b+u677+zqq6+25557znr06OHEyYorruiu02+33nqrSdQsvPDCttdee1nfvn3d3xAgyQcMd4QABCAAAQhAAAIQgEAcAjUXoUuASIi8+OKLbrrU2LFjbZVVVnFrMx566CF76623nICYMGFCyQaJEOXv1q1bSQAMGDDAXnjhBZfn0EMPtXPOOWcmm3fccUe74YYbZhIN48ePdyMlb7/9dumajh072v333+9GZq6//vpW95L4eemll2y++eZDgMSJDK6FAAQgAAEIQAACEIBAAwjUFCCagqWpWGeeeaabjnXSSSfZiSee6ATEIYcc4sSH1on87W9/c2stlOf222+3c8891w4++OCSAJDdyn/AAQdY165dTes8dthhB1tjjTVs9OjR7rEkWLTYvXzUYp111rGHH37Y5Vf57777rl177bWmtSgqR/faf//93ejL4Ycf7kZDjjvuODvllFMQIA0IGG4JAQhAAAIQ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AALtnsADDzxg66+/vq255pr2yCOPlHgEv6+77rqm/x83Pfnkk7b66qvbxhtvbHfffXfc20W+/qeffrJdd93V2iKkqrGJXDgZIQABCEAgEgEESCRMZIIABCDQHATSEiDffPONbbnlljZkyBDbc889U4P30Ucf2QILLGBtEVIIkNTcREEQgEA7J4AAaecBwONDAALti0C9AuTbb7+1q666yp5//nmbY445bPDgwbbiiiuWoAXTlnbZZRebNGmS3XfffbbYYovZtttuO9NUr5122mkm2KuttpoddNBB7veoZWmEY9ZZZ7VbbrnFunTpYjvuuKPpPkqbbrqpG3GZd955ba211nK/hct47rnn7NZbbzUJlYUXXtj22msv69u3r8uHAGlf7wJPCwEIZEcAAZIde0qGAAQgkDqBegTI+PHj3UjC22+/XbKzQ4cOduaZZ9rhhx/eqtE+YMAAe+GFF9xvhx56qJt6VT7VS9eWpwMPPNDOP/98q6csCSAJiSB17tzZxo4da7/+9a+tUhkSKDfccIPtvPPOdv3117cyYa655rKXXnrJ5ptvPgRI6tFIgRCAQHslgABpr57nuSEAgXZJIBAgffr0caMFQdKIwD333NNq6tJ6661nDz74oGnk4vjjj7dXX33VjRhopEIN/pVWWqnUaNd9DjnkEDvggAOsa9euLm+5APnkk09ccRIqm222mfXu3dtee+01m2eeeayesiQyjjnmGGfLiSeeaFdffbUr969//avddNNNtsMOO9gaa6xho0ePduV169bNlfXnP//ZtDZl//33t8UXX9yJKI2GHHfccXbKKacgQNrlG8FDQwACWRBAgGRBnTIhAAEIZEQgECDVig/WTnz44YduatLss89uEyZMsO7du7tLTjrpJNfoDxr8wf2WWGIJe/3110sjENVGWr7//ns3hUvCY+TIkbbvvvtavWVJXIwZM8bZ889//tM233xzJ6b0/32L6VtaWuzZZ5+1xx9/3O6//34nurbeemsnRJiClVFQUiwEINDuCCBA2p3LeWAIQKA9Ewga2b/97W/t2muvLaHQyMDuu+9eGgFRA10NfeXTaEeQ7rzzTre1rkYw9P99U7rKd9uS4Bg1apRbl6EyNJoRp6xywVFLgDz88MM2dOhQe/PNN93jaE3LV199VXpmBEh7fjN4dghAIE0CCJA0aVMWBCAAgYwJ+ARDMAKixeXaynaRRRaxd955p2T1ZZddZnvvvbfb6lZTn3z3CwuQ2267zbbZZhubZZZZ3KL25ZZbzt03TllRBciXX35p/fr1s6lTp7o1LHvssYdbR6JpYsEzI0AyDk6KhwAE2g0BBEi7cTUPCgEIQKD6Tk/lDfnp06e7XaI0Peq6665zu19p7cegQYNs3LhxduWVV7otdqMKEE3jWmGFFUxC4KijjrLTTz+95I44ZZXbrelVWpsi8fTiiy+2GuHRuSQSPVp0rqlYOohRhzIiQHgzIAABCKRLAAGSLm9KgwAEIJApAZ9gCJ+foW1ut99+e2evGvRaqD5x4kR3wKAOMdTuU777BSMgOg09OPhw5ZVXdtvoKq2zzjpOCLS1rHIBMmXKFDdq8+mnn7rT3nv27OkWnGuBukZAJk+ebEsttZTbMvizzz6zTp06OVGl+2hURutTtGBd08SCZ8/UYRQOAQhAoAkJIECa0Kk8EgQgAIFqBHyCofwAv3/961928sknu52rtGZCO2Jpx6hgUbrvfoEAqbQ9rmwMtsjV/29LWZXWfDz00ENuK+BXXnnFCRDtsKUdsXRGiXa+euONN5wI0XPstttupd28NCqi80vuuusut9D+6KOPJpAgAAEIQKABBBAgDYDKLSEAAQhAAAIQgAAEIACBygQQIEQGBCAAAQhAAAIQgAAEIJAaAQRIaqgpCAIQgAAEIAABCEAAAhBAgBADEIAABCAAAQhAAAIQgEBqBBAgqaGmIAhAAAIQgAAEIAABCEAAAUIMQAACEIAABCAAAQhAAAKpEUCApIaagiAAAQhAAAIQgAAEIAABBAgxAAEIQAACEIAABCAAAQikRgABkhpqCoIABCAAAQhAAAIQgAAEECDEAAQgAAEIQAACEIAABCCQGgEESGqoKQgCEIAABCAAAQhAAAIQQIAQAxCAAAQgAAEIQAACEIBAagQQIKmhpiAIQAACEIAABCAAAQhAAAFCDEAAAhCAAAQgAAEIQAACqRFAgKSGmoIgAAEIQAACEIAABCAAAQQIMQABCEAAAhCAAAQgAAEIpEYAAZIaagqCAAQgAAEIQAACEIAABBAgxAAEIAABCEAAAhCAAAQgkBoBBEhqqCkIAhCAAAQgAAEIQAACEECAEAMQgAAEIAABCEAAAhCAQGoEmlqAtLS0VAXZoUOH1CDXW1A1u/Nsc73PSH4IQAACEIAABCAAgfZJoKkFyLvvvmv//ve/bcKECfbTTz/ZnHPOaUsttZStuOKK1rNnz9x6fPr06fbEE0/Y5ZdfbnPNNZcdeuihNt988xkCJLcuwzAIQAACEIAABCAAgYgEmk6ATJw40UaPHm2PP/64ffzxxxUxzDLLLLbYYovZQQcdZEsvvXREVOlle++992z//fe3KVOmuEIXX3xxO++886xHjx7pGUFJEIAABCAAAQhAAAIQaACBphIgkyZNssMPP9zeeustL6quXbva2Wefbcsuu6w3b9oZ7rrrLvvzn/9cGvHQaM0VV1zhRkMakTTlSyNE33//vXXr1s26dOnSiGK4JwQgAAEIQAACEIAABKxpBMg333zjpipJfARTldSw7t27t/Xr1886depkGh3RdCz9rsa8pjjNNttsuQuDjz76yPbZZx8nCJSWXHJJNwLSvXv3xG1VWRoteu655+zDz63/iwAAGF5JREFUDz+0448/3n75y18mXg43hAAEIAABCEAAAhCAgAg0jQB54IEH7LTTTnPiQv/TdKVdd93Vttlmm1Y9+hIq9957r33xxRe233775XJdxYwZM5wo0EjI7LPPbrvttpstsMACDbF1yJAh9v7777u3QcLtb3/7Wy5HhXhdIQABCEAAAhCAAASag0DTCBCJj/vvv7/klT333NMJEDWqyxdvS6BooXfnzp2bw4sxniIQIAEjBEgMmFwKAQhAAAIQgAAEIOAlUFGAqAdeIwQ//viju4HWBPziF79w//+7775z05y++uor69ixo80zzzxukXR43YAa+J9//rlpMfW3335rWvStXZwWXnhh9//DSQvFVV6QNM1ojjnmqGj41KlT3X3DgkJTqWaddVY78MAD7aWXXioJjpNOOsnWWGMNL4BaGZxQ+XGa/TDiLJt63Q3WcZFFrMc5I6zL0m2boqTRF01z+vLLL+2HH35w08I0UjPvvPPa/PPPX2IjcfTJJ5+UTJNQmnvuuR3vaknTyzSSoTJ0ve7bp08f69u3rysnnMIctWZGPgiYnnDCCbbEEku47CpPftdiePkxSPpd/qyUxEz3C7YS1n/lU+1ARoIABCAAAQhAAAIQgEBFATJt2jS3FmDcuHGO0HLLLWennnqq+/fJJ5/shIkauUpqHGuNgv7eq1cv99t9991nF1xwgWu4BuJC+VZddVU78cQT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", + "text/plain": [ + "alt.Chart(...)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "chart = (\n", + " Chart(source)\n", + " .mark_bar()\n", + " .encode(y=\"Vertical\", x=\"Horizontal\")\n", + " .properties(\n", + " title=\"This is an example chart, with the right fonts\",\n", + " logo=True,\n", + " caption=\"Data source goes here\",\n", + " width=800,\n", + " )\n", + ")\n", + "\n", + "chart.display()" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "4cd7ab41f5fca4b9b44701077e38c5ffd31fe66a6cab21e0214b68d958d0e462" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + }, + "orig_nbformat": 2 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..e5d3e124 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,34 @@ +[tool.poetry] +name = "publicwhip_data" +version = "0.1.0" +description = "More accessible version of public whip database" +authors = [] + +[tool.poetry.dependencies] +python = ">=3.10,<3.11" +data_common = { path = "src/data_common/", develop = true } + +[tool.poetry.dev-dependencies] +pytest = "^7.1.1" + +[tool.poetry.scripts] +project = 'publicwhip_data.__main__:main' +notebook = "data_common.notebookcli.__main__:run" +dataset = "data_common.dataset.__main__:run" + +[tool.pyright] +include = ["src", "notebooks"] +exclude=["src/data_common/typings"] +stubPath="src/data_common/typings" +typeCheckingMode="basic" +reportPrivateImportUsage="warning" + +[notebook.settings] +default_page_title = "Settings file defined title" + +[tool.dataset] +dataset_dir = "data/packages" +publish_dir = "docs/" +publish_url = "https://pages.mysociety.org/publicwhip_data/" +credit_text = "If you find this data useful, please let us know to help us make the case for future funding." +credit_url = "https://survey.alchemer.com/s3/6876792/Data-usage" \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..6545e9b5 --- /dev/null +++ b/readme.md @@ -0,0 +1,12 @@ + +# publicwhip-data + +[![badge](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/mysociety/publicwhip-data/HEAD) + +More accessible version of public whip database + +This repository is available online at https://github.com/mysociety/publicwhip-data + +If Github Pages are enabled, the URL is: https://mysociety.github.io/publicwhip-data/ + +Instructions on using the features of this notebook (data publishing, notebook rendering, Github Pages) are available in [https://github.com/mysociety/data_common/blob/main/data-repo-readme.md](Data Common readme file). \ No newline at end of file diff --git a/script/server b/script/server new file mode 100644 index 00000000..795c430e --- /dev/null +++ b/script/server @@ -0,0 +1,4 @@ +#!/bin/bash +cd docs +bundle install +bundle exec jekyll serve \ No newline at end of file diff --git a/script/setup b/script/setup new file mode 100644 index 00000000..2c895f61 --- /dev/null +++ b/script/setup @@ -0,0 +1,2 @@ +#!/bin/bash +bash .devcontainer/initializeCommand \ No newline at end of file diff --git a/script/test b/script/test new file mode 100644 index 00000000..e753b90c --- /dev/null +++ b/script/test @@ -0,0 +1,29 @@ +#!/bin/bash + +echo "--------------" +echo "Running pytest" +echo "--------------" +poetry run pytest +pytestexit=$? +echo "--------------" +echo "Running black" +echo "--------------" +poetry run black . --check +blackexit=$? +echo "--------------" +echo "Running pyright" +echo "--------------" +poetry run pyright +pyrightexit=$? + +echo "Pytest status: $pytestexit" +echo "Black status: $blackexit" +echo "Pyright status: $pyrightexit" + +sum="$(($pytestexit + $blackexit + $pyrightexit))" + +if [ "$sum" != "0" ]; then + exit 1 +else + exit 0 +fi \ No newline at end of file diff --git a/script/update-from-template b/script/update-from-template new file mode 100644 index 00000000..c1d5e38d --- /dev/null +++ b/script/update-from-template @@ -0,0 +1,17 @@ +#!/bin/bash + +git remote rm template +git remote add template https://github.com/mysociety/template_data_repo +git fetch template +git merge template/main + +cd src/data_common +git fetch origin +git pull origin main +git checkout main + +cd ../.. +cd docs/theme +git fetch origin +git pull origin main +git checkout main diff --git a/src/publicwhip_data/__init__.py b/src/publicwhip_data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/publicwhip_data/__main__.py b/src/publicwhip_data/__main__.py new file mode 100644 index 00000000..b882d504 --- /dev/null +++ b/src/publicwhip_data/__main__.py @@ -0,0 +1,20 @@ +import rich_click as click +from .process import fetch_and_move_pw + + +@click.group() +def cli(): + pass + + +def main(): + cli() + + +@cli.command() +def download(): + fetch_and_move_pw() + + +if __name__ == "__main__": + main() diff --git a/src/publicwhip_data/mysql2sqlite b/src/publicwhip_data/mysql2sqlite new file mode 100644 index 00000000..8b80fbf2 --- /dev/null +++ b/src/publicwhip_data/mysql2sqlite @@ -0,0 +1,289 @@ +#!/usr/bin/awk -f + +# Authors: @esperlu, @artemyk, @gkuenning, @dumblob + +# FIXME detect empty input file and issue a warning + +function printerr( s ){ print s | "cat >&2" } + +BEGIN { + if( ARGC != 2 ){ + printerr( \ + "USAGE:\n"\ + " mysql2sqlite dump_mysql.sql > dump_sqlite3.sql\n" \ + " OR\n" \ + " mysql2sqlite dump_mysql.sql | sqlite3 sqlite.db\n" \ + "\n" \ + "NOTES:\n" \ + " Dash in filename is not supported, because dash (-) means stdin." ) + no_END = 1 + exit 1 + } + + # Find INT_MAX supported by both this AWK (usually an ISO C signed int) + # and SQlite. + # On non-8bit-based architectures, the additional bits are safely ignored. + + # 8bit (lower precision should not exist) + s="127" + # "63" + 0 avoids potential parser misbehavior + if( (s + 0) "" == s ){ INT_MAX_HALF = "63" + 0 } + # 16bit + s="32767" + if( (s + 0) "" == s ){ INT_MAX_HALF = "16383" + 0 } + # 32bit + s="2147483647" + if( (s + 0) "" == s ){ INT_MAX_HALF = "1073741823" + 0 } + # 64bit (as INTEGER in SQlite3) + s="9223372036854775807" + if( (s + 0) "" == s ){ INT_MAX_HALF = "4611686018427387904" + 0 } +# # 128bit +# s="170141183460469231731687303715884105728" +# if( (s + 0) "" == s ){ INT_MAX_HALF = "85070591730234615865843651857942052864" + 0 } +# # 256bit +# s="57896044618658097711785492504343953926634992332820282019728792003956564819968" +# if( (s + 0) "" == s ){ INT_MAX_HALF = "28948022309329048855892746252171976963317496166410141009864396001978282409984" + 0 } +# # 512bit +# s="6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048" +# if( (s + 0) "" == s ){ INT_MAX_HALF = "3351951982485649274893506249551461531869841455148098344430890360930441007518386744200468574541725856922507964546621512713438470702986642486608412251521024" + 0 } +# # 1024bit +# s="89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119973622969239858152417678164812112068608" +# if( (s + 0) "" == s ){ INT_MAX_HALF = "44942328371557897693232629769725618340449424473557664318357520289433168951375240783177119330601884005280028469967848339414697442203604155623211857659868531094441973356216371319075554900311523529863270738021251442209537670585615720368478277635206809290837627671146574559986811484619929076208839082406056034304" + 0 } +# # higher precision probably not needed + + FS=",$" + print "PRAGMA synchronous = OFF;" + print "PRAGMA journal_mode = MEMORY;" + print "BEGIN TRANSACTION;" +} + +# historically 3 spaces separate non-argument local variables +function bit_to_int( str_bit, powtwo, i, res, bit, overflow ){ + powtwo = 1 + overflow = 0 + # 011101 = 1*2^0 + 0*2^1 + 1*2^2 ... + for( i = length( str_bit ); i > 0; --i ){ + bit = substr( str_bit, i, 1 ) + if( overflow || ( bit == 1 && res > INT_MAX_HALF ) ){ + printerr( \ + NR ": WARN Bit field overflow, number truncated (LSBs saved, MSBs ignored)." ) + break + } + res = res + bit * powtwo + # no warning here as it might be the last iteration + if( powtwo > INT_MAX_HALF ){ overflow = 1; continue } + powtwo = powtwo * 2 + } + return res +} + +# CREATE TRIGGER statements have funny commenting. Remember we are in trigger. +/^\/\*.*(CREATE.*TRIGGER|create.*trigger)/ { + gsub( /^.*(TRIGGER|trigger)/, "CREATE TRIGGER" ) + print + inTrigger = 1 + next +} +# The end of CREATE TRIGGER has a stray comment terminator +/(END|end) \*\/;;/ { gsub( /\*\//, "" ); print; inTrigger = 0; next } +# The rest of triggers just get passed through +inTrigger != 0 { print; next } + +# CREATE VIEW looks like a TABLE in comments +/^\/\*.*(CREATE.*TABLE|create.*table)/ { + inView = 1 + next +} +# end of CREATE VIEW +/^(\).*(ENGINE|engine).*\*\/;)/ { + inView = 0 + next +} +# content of CREATE VIEW +inView != 0 { next } + +# skip comments +/^\/\*/ { next } + +# skip PARTITION statements +/^ *[(]?(PARTITION|partition) +[^ ]+/ { next } + +# print all INSERT lines +( /^ *\(/ && /\) *[,;] *$/ ) || /^(INSERT|insert|REPLACE|replace)/ { + prev = "" + + # first replace \\ by \_ that mysqldump never generates to deal with + # sequnces like \\n that should be translated into \n, not \. + # After we convert all escapes we replace \_ by backslashes. + gsub( /\\\\/, "\\_" ) + + # single quotes are escaped by another single quote + gsub( /\\'/, "''" ) + gsub( /\\n/, "\n" ) + gsub( /\\r/, "\r" ) + gsub( /\\"/, "\"" ) + gsub( /\\\032/, "\032" ) # substitute char + + gsub( /\\_/, "\\" ) + + # sqlite3 is limited to 16 significant digits of precision + while( match( $0, /0x[0-9a-fA-F]{17}/ ) ){ + hexIssue = 1 + sub( /0x[0-9a-fA-F]+/, substr( $0, RSTART, RLENGTH-1 ), $0 ) + } + if( hexIssue ){ + printerr( \ + NR ": WARN Hex number trimmed (length longer than 16 chars)." ) + hexIssue = 0 + } + print + next +} + +# CREATE DATABASE is not supported +/^(CREATE DATABASE|create database)/ { next } + +# print the CREATE line as is and capture the table name +/^(CREATE|create)/ { + if( $0 ~ /IF NOT EXISTS|if not exists/ || $0 ~ /TEMPORARY|temporary/ ){ + caseIssue = 1 + printerr( \ + NR ": WARN Potential case sensitivity issues with table/column naming\n" \ + " (see INFO at the end)." ) + } + if( match( $0, /`[^`]+/ ) ){ + tableName = substr( $0, RSTART+1, RLENGTH-1 ) + } + aInc = 0 + prev = "" + firstInTable = 1 + print + next +} + +# Replace `FULLTEXT KEY` (probably other `XXXXX KEY`) +/^ (FULLTEXT KEY|fulltext key)/ { gsub( /[A-Za-z ]+(KEY|key)/, " KEY" ) } + +# Get rid of field lengths in KEY lines +/ (PRIMARY |primary )?(KEY|key)/ { gsub( /\([0-9]+\)/, "" ) } + +aInc == 1 && /PRIMARY KEY|primary key/ { next } + +# Replace COLLATE xxx_xxxx_xx statements with COLLATE BINARY +/ (COLLATE|collate) [a-z0-9_]*/ { gsub( /(COLLATE|collate) [a-z0-9_]*/, "COLLATE BINARY" ) } + +# Print all fields definition lines except the `KEY` lines. +/^ / && !/^( (KEY|key)|\);)/ { + if( match( $0, /[^"`]AUTO_INCREMENT|auto_increment[^"`]/) ){ + aInc = 1 + gsub( /AUTO_INCREMENT|auto_increment/, "PRIMARY KEY AUTOINCREMENT" ) + } + gsub( /(UNIQUE KEY|unique key) (`.*`|".*") /, "UNIQUE " ) + gsub( /(CHARACTER SET|character set) [^ ]+[ ,]/, "" ) + # FIXME + # CREATE TRIGGER [UpdateLastTime] + # AFTER UPDATE + # ON Package + # FOR EACH ROW + # BEGIN + # UPDATE Package SET LastUpdate = CURRENT_TIMESTAMP WHERE ActionId = old.ActionId; + # END + gsub( /(ON|on) (UPDATE|update) (CURRENT_TIMESTAMP|current_timestamp)(\(\))?/, "" ) + gsub( /(DEFAULT|default) (CURRENT_TIMESTAMP|current_timestamp)(\(\))?/, "DEFAULT current_timestamp") + gsub( /(COLLATE|collate) [^ ]+ /, "" ) + gsub( /(ENUM|enum)[^)]+\)/, "text " ) + gsub( /(SET|set)\([^)]+\)/, "text " ) + gsub( /UNSIGNED|unsigned/, "" ) + gsub( /_utf8mb3/, "" ) + gsub( /` [^ ]*(INT|int|BIT|bit)[^ ]*/, "` integer" ) + gsub( /" [^ ]*(INT|int|BIT|bit)[^ ]*/, "\" integer" ) + ere_bit_field = "[bB]'[10]+'" + if( match($0, ere_bit_field) ){ + sub( ere_bit_field, bit_to_int( substr( $0, RSTART +2, RLENGTH -2 -1 ) ) ) + } + + # remove USING BTREE and other suffixes for USING, for example: "UNIQUE KEY + # `hostname_domain` (`hostname`,`domain`) USING BTREE," + gsub( / USING [^, ]+/, "" ) + + # field comments are not supported + gsub( / (COMMENT|comment).+$/, "" ) + # Get commas off end of line + gsub( /,.?$/, "" ) + if( prev ){ + if( firstInTable ){ + print prev + firstInTable = 0 + } + else { + print "," prev + } + } + else { + # FIXME check if this is correct in all cases + if( match( $1, + /(CONSTRAINT|constraint) ["].*["] (FOREIGN KEY|foreign key)/ ) ){ + print "," + } + } + prev = $1 +} + +/ ENGINE| engine/ { + if( prev ){ + if( firstInTable ){ + print prev + firstInTable = 0 + } + else { + print "," prev + } + } + prev="" + print ");" + next +} +# `KEY` lines are extracted from the `CREATE` block and stored in array for later print +# in a separate `CREATE KEY` command. The index name is prefixed by the table name to +# avoid a sqlite error for duplicate index name. +/^( (KEY|key)|\);)/ { + if( prev ){ + if( firstInTable ){ + print prev + firstInTable = 0 + } + else { + print "," prev + } + } + prev = "" + if( $0 == ");" ){ + print + } + else { + if( match( $0, /`[^`]+/ ) ){ + indexName = substr( $0, RSTART+1, RLENGTH-1 ) + } + if( match( $0, /\([^()]+/ ) ){ + indexKey = substr( $0, RSTART+1, RLENGTH-1 ) + } + # idx_ prefix to avoid name clashes (they really happen!) + key[tableName] = key[tableName] "CREATE INDEX \"idx_" \ + tableName "_" indexName "\" ON \"" tableName "\" (" indexKey ");\n" + } +} + +END { + if( no_END ){ exit 1} + # print all KEY creation lines. + for( table in key ){ printf key[table] } + + print "END TRANSACTION;" + + if( caseIssue ){ + printerr( \ + "INFO Pure sqlite identifiers are case insensitive (even if quoted\n" \ + " or if ASCII) and doesnt cross-check TABLE and TEMPORARY TABLE\n" \ + " identifiers. Thus expect errors like \"table T has no column named F\".") + } +} \ No newline at end of file diff --git a/src/publicwhip_data/process.py b/src/publicwhip_data/process.py new file mode 100644 index 00000000..a787504d --- /dev/null +++ b/src/publicwhip_data/process.py @@ -0,0 +1,143 @@ +""" +Download public whips database dumps, convert it to parquet files +""" + + +import bz2 +import shutil +import sqlite3 +import subprocess +from pathlib import Path + +import pandas as pd +import requests +import rich +from tqdm import tqdm + + +def download_url_to_file(url: str, filepath: Path) -> Path: + """ + Given a url + """ + with requests.get(url, stream=True, timeout=60) as r: # type: ignore + r.raise_for_status() + rich.print(f"Downloading [green]{url}[/green] to [green]{filepath}[/green]") + with open(filepath, "wb") as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) + return filepath + + +def unzip_zip_file(zip_file: Path, output_dir: Path) -> Path: + """ + Given a bz2 file, unzip it to the output_dir + """ + rich.print(f"Unzipping [green]{zip_file}[/green] to [green]{output_dir}[/green]") + filepath = output_dir / zip_file.stem + with bz2.BZ2File(zip_file) as fr, open(filepath, "wb") as fw: + shutil.copyfileobj(fr, fw) + return filepath + + +def convert_to_sqlite(input_file: Path, output_file: Path) -> Path: + """ + run the mysql2sqlite shell utility on the input file + """ + rich.print("Converting from [green]mysql[/green] to [green]sqlite[/green]") + converted_pp = subprocess.run( + ["src/publicwhip_data/mysql2sqlite", str(input_file)], + check=True, + stdout=subprocess.PIPE, + ) + + rich.print( + f"Converting [green]{input_file}[/green] to [green]{output_file}[/green]" + ) + conn = sqlite3.connect(output_file) + conn.executescript(converted_pp.stdout.decode("ISO-8859-1")) + conn.commit() + conn.close() + + return output_file + + +def convert_bz2_mysql_to_paraquet(url: str, output_dir: Path) -> list[Path]: + """ + Fetch the bz2 file, unzip it, convert it to sqlite, convert it to parquet + """ + output_dir.mkdir(exist_ok=True) + output_file = output_dir / url.split("/")[-1] + download_url_to_file(url, output_file) + result = unzip_zip_file(output_file, output_dir) + db_filename = output_dir / ( + Path(url.split("/")[-1]).stem.removesuffix(".sql") + ".db" + ) + if db_filename.exists(): + db_filename.unlink() + convert_to_sqlite(result, db_filename) + files = convert_sqlitedb_to_paraquet(db_filename) + output_file.unlink() + result.unlink() + db_filename.unlink() + return files + + +def convert_sqlitedb_to_paraquet(input_path: Path) -> list[Path]: + """ + Convert the sqlite db to a parquet file + """ + rich.print( + f"Converting [green]{input_path}[/green] to [green]parquet files[/green]" + ) + conn = sqlite3.connect(input_path) + # for all tables in the db + outputs: list[Path] = [] + for table in conn.execute("SELECT name FROM sqlite_master WHERE type='table';"): + if table[0] == "sqlite_sequence": + continue + # 7.3+0.3+0.2+5.9 + # get the size of a dataframe, chunk it into 1000 rows chunks and reassemble + count = conn.execute(f"SELECT COUNT(*) FROM {table[0]};").fetchone()[0] + rich.print(f"Converting {table[0]} with {count} rows") + dfs: list[pd.DataFrame] = [] + for i in tqdm(range(0, count, 1000)): + df = pd.read_sql_query( + f"SELECT * from {table[0]} limit 1000 offset {i}", conn + ) + dfs.append(df) + df = pd.concat(dfs) # type: ignore + output_filename: Path = input_path.parent / f"{table[0]}.parquet" + df.to_parquet(output_filename) + outputs.append(output_filename) + conn.close() + return outputs + + +def fetch_public_whip(): + """ + Convert public whip files into parquet files + """ + + urls = [ + "https://www.publicwhip.org.uk/data/pw_static_tables.sql.bz2", + "https://www.publicwhip.org.uk/data/pw_dynamic_tables.sql.bz2", + ] + for url in urls: + convert_bz2_mysql_to_paraquet(url, Path("data", "raw")) + + +def move_files(): + """ + Move the files to the data directory + """ + for file in Path("data", "raw").glob("*.parquet"): + shutil.copyfile(file, Path("data", "packages", "public_whip_data", file.name)) + + +def fetch_and_move_pw(): + fetch_public_whip() + move_files() + + +if __name__ == "__main__": + fetch_and_move_pw() diff --git a/tests/test_publicwhip_data.py b/tests/test_publicwhip_data.py new file mode 100644 index 00000000..f68d7200 --- /dev/null +++ b/tests/test_publicwhip_data.py @@ -0,0 +1,7 @@ +import publicwhip_data + +import pytest + + +def test_true_is_true(): + assert True is True