diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f22ff37
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,16 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+max_line_length = 120
+
+[{*.py,*.pyw}]
+max_line_length = 88
+
+[{*.yaml,*.yml}]
+indent_size = 2
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..8f352ad
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,34 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: 'bug'
+assignees: 'OliverSieweke'
+
+---
+[comment]: # (Specify the version used below.)
+**version:** v0.1.0
+
+### Description
+[comment]: # (A clear and concise description of what the bug is.)
+
+### Expected behavior
+[comment]: # (A clear and concise description of what you expected to happen.)
+
+### Configurations
+[comment]: # (Paste the workflow YAML file here.)
+
+```yaml
+```
+
+### Stack Trace
+[comment]: # (Paste your actions error stack trace here if applicable.)
+
+```txt
+```
+
+### Additional context
+[comment]: # (Any additonal information that may be relevant.)
+
+
+[comment]: # (Awesome! Thanks!)
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..0a073cd
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,13 @@
+# Description
+[comment]: # (Please include a summary of the change and which issue is fixed if applicable.)
+
+Fixes # (issue)
+
+## Type of change
+
+[comment]: # (Please delete options that are not relevant.)
+
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] This change requires a documentation update
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..92e8f09
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+# Documentation Build:
+/docs/build
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..73f69e0
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/dictionaries/os.xml b/.idea/dictionaries/os.xml
new file mode 100644
index 0000000..625b424
--- /dev/null
+++ b/.idea/dictionaries/os.xml
@@ -0,0 +1,21 @@
+
+
+
+ autodoc
+ docformatter
+ dotenv
+ genindex
+ isort
+ kaggle
+ matplotlib
+ modindex
+ noninfringement
+ oliversieweke
+ seaborn
+ sieweke
+ stefanzweifel
+ ymax
+ ymin
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..33d501a
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..083b565
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kaggle-graph.iml b/.idea/kaggle-graph.iml
new file mode 100644
index 0000000..527c7bc
--- /dev/null
+++ b/.idea/kaggle-graph.iml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..2efefd2
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..c49ca88
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/docs.xml b/.idea/runConfigurations/docs.xml
new file mode 100644
index 0000000..56f013c
--- /dev/null
+++ b/.idea/runConfigurations/docs.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml
new file mode 100644
index 0000000..e85a6ef
--- /dev/null
+++ b/.idea/watcherTasks.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..e12428f
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,17 @@
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+version: 2
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+ configuration: docs/source/conf.py
+
+# Python version and requirements to build the docs
+python:
+ version: 3.7
+ install:
+ - requirements: requirements-docs.txt
+
+# Build docs in all formats:
+formats: all
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..beb01fb
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+Contributions are very welcome!
+
+This is a small and manageable project, so feel free to contribute in whatever way suits you best. GitHub issues, pull requests, emails are all great!
+
+Make sure you check out the [Developer Guide](https://kaggle-graph.readthedocs.io/en/latest/?badge=latest) 🐒
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..7ea2a10
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,5 @@
+FROM python:3.7-slim
+
+ADD . /
+RUN python -m pip install -e .
+ENTRYPOINT python -m kaggle_graph
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..c81d691
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Oliver Sieweke
+
+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/README.rst b/README.rst
new file mode 100644
index 0000000..72e1b41
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,132 @@
+Kaggle Graph
+====================
+
+|license| |version| |docs|
+
+.. |license| image:: https://img.shields.io/github/license/OliverSieweke/kaggle-graph
+ :target: https://choosealicense.com/licenses/mit
+ :alt: License: MIT
+.. |version| image:: https://img.shields.io/github/v/tag/OliverSieweke/kaggle-graph
+ :target: https://github.com/OliverSieweke/kaggle-graph/releases
+ :alt: Versions
+.. |docs| image:: https://readthedocs.org/projects/kaggle-graph/badge/?version=latest
+ :target: https://kaggle-graph.readthedocs.io/en/latest/?badge=latest
+ :alt: Documentation Status
+
+GitHub action to automatically generate graphs of your submissions to Kaggle
+competitions and display them in your Github repository’s ``README`` to track
+your progress.
+
+.. image:: ./example-graph.png
+ :alt: Example Graph
+
+See it in action `here`_.
+
+.. getting_started
+
+Getting Started
+---------------
+
+1. Add the :code:`KAGGLE_KEY` secret to your GitHub repository under
+ ``Settings > Secrets > Add a new secret``.
+
+2. Define a GitHub workflow_ by adding a ``.github/workflow/kaggle-graph.yaml``
+ to your repository using the template below:
+
+.. code:: yaml
+
+ name: Kaggle Graph Generation
+
+ on:
+ push:
+ branches: master
+ schedule:
+ - cron: '00 00 * * *'
+
+ jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Git Checkout
+ uses: actions/checkout@v2
+ - name: Kaggle Graph Generation
+ uses: OliverSieweke/kaggle-graph@v0.1.0
+ with:
+ KAGGLE_KEY: ${{ secrets.KAGGLE_KEY }}
+ KAGGLE_USERNAME: oliversieweke
+ KAGGLE_COMPETITION_ID: titanic
+ - name: Git Auto Commit
+ uses: stefanzweifel/git-auto-commit-action@v4.1.6
+ with:
+ commit_message: Automatic Kaggle graph generation
+
+3. Add the graph to your ``README``:
+
+ - Markdown:
+ .. code:: md
+
+ [!Kaggle Submissions Graph](./kaggle-submissions-graph.png)
+ - reStructuredText:
+ .. code:: rst
+
+ .. image:: ./kaggle-submissions-graph.png
+ :alt: Kaggle Submissions Graph
+
+.. before_notes
+
+**NB 1:**
+ - The :code:`KAGGLE_KEY` key can be generated on Kaggle_ under ``My Profile
+ > Edit Profile > API > Create New Api Token``.
+ - The :code:`KAGGLE_USERNAME` can be found on Kaggle_ under ``My Profile > Edit
+ Profile``.
+ - The :code:`KAGGLE_COMPETITION_ID` can be read from the Kaggle_ competition's URL.
+
+**NB 2:**
+ **Kaggle Graph** is designed to be used in conjunction with the following
+ GitHub actions:
+
+ - checkout_
+ - git-auto-commit_
+
+.. after_notes
+
+Configurations
+--------------
+
+The action can be further configured over the input keys below:
+
++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+
+| Name | Description | Required | Default | Type |
++===============================+==========================+===========+====================================+=========================================================+
+| :code:`KAGGLE_USERNAME` | Kaggle username. | ``true`` | ``-`` | :code:`str` |
++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+
+| :code:`KAGGLE_KEY` | Kaggle key. | ``true`` | ``-`` | :code:`str` |
++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+
+| :code:`KAGGLE_COMPETITION_ID` | Kaggle competition ID. | ``true`` | ``-`` | :code:`str` |
++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+
+| :code:`GRAPH_NAME` | Output graph file name. | ``false`` | :code:`"kaggle-submissions-graph"` | :code:`str` |
++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+
+| :code:`Y_MIN` | Y-axis minimum boundary. | ``false`` | ``-`` | :code:`float` |
++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+
+| :code:`Y_MAX` | Y-axis maximum boundary. | ``false`` | ``-`` | :code:`float` |
++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+
+| :code:`SCORE` | Score type. | ``false`` | :code:`positive` | :code:`str` (:code:`positive` or :code:`negative`) |
++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+
+| :code:`OBJECTIVE` | Score objective. | ``false`` | ``-`` | :code:`float` |
++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+
+
+.. _here: https://github.com/OliverSieweke/bike-sharing-demand
+.. _.github/workflow/kaggle-graph.yaml: ./workflow.template.yaml
+.. _Kaggle: https://www.kaggle.com/
+.. _workflow: https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
+.. _checkout: https://github.com/actions/checkout
+.. _git-auto-commit: https://github.com/stefanzweifel/git-auto-commit-action
+
+.. before_references
+
+References
+----------
+
+- Documentation_
+
+.. _Documentation: https://kaggle-graph.readthedocs.io/en/latest/?badge=latest
diff --git a/action.yaml b/action.yaml
new file mode 100644
index 0000000..f6b768b
--- /dev/null
+++ b/action.yaml
@@ -0,0 +1,38 @@
+name: "Kaggle Graph Generation"
+author: "Oliver Sieweke"
+description: "Generate a graph of your submissions to a Kaggle competition."
+branding:
+ icon: "trending-up"
+ color: "blue"
+
+inputs:
+ KAGGLE_USERNAME:
+ required: true
+ description: Kaggle username, to be found on Kaggle under My Profile > Edit Profile.
+ KAGGLE_KEY:
+ required: true
+ description: Kaggle key ID, to be read from the competition's URL.
+ KAGGLE_COMPETITION_ID:
+ required: true
+ description: Kaggle Competition ID, to be read from the competition's URL.
+ GRAPH_NAME:
+ required: false
+ description: Output graph file name.
+ Y_MIN:
+ required: false
+ description: Y-axis mimumium boundary.
+ Y_MAX:
+ required: false
+ description: Y-axis maximum boundary.
+ SCORE:
+ required: false
+ description: Whether the score is to be maximized (positive) or minimized (negative).
+ default: "positive"
+ OBJECTIVE:
+ required: false
+ description: Score objective to be shown on the graph.
+
+runs:
+ using: "docker"
+ image: "Dockerfile"
+
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d0c3cbf
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..9534b01
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/source/_static/example-graph.png b/docs/source/_static/example-graph.png
new file mode 100644
index 0000000..bad4ccd
Binary files /dev/null and b/docs/source/_static/example-graph.png differ
diff --git a/docs/source/_static/graph-logo.svg b/docs/source/_static/graph-logo.svg
new file mode 100644
index 0000000..6e4922f
--- /dev/null
+++ b/docs/source/_static/graph-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/source/action_inputs.rst b/docs/source/action_inputs.rst
new file mode 100644
index 0000000..c4d38dd
--- /dev/null
+++ b/docs/source/action_inputs.rst
@@ -0,0 +1,2 @@
+.. automodule:: kaggle_graph.action_inputs
+ :members:
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..5d47267
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,65 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+
+# Standard Library ---------------------------------------------------------------------
+import os
+import sys
+
+
+sys.path.insert(0, os.path.abspath("../../"))
+
+# -- Project information -----------------------------------------------------
+
+project = "Kaggle Graph"
+copyright = "2020, Oliver Sieweke"
+author = "Oliver Sieweke"
+
+# The full version, including alpha/beta/rc tags
+release = "0.1.0"
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ "sphinx.ext.autodoc",
+ "sphinx.ext.napoleon",
+ "sphinx_rtd_theme",
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+
+html_theme = "sphinx_rtd_theme"
+html_logo = "_static/graph-logo.svg"
+html_favicon = "_static/graph-logo.svg"
+html_theme_options = {
+ "style_nav_header_background": "#0283B3",
+}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
diff --git a/docs/source/developer_guide.rst b/docs/source/developer_guide.rst
new file mode 100644
index 0000000..3062ae3
--- /dev/null
+++ b/docs/source/developer_guide.rst
@@ -0,0 +1,17 @@
+Developer Guide
+===============
+
+.. toctree::
+ :maxdepth: 2
+
+ kaggle_graph
+ action_inputs
+ submissions
+ plot
+
+
+Indices
+-------
+
+* :ref:`genindex`
+* :ref:`modindex`
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..8a5e897
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,15 @@
+Kaggle Graph
+============
+
+GitHub action to automatically generate graphs of your submissions to Kaggle competitions and display them in your Github repository's ``README`` to track your progress.
+
+.. image:: ./_static/example-graph.png
+ :alt: Example Graph
+ :align: center
+ :scale: 70%
+
+.. toctree::
+ :maxdepth: 2
+
+ user_guide
+ developer_guide
diff --git a/docs/source/kaggle_graph.rst b/docs/source/kaggle_graph.rst
new file mode 100644
index 0000000..5d32a83
--- /dev/null
+++ b/docs/source/kaggle_graph.rst
@@ -0,0 +1,2 @@
+.. automodule:: kaggle_graph
+ :members:
diff --git a/docs/source/plot.rst b/docs/source/plot.rst
new file mode 100644
index 0000000..17e8f7f
--- /dev/null
+++ b/docs/source/plot.rst
@@ -0,0 +1,2 @@
+.. automodule:: kaggle_graph.plot
+ :members:
diff --git a/docs/source/submissions.rst b/docs/source/submissions.rst
new file mode 100644
index 0000000..b0fc317
--- /dev/null
+++ b/docs/source/submissions.rst
@@ -0,0 +1,2 @@
+.. automodule:: kaggle_graph.submissions
+ :members: fetch, parse
diff --git a/docs/source/user_guide.rst b/docs/source/user_guide.rst
new file mode 100644
index 0000000..0831a7f
--- /dev/null
+++ b/docs/source/user_guide.rst
@@ -0,0 +1,47 @@
+User Guide
+==========
+
+.. include:: ../../README.rst
+ :start-after: .. getting_started
+ :end-before: .. before_notes
+
+.. note::
+ - The :code:`KAGGLE_KEY` key can be generated on Kaggle_ under ``My Profile
+ > Edit Profile > API > Create New Api Token``.
+ - The :code:`KAGGLE_USERNAME` can be found on Kaggle_ under ``My Profile > Edit
+ Profile``.
+ - The :code:`KAGGLE_COMPETITION_ID` can be read from the competition's URL.
+
+.. note::
+ **Kaggle Graph** is designed to be used in conjunction with the following
+ GitHub actions:
+
+ - checkout_
+ - git-auto-commit_
+
+.. include:: ../../README.rst
+ :start-after: .. after_notes
+ :end-before: .. before_references
+
+Troubleshooting
+---------------
+
+- My graph does not update:
+ GitHub seems to be caching images. Although the graph updates, the display
+ may take a couple of minutes to reflect the change.
+
+References
+----------
+
+ - `Creating and storing encrypted secrets`_
+ - `Workflow syntax for GitHub Actions`_
+ - `checkout action`_
+ - `git-auto-commit action`_
+
+.. _Kaggle: https://www.kaggle.com/
+.. _`Creating and storing encrypted secrets`: https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets
+.. _`Workflow syntax for GitHub Actions`: https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
+.. _checkout: https://github.com/actions/checkout
+.. _git-auto-commit: https://github.com/stefanzweifel/git-auto-commit-action
+.. _checkout action: https://github.com/actions/checkout
+.. _git-auto-commit action: https://github.com/stefanzweifel/git-auto-commit-action
diff --git a/example-graph.png b/example-graph.png
new file mode 100644
index 0000000..bad4ccd
Binary files /dev/null and b/example-graph.png differ
diff --git a/kaggle_graph/__init__.py b/kaggle_graph/__init__.py
new file mode 100644
index 0000000..dd0089b
--- /dev/null
+++ b/kaggle_graph/__init__.py
@@ -0,0 +1,11 @@
+"""
+Kaggle Graph
+============
+
+This is the top level module, which includes a :code:`__main__` script
+to generate the Kaggle graph by loading the required environment
+variables (including the Kaggle credentials) using
+:py:mod:`kaggle_graph.action_inputs` and then composing the methods
+exposed in :py:mod:`kaggle_graph.plot` and
+:py:mod:`kaggle_graph.submissions`.
+"""
diff --git a/kaggle_graph/__main__.py b/kaggle_graph/__main__.py
new file mode 100644
index 0000000..023281e
--- /dev/null
+++ b/kaggle_graph/__main__.py
@@ -0,0 +1,19 @@
+# Third Party --------------------------------------------------------------------------
+from kaggle_graph.action_inputs import get_action_inputs
+from kaggle_graph.plot import kaggle_submission_scores
+from kaggle_graph.submissions import fetch, parse
+
+
+# Load action inputs:
+action_inputs = get_action_inputs()
+
+
+# Generate and save Kaggle graph:
+kaggle_submission_scores(
+ parse(fetch(action_inputs["KAGGLE_COMPETITION_ID"])),
+ graph_name=action_inputs["GRAPH_NAME"],
+ ymin=action_inputs["Y_MIN"],
+ ymax=action_inputs["Y_MAX"],
+ objective=action_inputs["OBJECTIVE"],
+ score=action_inputs["SCORE"],
+)
diff --git a/kaggle_graph/action_inputs/__init__.py b/kaggle_graph/action_inputs/__init__.py
new file mode 100644
index 0000000..05570df
--- /dev/null
+++ b/kaggle_graph/action_inputs/__init__.py
@@ -0,0 +1,63 @@
+"""
+Action Inputs
+=============
+
+This module contains utility methods for retrieving action inputs from
+the environment.
+"""
+
+# Standard Library ---------------------------------------------------------------------
+import os
+
+# Third Party --------------------------------------------------------------------------
+from dotenv import load_dotenv
+
+
+input_schema = {
+ "KAGGLE_USERNAME": str,
+ "KAGGLE_KEY": str,
+ "KAGGLE_COMPETITION_ID": str,
+ "GRAPH_NAME": str,
+ "Y_MIN": float,
+ "Y_MAX": float,
+ "OBJECTIVE": float,
+ "SCORE": str,
+}
+
+
+def get_action_inputs() -> dict:
+ """Return a dictionary containing all action inputs.
+
+ .. note::
+ This method further loads the Kaggle credentials into
+ the environment.
+
+ Returns
+ -------
+ :code:`dict`
+ Dictionary containing all action inputs.
+
+ Example
+ -------
+ >>> get_action_inputs()
+ {'KAGGLE_USERNAME': 'oliversieweke',
+ 'KAGGLE_KEY': '***',
+ 'KAGGLE_COMPETITION_ID': 'titanic',
+ 'GRAPH_NAME': None,
+ 'Y_MIN': 0,
+ 'Y_MAX': 1,
+ 'OBJECTIVE': 0.8,
+ 'SCORE': 'positive'}
+ """
+ load_dotenv()
+
+ action_inputs = {
+ key: (cast(os.getenv(f"INPUT_{key}")) if os.getenv(f"INPUT_{key}") else None)
+ for (key, cast) in input_schema.items()
+ }
+
+ # Loading Kaggle credentials into the environment:
+ os.environ["KAGGLE_USERNAME"] = action_inputs["KAGGLE_USERNAME"]
+ os.environ["KAGGLE_KEY"] = action_inputs["KAGGLE_KEY"]
+
+ return action_inputs
diff --git a/kaggle_graph/plot/__init__.py b/kaggle_graph/plot/__init__.py
new file mode 100644
index 0000000..0bbdddb
--- /dev/null
+++ b/kaggle_graph/plot/__init__.py
@@ -0,0 +1,121 @@
+"""
+Plot
+====
+
+This module contains methods for generating and saving graphs of Kaggle
+submission scores.
+"""
+
+# Standard Library ---------------------------------------------------------------------
+from typing import Optional
+
+# Third Party --------------------------------------------------------------------------
+import matplotlib.pyplot as plt
+import seaborn as sns
+from matplotlib.dates import DateFormatter, DayLocator
+
+# Data Science
+import pandas as pd
+
+
+def kaggle_submission_scores(
+ submissions_df: pd.DataFrame,
+ graph_name: Optional[str] = "kaggle-submissions-graph",
+ ymin: Optional[float] = None,
+ ymax: Optional[float] = None,
+ objective: Optional[float] = None,
+ score: Optional[str] = "positive",
+) -> None:
+ """Save plot of Kaggle submission scores.
+
+ Parameters
+ ----------
+ submissions_df
+ Submissions data frame
+ graph_name: optional
+ Name of the graph image file to be saved (defaults to
+ :code:`"kaggle-submissions-graph"`).
+ ymin: optional
+ Y-axis mimumium boundary (set automatically if not provided).
+ ymax: optional
+ Y-axis maximum boundary (set automatically if not provided).
+ objective: optional
+ Score objective, to be displayed on the graph (defaults
+ to :code:`None`).
+ score: optional
+ Specifies whether the objective is to :code:`"maximize"` or
+ :code:`"minimize"` the score (defaults to :code:`"maximize"`)
+
+ Returns
+ -------
+ None
+ While nothing is returned, a plot of the Kaggle submission
+ scores is saved, under :code:`f"./{graph_name}.png"`.
+
+ Note
+ ----
+ This function is intended to be used in conjunction with
+ :meth:`kaggle_graph.submissions.parse`.
+
+ Example
+ -------
+ >>> from kaggle_graph.submissions import fetch, parse
+ >>> kaggle_submission_scores(parse(fetch("titanic")))
+ """
+ if graph_name is None:
+ graph_name = "kaggle-submissions-graph"
+
+ # Clean and Sort Data
+ submissions_df.dropna(subset=["publicScore"], inplace=True)
+ submissions_df.sort_values(by=["date"], inplace=True)
+
+ # Graph
+ sns.set()
+ g = sns.lineplot(x="date", y="publicScore", data=submissions_df, marker="o")
+
+ # Title and Labels
+ g.set(title="Kaggle", xlabel="Date", ylabel="Public Score")
+ g.xaxis.set_major_locator(DayLocator(interval=1))
+ g.xaxis.set_major_formatter(DateFormatter("%d %b %y"))
+
+ # Boundaries
+ if ymin is not None:
+ plt.ylim(ymin=ymin)
+ if ymax is not None:
+ plt.ylim(ymax=ymax)
+
+ # Color Areas
+ if score is None:
+ score = "positive"
+ plt.fill_between(
+ submissions_df["date"], submissions_df["publicScore"], color="b", alpha=0.3
+ )
+ if objective:
+ g.axhline(objective, ls="-", color="r", alpha=0.3)
+ plt.fill_between(
+ submissions_df["date"],
+ submissions_df["publicScore"],
+ objective,
+ where=submissions_df["publicScore"] < objective,
+ interpolate=True,
+ color="g" if score == "negative" else "r",
+ alpha=0.3,
+ )
+ plt.fill_between(
+ submissions_df["date"],
+ objective,
+ submissions_df["publicScore"],
+ where=objective <= submissions_df["publicScore"],
+ interpolate=True,
+ color="r" if score == "negative" else "g",
+ alpha=0.3,
+ )
+
+ # Save
+ graph_file_name = f"{graph_name}.png"
+
+ plt.savefig(
+ graph_file_name,
+ # Prevent labels to be cut off in saved image:
+ bbox_inches="tight",
+ )
diff --git a/kaggle_graph/submissions/__init__.py b/kaggle_graph/submissions/__init__.py
new file mode 100644
index 0000000..faa1c97
--- /dev/null
+++ b/kaggle_graph/submissions/__init__.py
@@ -0,0 +1,141 @@
+"""
+Submissions
+===========
+
+This module contains methods for fetching and parsing submissions to
+`Kaggle Competitions`_.
+
+.. _Kaggle Competitions: https://www.kaggle.com/competitions
+"""
+
+# Standard Library ---------------------------------------------------------------------
+import subprocess
+from io import StringIO
+
+# Data Science
+import pandas as pd
+
+
+class KaggleError(Exception):
+ """Raised when a Kaggle API call exits with a non-zero error code."""
+
+ ...
+
+
+def fetch(competition_id: str) -> str:
+ """Fetch submissions to the Kaggle competition.
+
+ .. warning::
+
+ This method relies on Kaggle credentials to be exposed under
+ the environment variables:
+
+ - :code:`KAGGLE_USERNAME`
+ - :code:`KAGGLE_KEY`
+
+ Parameters
+ ----------
+ competition_id
+ Kaggle competition ID (can be read from the Kaggle competition
+ URL).
+
+ Returns
+ -------
+ :code:`str`
+ Kaggle submissions table in CSV format.
+
+ The table contains the following columns and associated types::
+
+ fileName date description status publicScore privateScore
+ --------------------- ------------------- ----------------- -------- ----------- ------------
+ gender_submission.csv 2020-04-27 10:56:14 Gender Submission complete 0.76555 0.75443
+
+ Raises
+ ------
+ KaggleError
+ If the Kaggle API call fails.
+
+ Example
+ -------
+ >>> fetch("titanic")
+ fileName,date,description,status,publicScore,privateScore
+ gender_submission.csv,2020-04-27 10:56:14,Gender Submission,complete,0.76555,None
+
+ References
+ ----------
+ - `Kaggle API Credentials`_
+ - `Kaggle Submissions API`_
+
+ .. _Kaggle API Credentials: https://github.com/Kaggle/kaggle-api#api-credentials
+ .. _Kaggle Submissions API: https://github.com/Kaggle/kaggle-api/#list-competition-submissions
+ """
+ result = subprocess.run(
+ ["kaggle", "competitions", "submissions", "--csv", competition_id],
+ capture_output=True,
+ )
+
+ if result.returncode == 1:
+ output = str(result.stderr or result.stdout, "utf-8").replace("\n", "\n\t\t")
+ raise KaggleError(
+ f"Unexpected error in fetching Kaggle submissions:\n"
+ f"\tReturn Code: {result.returncode}\n"
+ f"\tOutput: {output}\n"
+ "\tPlease verify the Kaggle competition ID and credentials "
+ "provided in the environment"
+ )
+ elif result.returncode == 2:
+ output = str(result.stderr or result.stdout, "utf-8").replace("\n", "\n\t\t")
+ raise KaggleError(
+ f"Misused Kaggle command in fetching submissions:\n"
+ f"\tReturn Code: {result.returncode}\n"
+ f"\tOutput:\n\t\t{output}\n"
+ "\tPlease check the documentation: https://github.com/Kaggle/kaggle-api#kaggle-api"
+ )
+ else:
+ return str(result.stdout, "utf-8")
+
+
+def parse(kaggle_csv_submissions_table: str) -> pd.DataFrame:
+ """
+
+ Parameters
+ ----------
+ kaggle_csv_submissions_table
+ Kaggle submissions table in CSV format.
+
+ The table should contain the following columns and associated
+ types::
+
+ fileName date description status publicScore privateScore
+ --------------------- ------------------- ----------------- -------- ----------- ------------
+ gender_submission.csv 2020-04-27 10:56:14 Gender Submission complete 0.76555 0.75443
+
+
+ Returns
+ -------
+ :code:`pd.DataFrame`
+ Kaggle submissions data frame.
+
+ Note
+ ----
+ This function is intended to be used in conjunction with :meth:`fetch`.
+
+ Example
+ -------
+ >>> parse(fetch("titanic"))
+ fileName date ... publicScore privateScore
+ 0 gender_submission.csv 2020-04-27 10:56:14 ... 0.76555 0.75443
+ """
+ submissions_df = pd.read_csv(StringIO(kaggle_csv_submissions_table),)
+
+ submissions_df["date"] = pd.to_datetime(
+ submissions_df["date"], format="%Y-%m-%d %H:%M:%S"
+ )
+ submissions_df["publicScore"] = pd.to_numeric(
+ submissions_df["publicScore"], errors="coerce"
+ )
+ submissions_df["privateScore"] = pd.to_numeric(
+ submissions_df["privateScore"], errors="coerce"
+ )
+
+ return submissions_df
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..9286d89
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,82 @@
+[tool.black] # https://black.readthedocs.io/en/stable/pyproject_toml.html
+target-version = ['py37', 'py38']
+include = '\.pyi?$' # Default
+exclude = '''
+# Directory exclusions
+/(
+ # Defaults:
+ \.eggs
+ | \.git
+ | \.hg
+ | \.mypy_cache
+ | \.tox
+ | \.venv
+ | _build
+ | buck-out
+ | build
+ | dist
+ # Add additional patterns here:
+ # | my_directory
+)/
+
+| # File exclusions
+/(
+ # Add additional file patterns here:
+ # my_file\.py
+)
+'''
+
+[tool.isort] # https://github.com/timothycrosley/isort/wiki/isort-Settings#full-reference-of-isort-settings
+# Compatibility with Black (https://black.readthedocs.io/en/stable/the_black_code_style.html#how-black-wraps-lines)
+# Style ---------------------
+line_length=88
+wrap_length=88
+multi_line_output=3
+combine_star='True'
+combine_as_imports='True'
+balanced_wrapping='True'
+indent=4
+lines_after_imports=2
+lines_between_types=0
+include_trailing_comma='True'
+use_parentheses='True'
+#force_single_line #Would override mult-line_output
+#force_grid_wrap #Would override mult-line_output
+# Sections ------------------
+default_section="THIRDPARTY"
+known_datascience=['pandas', 'numpy', 'scipy', 'sklearn']
+import_heading_stdlib="Standard Library ---------------------------------------------------------------------"
+import_heading_thirdparty="Third Party --------------------------------------------------------------------------"
+import_heading_datascience="Data Science"
+import_heading_firstparty="Project ------------------------------------------------------------------------------"
+#forced_separate=""
+# Order ---------------------
+sections=['FUTURE','STDLIB', 'THIRDPARTY', 'DATASCIENCE', 'FIRSTPARTY','LOCALFOLDER']
+case_sensitive='False'
+length_sort='False'
+order_by_type='True'
+force_alphabetical_sort='False'
+force_sort_within_sections='False'
+from_first='False'
+reverse-relative='True'
+#force_to_top=""
+# Inclusions/Exclusions -----
+#skip # Requires full file path
+skip_glob=[
+ ".eggs/**",
+ ".git/*",
+ ".hg/*",
+ ".mypy_cache/*",
+ ".tox/*",
+ ".venv/*",
+ "_build/*",
+ "buck-out/*",
+ "build/*",
+ "dist/*",
+ # Add additional patterns here:
+ # "my_directory/*"
+]
+#not_skip=[]
+#filter_files # Does not seem to work...
+# Other ---------------------
+atomic='False'
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..6dc6368
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,6 @@
+-r requirements.txt
+-r requirements-docs.txt
+
+black==19.10b0
+isort==4.3.21
+docformatter==1.3.1
diff --git a/requirements-docs.txt b/requirements-docs.txt
new file mode 100644
index 0000000..f89f55c
--- /dev/null
+++ b/requirements-docs.txt
@@ -0,0 +1,2 @@
+sphinx==3.0.3
+sphinx-rtd-theme==0.4.3
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..d6e1198
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+-e .
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..6bb8c49
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,17 @@
+# Third Party --------------------------------------------------------------------------
+from setuptools import setup
+
+
+# This minimal package configuration is solely meant to make the module
+# installable for use in the action Docker container and not to publish
+# it on PyPi.
+setup(
+ name="kaggle_graph",
+ install_requires=[
+ "kaggle==1.5.6",
+ "python-dotenv==0.13.0",
+ "pandas==1.0.3",
+ "seaborn==0.10.1",
+ "matplotlib==3.2.1",
+ ],
+)
diff --git a/workflow.template.yaml b/workflow.template.yaml
new file mode 100644
index 0000000..e0718a0
--- /dev/null
+++ b/workflow.template.yaml
@@ -0,0 +1,24 @@
+name: Kaggle Graph Generation
+
+on:
+ push:
+ branches: master
+ schedule:
+ - cron: '00 00 * * *'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Git Checkout
+ uses: actions/checkout@v2
+ - name: Kaggle Graph Generation
+ uses: OliverSieweke/kaggle-graph@v0.1.0
+ with:
+ KAGGLE_KEY: ${{ secrets.KAGGLE_KEY }}
+ KAGGLE_USERNAME: oliversieweke
+ KAGGLE_COMPETITION_ID: titanic
+ - name: Git Auto Commit
+ uses: stefanzweifel/git-auto-commit-action@v4.1.6
+ with:
+ commit_message: Automatic Kaggle graph generation