Skip to content

Commit

Permalink
✨(project) add boilerplate
Browse files Browse the repository at this point in the history
Let's dive in course recommendation with wendy. First steps for this project:
- create a Python project package
- provide a first investigation notebook
- provide a CI with Github Actions for common code quality jobs
  • Loading branch information
quitterie-lcs committed Apr 19, 2024
1 parent b912665 commit 98d2123
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Jupyter
data/
__pycache__
.ipynb_checkpoints

# System-specific files
.DS_Store
**/.DS_Store

# Docker
.dockerignore
Dockerfile
docker-compose.*

# Build
bin/
Makefile

# Development/test cache & configurations
gitlint
.cache
.coverage
.env
.env.dist
.git
.github
.gitignore
.pylint.d
.pylintrc
.pytest_cache
85 changes: 85 additions & 0 deletions .github/workflows/service-notebook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Notebook service CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
lint-git:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
# Checkout on HEAD commit of the current pull request HEAD.
# As this job is triggered on pull request event, the checkout is by
# default on the merge commit.
ref: ${{ github.event.pull_request.head.sha }}
# By default, `fetch-depth` is set to 1, meaning it would only
# consider the last commit. Set to 0 will fetch the entire history
# of the branch commits.
fetch-depth: 0

- name: Enforce absence of print statements in code
run: |
! git diff origin/main..HEAD -- . ':(exclude).github' | grep "print("
- name: Enforce absence of FIXME statements in code
run: |
! git diff origin/main..HEAD -- . ':(exclude).github' | grep "FIXME"
- name: Check absence of fixup commits
run: |
! git log --pretty=format:%s | grep 'fixup!'
- name: Install gitlint
run: |
pip install --user gitlint requests
- name: Lint commit messages added to main
run: |
~/.local/bin/gitlint --commits origin/main..HEAD

build-docker-notebook:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Build wendy notebook image
run: make build

- name: Check built image availability
run: docker images "wendy-notebook:latest"

- name: Export Docker image
run: >
mkdir artifacts &&
docker save wendy-notebook:latest > artifacts/wendy-notebook.tar
- name: Save Docker image artifact
uses: actions/upload-artifact@v4
with:
name: docker-wendy-notebook
path: artifacts
retention-days: 1
compression-level: 0

lint:
needs: build-docker-notebook
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Retrieve saved Docker image
uses: actions/download-artifact@v4
with:
name: docker-wendy-notebook
path: artifacts

- name: Load Docker image
run: docker load -i artifacts/wendy-notebook.tar

- name: Lint notebook with nbqa
run: make lint
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Checkpoints
**/.ipynb_checkpoints/

# Jupytext
*.ipynb
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -- Base image --
FROM jupyter/minimal-notebook:latest as base

# Upgrade pip to its latest release to speed up dependencies installation
RUN pip install --upgrade pip

COPY . /home/jovyan/work

WORKDIR /home/jovyan/work

RUN pip install -e .[dependencies]

# -- Development image --
FROM base as development

RUN pip install -e .[dev]

# Un-privileged user running the application
USER ${DOCKER_USER:-1000}
70 changes: 70 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# -- General
SHELL := /bin/bash

# -- Docker
# Get the current user ID to use for docker run and docker exec commands
COMPOSE = bin/compose
COMPOSE_RUN = $(COMPOSE) run --rm --no-deps
COMPOSE_RUN_NOTEBOOK = $(COMPOSE_RUN) notebook

# ==============================================================================
# RULES

default: help

bootstrap: ## bootstrap project
bootstrap: \
build \
run
.PHONY: bootstrap

build: ## build custom jupyter notebook image
@$(COMPOSE) build notebook
.PHONY: build

down: ## stop and remove backend containers
@$(COMPOSE) down
.PHONY: down

jupytext-to-md: ## convert local ipynb files into md
bin/jupytext --to md notebooks/**/*.ipynb
.PHONY: jupytext-to-md

jupytext-to-ipynb: ## convert remote md files into ipynb
bin/jupytext --to ipynb notebooks/**/*.md
.PHONY: jupytext-to-ipynb

lint: ## lint notebook with nbqa
@echo 'lint:ruff started…'
@$(COMPOSE_RUN_NOTEBOOK) nbqa ruff notebooks
@echo 'lint:black started…'
@$(COMPOSE_RUN_NOTEBOOK) nbqa black notebooks
.PHONY: lint

