Skip to content

Commit

Permalink
fix(apps): Enhance project configuration, testing and logging
Browse files Browse the repository at this point in the history
- Added new classifiers to pyproject.toml for better package categorization.
- Updated .gitignore to exclude htmlcov directory.
- Improved logging in _bootstrap.py for better visibility during template copying and dependency installation.
- Enhanced communication management in _communication.py and _execution.py to streamline message handling and improve error reporting.
- Refactored widget handling in app.py to ensure consistent session management and improved error handling in template rendering.

These changes improve the overall configuration, logging, and communication flow within the application, enhancing maintainability and user experience.
  • Loading branch information
Lasse-numerous committed Dec 28, 2024
1 parent bb77b1f commit c0dbc53
Show file tree
Hide file tree
Showing 24 changed files with 2,037 additions and 652 deletions.
Binary file added .coverage
Binary file not shown.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ node_modules
test_apps

build
htmlcov
33 changes: 33 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
default_stages: ["pre-commit", "pre-push"]
default_install_hook_types: [pre-commit, pre-push]
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.6.4
hooks:
# Run the linter.
- id: ruff
args: [--fix]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
hooks:
- id: mypy
entry: "mypy --strict ./src"
pass_filenames: false
additional_dependencies:
- "types-requests"
- "pytest-asyncio"
- "pydantic"
- "marimo"
- repo: local
hooks:
- id: pytest-check
stages: [pre-push]
types: [python]
name: pytest-check
entry: python -m pytest -v tests/
language: system
pass_filenames: false
always_run: true
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
81 changes: 81 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ dependencies = [

readme = "docs/README.md"
license = {text = "MIT"} # Adjust license as needed
classifiers = [
"Programming Language :: Python :: 3",
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers"
]

[project.optional-dependencies]
dev = [
Expand All @@ -36,6 +41,15 @@ dev = [
"mkdocstrings==0.26.2",
"mkdocstrings-python==1.12.2",
"mkdocs_snippet_plugin==1.0.2",
"pre-commit",
"black",
"mkdocs-material==9.5.46",
"mkdocs-gen-files==0.5.0",
"mkdocs-section-index==0.3.9",
"mkdocs-literate-nav==0.6.1",
"marimo",
"panel",
"pytest-cov==4.1.0"
]

[tool.poetry.dependencies]
Expand Down Expand Up @@ -76,3 +90,70 @@ packages = ["numerous.apps"]

[project.scripts]
numerous-bootstrap = "numerous.apps._bootstrap:main"

[tool.ruff]
src = ["src"]
exclude = ["examples", "tests"]

[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN101",
"D101",
"D103",
"D107",
"D203",
"D211",
"D212",
"FA100",
"FA102",
"ISC001",
"COM812",
"FBT001",
"FBT002",
"PLR0913",
"G004",
"EM101",
"TRY003",
"DTZ005",
"EM102",
"PLC0414",
]

[tool.ruff.lint.isort]
lines-after-imports = 2

[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false

[tool.ruff.lint.extend-per-file-ignores]
"tests/**" = ["INP001", "S101", "D100", "D103"]

[tool.mypy]
ignore_missing_imports = true
exclude = ["examples"]

[tool.pytest.ini_options]
addopts = "--cov=numerous.apps --cov-report=term-missing --cov-report=html"
testpaths = ["tests"]

[tool.coverage.run]
source = ["numerous.apps"]
branch = true

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"pass",
"raise ImportError",
]
ignore_errors = true
omit = [
"tests/*",
"setup.py",
]
4 changes: 3 additions & 1 deletion src/numerous/apps/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .app import create_app as create_app
"""Module containing the app framework."""

from .app import create_app as create_app
74 changes: 40 additions & 34 deletions src/numerous/apps/_bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,73 @@
#!/usr/bin/env python3

import argparse
import os
import logging
import shutil
import subprocess
import sys
from pathlib import Path


# Set up basic logging configuration
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)


def copy_template(destination_path: Path) -> None:
"""
Copy template directory to destination
"""
"""Copy template directory to destination."""
if destination_path.exists():
print("Skipping copy...")
return
logging.info("Skipping copy...")
return

try:
template_path = Path(__file__).parent / "bootstrap_app"
shutil.copytree(template_path, destination_path)
print(f"Created new project at: {destination_path}")
except Exception as e:
print(f"Error copying template: {e}")
logging.info(f"Created new project at: {destination_path}")
except Exception:
logging.exception("Error copying template.")
sys.exit(1)


def install_requirements(project_path: Path) -> None:
"""
Install requirements from requirements.txt if it exists
"""
"""Install requirements from requirements.txt if it exists."""
requirements_file = project_path / "requirements.txt"

if not requirements_file.exists():
print("No requirements.txt found, skipping dependency installation")
logging.info("No requirements.txt found, skipping dependency installation")
return

print("Installing dependencies from requirements.txt...")
logging.info("Installing dependencies from requirements.txt...")
try:
subprocess.run([sys.executable, "-m", "pip", "install", "-r", str(requirements_file)], check=True)
print("Dependencies installed successfully")
except subprocess.CalledProcessError as e:
print(f"Error installing dependencies: {e}")
subprocess.run( # noqa: S603
[sys.executable, "-m", "pip", "install", "-r", str(requirements_file)],
check=True,
)
logging.info("Dependencies installed successfully")
except subprocess.CalledProcessError:
logging.exception("Error installing dependencies.")
sys.exit(1)


def run_app(project_path: Path) -> None:
"""
Run the app
"""
subprocess.run([sys.executable, "app.py"], cwd=project_path)
"""Run the app."""
subprocess.run( # noqa: S603
[sys.executable, "app.py"], cwd=project_path, check=False
)


def main():
parser = argparse.ArgumentParser(description="Bootstrap a new app project from our template")
def main() -> None:
parser = argparse.ArgumentParser(
description="Bootstrap a new app project from our template"
)
parser.add_argument("project_name", help="Name of the new project")

parser.add_argument(
"--skip-deps",
action="store_true",
help="Skip installing dependencies"
"--skip-deps", action="store_true", help="Skip installing dependencies"
)

parser.add_argument(
"--run-skip",
action="store_true",
help="Skip running the app after creation"
"--run-skip", action="store_true", help="Skip running the app after creation"
)

args = parser.parse_args()
Expand All @@ -76,10 +83,9 @@ def main():
if not args.skip_deps:
install_requirements(project_path)

print(f"\nProject '{args.project_name}' has been created successfully!")

if not args.run_skip:
run_app(project_path)


if __name__ == "__main__":
main()
63 changes: 16 additions & 47 deletions src/numerous/apps/_builtins.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,13 @@
import pathlib
from typing import Any

import anywidget
import traitlets
from typing import Any, List, Dict
from anywidget import AnyWidget


class ParentVisibility(anywidget.AnyWidget):
_esm = """
function render({ model, el }) {
// Get the parent element - handle both Shadow DOM and regular DOM cases
let parent_el;
if (el.getRootNode() instanceof ShadowRoot) {
// Shadow DOM case
let shadow_host = el.getRootNode().host;
parent_el = shadow_host.parentElement;
} else {
// Regular DOM case
parent_el = el.parentElement;
}
el.style.display = "none";
set_visibility(model.get('visible'));
function set_visibility(visible) {
if (!parent_el) return;
if (visible) {
parent_el.classList.remove("numerous-apps-hidden");
parent_el.classList.add("numerous-apps-visible");
} else {
parent_el.classList.add("numerous-apps-hidden");
parent_el.classList.remove("numerous-apps-visible");
}
}
model.on("change:visible", (value) => set_visibility(value));
}
export default { render };
"""
class ParentVisibility(anywidget.AnyWidget): # type: ignore [misc]
_esm = pathlib.Path(__file__).parent / "js" / "parent_visibility.js"
_css = """
.numerous-apps-visible {
display: var(--display-value) !important;
Expand All @@ -64,25 +34,24 @@ class ParentVisibility(anywidget.AnyWidget):

visible = traitlets.Bool(default_value=True).tag(sync=True)

def __init__(self, **kwargs: Dict[str, Any]) -> None:
def __init__(self, **kwargs: dict[str, Any]) -> None:
super().__init__(**kwargs)
self._visible = True
self.observe(self._update_visibility, names="visible")
def _update_visibility(self, event: Any) -> None:

def _update_visibility(self, event: Any) -> None: # noqa: ANN401
self._visible = event.new

def tab_visibility(tabs_widget: AnyWidget) -> List[ParentVisibility]:
visibility_widgets = []
for tab in tabs_widget.tabs:
visibility_widgets.append(ParentVisibility(visible=tab == tabs_widget.active_tab))

def on_tab_change(event: Any) -> None:
def tab_visibility(tabs_widget: AnyWidget) -> list[ParentVisibility]:
visibility_widgets = [
ParentVisibility(visible=tab == tabs_widget.active_tab)
for tab in tabs_widget.tabs
]

def on_tab_change(event: Any) -> None: # noqa: ANN401
for i, tab in enumerate(tabs_widget.tabs):
visibility_widgets[i].visible = tab == event.new

tabs_widget.observe(on_tab_change, names='active_tab')
tabs_widget.observe(on_tab_change, names="active_tab")
return visibility_widgets



Loading

0 comments on commit c0dbc53

Please sign in to comment.