Skip to content

Commit

Permalink
Merge pull request #1 from dtrai2/dev-add-ci-pipeline
Browse files Browse the repository at this point in the history
add ci pipeline and linting
  • Loading branch information
dtrai2 authored Jun 22, 2024
2 parents f357a2c + 8971067 commit 6112e46
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 49 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI Pipeline

on:
pull_request:
types: [opened, synchronize]
push:
branches: [main]

jobs:
code-quality:
runs-on: ubuntu-22.04
strategy:
matrix:
python-version: ["3.12"]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- name: Install dependencies
run: |
pip install --upgrade pip pylint
pip install .[dev]
- name: check black formating
run: |
black --check --diff --config ./pyproject.toml .
- name: lint changed and added files
run: |
pylint --rcfile=.pylintrc --fail-under 9 shiny_invoice/
38 changes: 38 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
release:
types: [published]

jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/logprep
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel
- name: Build package
run: pip wheel --no-deps --wheel-dir ./dist .
- name: Publish package
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
5 changes: 5 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[FORMAT]
max-line-length=100

[MESAGES CONTROL]
disable=too-few-public-methods
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ packages = ["shiny_invoice"]
name = "shiny-invoice"
description = "Simply manage invoices"
dynamic = ["version"]
requires-python = ">=3.10"
requires-python = ">=3.12"
readme = "README.md"
license = { file = "LICENSE" }
classifiers = [
Expand Down
23 changes: 19 additions & 4 deletions shiny_invoice/shiny_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,29 @@

@click.group(name="shiny-invoice")
def cli():
...
"""Shiny Invoice CLI"""


@cli.command(short_help="Run Shiny Invoice")
@click.option("--config", type=click.Path(exists=True), required=True, help="Path to the configuration yaml file.")
@click.option("--host", type=str, default="0.0.0.0", help="Host used for the server, defaults to '0.0.0.0'.")
@click.option("--port", type=int, default=8000, help="Port used for the server, defaults to '8000'.")
@click.option(
"--config",
type=click.Path(exists=True),
required=True,
help="Path to the configuration yaml file.",
)
@click.option(
"--host", type=str, default="0.0.0.0", help="Host used for the server, defaults to '0.0.0.0'."
)
@click.option(
"--port", type=int, default=8000, help="Port used for the server, defaults to '8000'."
)
def run(config: Path, host: str, port: int):
"""Run shiny invoice"""
with open(config, "r", encoding="utf8") as file:
config_str = file.read()
config = yaml.load(config_str)

# pylint: disable=too-many-function-args
app_ui = ui.page_navbar(
ui.nav_panel("Existing Invoices", existing_invoices_ui("existing_invoices")),
ui.nav_panel("Create Invoice", new_invoice_ui("new_invoice", config)),
Expand All @@ -40,11 +50,16 @@ def run(config: Path, host: str, port: int):
id="navbar_id",
)

# pylint: enable=too-many-function-args

# pylint: disable=redefined-builtin, unused-argument, no-value-for-parameter
def server(input: Inputs, output: Outputs, session: Session):
existing_invoices_server("existing_invoices", config)
new_invoice_server("new_invoice", config)
config_server("config", config)

# pylint: enable=redefined-builtin, unused-argument, no-value-for-parameter

app = App(app_ui, server, static_assets=config.get("paths").get("invoices_root_dir"))
app.run(host=host, port=port)

Expand Down
15 changes: 7 additions & 8 deletions shiny_invoice/ui_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""
This module contains the ui and server for the configuration view. It simply displays the configuration inside the
shiny invoice application
This module contains the ui and server for the configuration view. It simply displays the
configuration inside the shiny invoice application
"""

import io

from ruamel.yaml import YAML
Expand All @@ -12,16 +13,14 @@

@module.ui
def config_ui():
return ui.div(
ui.card(
ui.card_header("Configuration"),
ui.output_code("config_output")
)
)
"""Defines the shiny ui for the configuration"""
return ui.div(ui.card(ui.card_header("Configuration"), ui.output_code("config_output")))


@module.server
def config_server(input, output, session, config):
"""Contains the shiny server for the configuration view"""

@render.text
def config_output():
"""Dump the configuration into a string and return it"""
Expand Down
58 changes: 33 additions & 25 deletions shiny_invoice/ui_existing_invoices.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,56 +10,60 @@

@module.ui
def existing_invoices_ui():
"""Defines the shiny ui for existing invoices"""
return ui.div(
ui.card(
ui.card_header("Filter"),
ui.layout_columns(
ui.tooltip(
ui.input_text("invoice_numbers", "Filter by invoices", placeholder="13,21,37"),
"Comma separated"
"Comma separated",
),
ui.input_date_range(
id="daterange",
label="Filter by Date range",
start=f"{datetime.date.today().year}-01-01"
start=f"{datetime.date.today().year}-01-01",
),
ui.input_checkbox_group(
id="paid_status", label=
"Paid Status",
id="paid_status",
label="Paid Status",
choices={"paid": "Paid", "unpaid": "Unpaid"},
inline=True
)
)
inline=True,
),
),
),
ui.card(
ui.layout_column_wrap(
ui.card(
ui.card_header("List of filtered invoices"),
ui.output_data_frame("invoice_list")
ui.output_data_frame("invoice_list"),
),
ui.card(
ui.card_header("Selected Invoice"),
ui.output_ui("selected_invoice")
)
ui.card(ui.card_header("Selected Invoice"), ui.output_ui("selected_invoice")),
)
)
),
)


@module.server
def existing_invoices_server(input, output, session, config):
"""Contains the Shiny Server for existing invoices"""

@reactive.calc
def get_filtered_invoices() -> pd.DataFrame | str:
"""Retrieve all invoices from the configured directories and parse them into a DataFrame. The input filters
will then be applied to the dataframe such that only the desired results will be returned."""
"""Retrieve all invoices from the configured directories and parse them into a DataFrame.
The input filters will then be applied to the dataframe such that only the desired results
will be returned.
"""
paid_records, unpaid_records = _get_invoice_records()
df = pd.DataFrame.from_records(paid_records + unpaid_records)
if len(df) == 0:
return df
duplicate_numbers = df[df.duplicated(["Invoice"], keep="last")]
if len(duplicate_numbers) > 0:
duplicate_ids = ", ".join(duplicate_numbers['Invoice'].to_list())
ui.notification_show(f"Found duplicate invoice ids: {duplicate_ids}", type="warning", duration=2)
duplicate_ids = ", ".join(duplicate_numbers["Invoice"].to_list())
ui.notification_show(
f"Found duplicate invoice ids: {duplicate_ids}", type="warning", duration=2
)
df = _filter_invoices(df)
return df

Expand Down Expand Up @@ -90,18 +94,22 @@ def _create_invoice_records(file_paths, status):
for invoice_path in file_paths:
parts = invoice_path.split("/")
name_parts = parts[-1].split("-")
date = datetime.date(year=int(name_parts[0]), month=int(name_parts[1]), day=int(name_parts[2]))
date = datetime.date(
year=int(name_parts[0]), month=int(name_parts[1]), day=int(name_parts[2])
)
invoice_number = name_parts[3]
customer = name_parts[-1].replace(".html", "")
root_dir = config.get("paths").get("invoices_root_dir")
invoice_path = invoice_path.replace(root_dir, "")
records.append({
"Date": date,
"Invoice": invoice_number,
"Status": status,
"Customer": customer,
"Link": ui.a("Download", href=invoice_path, target="_blank"),
})
records.append(
{
"Date": date,
"Invoice": invoice_number,
"Status": status,
"Customer": customer,
"Link": ui.a("Download", href=invoice_path, target="_blank"),
}
)
return records

@render.data_frame
Expand Down
27 changes: 16 additions & 11 deletions shiny_invoice/ui_new_invoice.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module contains the ui and the server for creating a new invoice."""

import datetime
import io
from pathlib import Path
Expand All @@ -10,6 +11,7 @@

@module.ui
def new_invoice_ui(config):
"""Defines the shiny ui for new invoices"""
invoice_defaults = config.get("invoice_defaults")

return ui.layout_column_wrap(
Expand All @@ -18,13 +20,15 @@ def new_invoice_ui(config):
ui.input_text(id="invoice_number", label="Invoice Number", value="1", width="100%"),
ui.input_date(id="created_at_date", label="Created At", width="100%"),
ui.output_ui(id="due_date_ui", width="100%"),
ui.input_text(id="introduction", label="Introduction", value="Dear Sir or Madam,", width="100%"),
ui.input_text(
id="introduction", label="Introduction", value="Dear Sir or Madam,", width="100%"
),
ui.input_text_area(
id="recipient_address",
label="Recipient Address",
value=invoice_defaults.get("recipient"),
rows=3,
width="100%"
width="100%",
),
ui.tooltip(
ui.input_text_area(
Expand All @@ -33,17 +37,16 @@ def new_invoice_ui(config):
value=invoice_defaults.get("items"),
rows=6,
width="100%",
spellcheck=True
spellcheck=True,
),
"Should be in csv format. The last column will be used to calculate the total price."
"The values should be before taxes."
"Should be in csv format. The last column will be used to calculate the"
"total price. The values should be before taxes.",
),
ui.download_button(id="download_button", label="Download Invoice", width="100%")
ui.download_button(id="download_button", label="Download Invoice", width="100%"),
),
ui.card(
ui.card_header("Rendered Invoice"),
ui.output_ui(id="rendered_invoice_ui", width="100%")
)
ui.card_header("Rendered Invoice"), ui.output_ui(id="rendered_invoice_ui", width="100%")
),
)


Expand All @@ -65,7 +68,9 @@ def convert_invoice_csv_to_html() -> str:
def calculate_totals():
items = parse_invoice_items()
last_column = items.columns[-1]
items[last_column] = items[last_column].str.replace(".", "").str.replace("€", "").astype(float)
items[last_column] = (
items[last_column].str.replace(".", "").str.replace("€", "").astype(float)
)
return items[last_column].sum()

@render.ui
Expand Down Expand Up @@ -114,7 +119,7 @@ def render_invoice():
"invoice_items": convert_invoice_csv_to_html(),
"total_net": f"{total_net:n} €",
"tax": f"{tax:n} €",
"total_gross": f"{total_gross:n} €"
"total_gross": f"{total_gross:n} €",
}
return html_template.substitute(substitutions)

Expand Down

0 comments on commit 6112e46

Please sign in to comment.