lint-fix: ## lint and fix notebook errors with nbqa
@echo 'lint:ruff started…'
@$(COMPOSE_RUN_NOTEBOOK) nbqa ruff --fix notebooks
.PHONY: lint-fix

logs: ## display app logs (follow mode)
@$(COMPOSE) logs -f notebook
.PHONY: logs

run: ## run notebook server
run:
@$(COMPOSE) up -d notebook
.PHONY: run

status: ## an alias for "docker compose ps"
@$(COMPOSE) ps
.PHONY: status

stop: ## stops backend servers
stop:
@$(COMPOSE) stop
.PHONY: stop

# -- Misc
help:
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: help
6 changes: 6 additions & 0 deletions bin/compose
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

declare DOCKER_USER
DOCKER_USER="$(id -u):$(id -g)"

DOCKER_USER=${DOCKER_USER} docker compose "$@"
6 changes: 6 additions & 0 deletions bin/jupytext
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

declare DOCKER_USER
DOCKER_USER="$(id -u):$(id -g)"

DOCKER_USER=${DOCKER_USER} docker compose run --rm notebook jupytext "$@"
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:

notebook:
build:
context: .
target: "${WENDY_IMAGE_BUILD_TARGET:-development}"
args:
DOCKER_USER: ${DOCKER_USER:-1000}
user: ${DOCKER_USER:-1000}
environment:
NB_UID: ${DOCKER_UID:-1000}
NB_GID: ${DOCKER_GID:-1000}
CHOWN_HOME: 'yes'
CHOWN_HOME_OPTS: -R
group_add:
- users
ports:
- 8080:8888
volumes:
- ./wendy:/home/jovyan/work
37 changes: 37 additions & 0 deletions gitlint/gitlint_emoji.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Gitlint extra rule to validate that the message title is of the form
"<gitmoji>(<scope>) <subject>"
"""
from __future__ import unicode_literals

import re

import requests

from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation


class GitmojiTitle(LineRule):
"""
This rule will enforce that each commit title is of the form "<gitmoji>(<scope>) <subject>"
where gitmoji is an emoji from the list defined in https://gitmoji.carloscuesta.me and
subject should be all lowercase
"""

id = "UC1"
name = "title-should-have-gitmoji-and-scope"
target = CommitMessageTitle

def validate(self, title, _commit):
"""
Download the list possible gitmojis from the project's github repository and check that
title contains one of them.
"""
gitmojis = requests.get(
"https://raw.githubusercontent.com/carloscuesta/gitmoji/master/packages/gitmojis/src/gitmojis.json"
).json()["gitmojis"]
emojis = [item["emoji"] for item in gitmojis]
pattern = r"^({:s})\(.*\)\s[a-z].*$".format("|".join(emojis))
if not re.search(pattern, title):
violation_msg = 'Title does not match regex "<gitmoji>(<scope>) <subject>"'
return [RuleViolation(self.id, violation_msg, title)]
29 changes: 29 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[project]
name = "wendy"
description = "An AI Poc for course recommendation"
readme = "README.md"
authors = [
{ name="Open FUN (France Universite Numerique)", email="[email protected]" },
]
dependencies = [
"pandas==1.5.3",
"scikit-learn==1.2.1",
"spacy==3.5.0",
"seaborn==0.12.2",
]
version = "0.1.0"

[project.optional-dependencies]
dev = [
"nbqa[toolchain]==1.7.1",
"jupytext==1.14.5"
]

[tool.ruff]
exclude = ["/**/*.ipynb"]

[tool.black]
line-length = 96

[tool.jupytext]
formats = "ipynb,md"
23 changes: 23 additions & 0 deletions wendy/notebooks/hello-world.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
jupyter:
jupytext:
text_representation:
extension: .md
format_name: markdown
format_version: '1.3'
jupytext_version: 1.14.5
kernelspec:
display_name: Python 3 (ipykernel)
language: python
name: python3
---

# Hello world !

This is an example.

```python
def hello_world():
"""Return 'Hello World!' string."""
return "Hello World!"
```

0 comments on commit 98d2123

Please sign in to comment.