From ec715ec6fb1bf696898f7510f3156f44b7d27751 Mon Sep 17 00:00:00 2001 From: Angel Dijoux Date: Thu, 14 Nov 2024 20:09:17 +0100 Subject: [PATCH] fix: pr suggesstions --- Cargo.toml | 2 +- src/commands/new/use_case.rs | 24 +++--- src/commands/test.rs | 45 ++++++----- src/readme.rs | 79 ++++++++++--------- template/assets/use_case/README.md.hbs | 2 - .../assets/use_case/template/README.md.hbs | 2 +- test/.gitignore | 55 +++++++++++++ test/README.md | 19 +++++ test/pyproject.toml | 28 +++++++ test/submission/solution.ipynb | 60 ++++++++++++++ 10 files changed, 242 insertions(+), 74 deletions(-) create mode 100644 test/.gitignore create mode 100644 test/README.md create mode 100644 test/pyproject.toml create mode 100644 test/submission/solution.ipynb diff --git a/Cargo.toml b/Cargo.toml index 517c6e9..b2761de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ mime = "0.3" open = "5.0" owo-colors = { version = "4.0", features = ["supports-colors"] } passterm = "2.0" -pyo3 = { version = "0.20", features = ["serde"]} +pyo3 = { version = "0.20", features = ["serde"] } pyo3-asyncio = { version = "0.20", features = ["attributes", "tokio-runtime"] } rand = "0.8" reqwest = { version = "0.11", default-features = false, features = [ diff --git a/src/commands/new/use_case.rs b/src/commands/new/use_case.rs index 92ac06d..2900aa8 100644 --- a/src/commands/new/use_case.rs +++ b/src/commands/new/use_case.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use aqora_template::UseCaseTemplate; use clap::Args; @@ -6,7 +6,7 @@ use graphql_client::GraphQLQuery; use indicatif::ProgressBar; use serde::Serialize; -use crate::error::{self, Error, Result}; +use crate::error::{self, Result}; use crate::graphql_client::{custom_scalars::*, GraphQLClient}; use super::GlobalArgs; @@ -53,20 +53,18 @@ pub async fn use_case(args: UseCase, global: GlobalArgs) -> Result<()> { .competition(args.competition) .title(competition.title) .render(&dest) - .map_err(|e| format_permission_error("create use case", &dest, &e))?; + .map_err(|e| { + error::user( + &format!("Failed to create use case at '{}': {}", dest.display(), e), + &format!( + "Make sure you have the correct permissions for '{}'", + dest.display() + ), + ) + })?; pb.finish_with_message(format!( "Created use case in directory '{}'", dest.display() )); Ok(()) } - -fn format_permission_error(action: &str, dest: &Path, error: &impl std::fmt::Display) -> Error { - error::user( - &format!("Failed to {} at '{}': {}", action, dest.display(), error), - &format!( - "Make sure you have the correct permissions for '{}'", - dest.display() - ), - ) -} diff --git a/src/commands/test.rs b/src/commands/test.rs index 7343a06..a694127 100644 --- a/src/commands/test.rs +++ b/src/commands/test.rs @@ -10,6 +10,7 @@ use crate::{ ipynb::{convert_submission_notebooks, convert_use_case_notebooks}, print::wrap_python_output, python::LastRunResult, + readme::get_readme_path, }; use aqora_config::{AqoraUseCaseConfig, PyProject}; use aqora_runner::{ @@ -230,13 +231,16 @@ fn run_pipeline( async fn update_score_badge_in_file( path: PathBuf, - competition: impl AsRef, + competition: Option, score: &Py, url: Url, ) -> Result<()> { let badge_url = build_shield_score_badge( score, - url.join(&format!("competitions/{}", competition.as_ref()))?, + url.join(&competition.map_or_else( + || "competitons".to_string(), + |name| format!("competitions/{}", name), + ))?, )?; with_locked_file( |file| { @@ -244,7 +248,7 @@ async fn update_score_badge_in_file( let mut contents = String::new(); file.read_to_string(&mut contents).await?; let updated_content = regex_replace_all!( - r"(?:)|(!\[Aqora Score Badge\]\([^\)]+\))", + r"(?:.*?)|(!\[Aqora Score Badge\]\([^\)]+\))", &contents, badge_url.clone() ); @@ -272,14 +276,17 @@ pub async fn run_submission_tests( .aqora() .and_then(|aqora| aqora.as_submission()) .ok_or_else(|| error::user("Submission config is not valid", ""))?; - let project_readme_path = project - .project - .as_ref() - .and_then(|p| p.readme.as_ref()) - .and_then(|readme| match readme { - aqora_config::ReadMe::RelativePath(path) => Some(path), - aqora_config::ReadMe::Table { file, .. } => file.as_ref(), - }); + let project_readme_path = get_readme_path( + &global.project, + project.project.as_ref().and_then(|p| p.readme.as_ref()), + ) + .await + .map_err(|err| { + error::user( + &format!("Could not read readme: {}", err), + "Please make sure the readme is valid", + ) + })?; let use_case_toml_path = project_use_case_toml_path(&global.project); let data_path = project_data_dir(&global.project, "data"); if !use_case_toml_path.exists() || !data_path.exists() { @@ -387,11 +394,10 @@ pub async fn run_submission_tests( }) .collect::>>()?; - let competition = modified_use_case.clone().competition.unwrap_or_default(); let (num_inputs, aggregated) = run_pipeline( &env, RunPipelineConfig { - use_case: modified_use_case, + use_case: modified_use_case.clone(), pipeline_config: config, tests: tests.clone(), last_run_dir, @@ -408,14 +414,15 @@ pub async fn run_submission_tests( "Score".if_supports_color(OwoStream::Stdout, |text| { text.bold() }), score )); - let path = global.project.join(project_readme_path.ok_or_else(|| { - error::user( - "Project must contain a README file", - "See : https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#readme", + if let Some(path) = project_readme_path { + update_score_badge_in_file( + global.project.join(path), + modified_use_case.competition, + &score, + global.aqora_url()?, ) - })?); - update_score_badge_in_file(path, &competition, &score, global.aqora_url().unwrap()) .await?; + }; pipeline_pb.finish_and_clear(); Ok(score) } diff --git a/src/readme.rs b/src/readme.rs index 2f34c0d..cc24e08 100644 --- a/src/readme.rs +++ b/src/readme.rs @@ -1,82 +1,85 @@ use aqora_config::ReadMe; use mime::Mime; -use std::path::Path; +use std::path::{Path, PathBuf}; use thiserror::Error; #[derive(Debug, Error)] -pub enum ReadMeError { +pub enum ReadmeError { #[error(transparent)] Io(#[from] std::io::Error), - #[error("Readme not found")] + #[error("README not found")] NotFound, - #[error("Readme content type not supported. Only markdown and plaintext supported")] + #[error("README content type not supported. Only markdown and plaintext supported")] ContentTypeNotSupported, } -pub async fn read_readme( +pub async fn get_readme_path( project_dir: impl AsRef, readme: Option<&ReadMe>, -) -> Result, ReadMeError> { +) -> Result, ReadmeError> { let path = match readme { Some(ReadMe::Table { ref file, - text, + text: _, content_type, }) => { let path: Option<&Path> = file.as_deref().map(str::as_ref); if let Some(content_type) = content_type { let mime: Mime = content_type .parse() - .map_err(|_| ReadMeError::ContentTypeNotSupported)?; + .map_err(|_| ReadmeError::ContentTypeNotSupported)?; if !(mime.type_() == mime::TEXT && (mime.subtype() == mime::PLAIN || mime.subtype() == "markdown")) { - return Err(ReadMeError::ContentTypeNotSupported); + return Err(ReadmeError::ContentTypeNotSupported); } } - if let Some(text) = text { - return Ok(Some(text.to_owned())); - } - path + path.map(|p| p.to_path_buf()) } - Some(ReadMe::RelativePath(ref path)) => Some(path.as_ref()), + Some(ReadMe::RelativePath(ref path)) => Some(PathBuf::from(path)), None => None, }; - let path = if let Some(path) = path { - project_dir.as_ref().join(path) - } else { - let mut dir = tokio::fs::read_dir(&project_dir).await?; - let mut path = None; - while let Some(entry) = dir.next_entry().await? { - match entry.file_name().to_string_lossy().to_lowercase().as_str() { - "readme.md" | "readme.txt" => {} - _ => continue, - } - let metadata = entry.metadata().await?; - if !metadata.is_file() { - continue; - } - path = Some(entry.path()); - break; + + if let Some(path) = path { + return Ok(Some(path)); + } + + let mut dir = tokio::fs::read_dir(&project_dir).await?; + while let Some(entry) = dir.next_entry().await? { + match entry.file_name().to_string_lossy().to_lowercase().as_str() { + "readme.md" | "readme.txt" => {} + _ => continue, } - if let Some(path) = path { - path - } else { - return Ok(None); + let metadata = entry.metadata().await?; + if metadata.is_file() { + return Ok(Some(entry.path())); } + } + + Ok(None) +} + +pub async fn read_readme( + project_dir: impl AsRef, + readme: Option<&ReadMe>, +) -> Result, ReadmeError> { + let path = match get_readme_path(project_dir, readme).await? { + Some(path) => path, + None => return Ok(None), }; + match path .extension() .map(|ext| ext.to_string_lossy().to_lowercase()) .as_deref() { Some("md") | Some("txt") | None => {} - _ => { - return Err(ReadMeError::ContentTypeNotSupported); - } + _ => return Err(ReadmeError::ContentTypeNotSupported), } + if !tokio::fs::try_exists(&path).await? { - return Err(ReadMeError::NotFound); + return Err(ReadmeError::NotFound); } + Ok(Some(tokio::fs::read_to_string(path).await?)) } diff --git a/template/assets/use_case/README.md.hbs b/template/assets/use_case/README.md.hbs index aafd526..5e5a480 100644 --- a/template/assets/use_case/README.md.hbs +++ b/template/assets/use_case/README.md.hbs @@ -1,5 +1,3 @@ - - ![Aqora badge](https://img.shields.io/badge/aqora-4328e5?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAIWSURBVHgBjZI%2FbBJxFMe%2F7%2F5x3BE9D2waCeZiEwNxECcbHSwmGt3qZkwcnB2Q0cFAR6d2Mk6KgcHNQSeHwmJaJ06XYmICprEq1PbaUk7ugJ93p1ToYPpb3svv5fve5%2F2%2BP8IRjqqdS4MGiwDTQFx1f2stx4%2BKip7Mi3KsKIVj87Kq7zj2z%2FqoJoajK15IAzTtxVlRiREFIi2ZB6Hg57yoIHEpC0k7lTGL16oRLTk3JFQmGcjkgkA0H4gEBWfSWShyHFIoet%2B%2FEyCbh9GJ2B%2Bhx24JvILU2SwiYhyCA4Q5feb6I3bRskyLGMuN6ZriEAsBqo9z3nhYkY%2FF0Q8BQznUjSQuvBseV9sshNzbArU0zdAAWbOsejOYOmpzY7ZiSJL%2BhMJKQoqeXh%2BqkuOogCuj48h4tlyg5Qncw%2Fz3brKrjoLbfRlTfQWwQ46w6zRmHM7edn5Zd8xi5t9EH8MVVUN0B5aPcvcWmwKHxX2%2Bc7Jtm5ddsaf4K2z9WEXrYznT2axXOVlLGg7JNdYf1BxCI6ynHpReUYvv4aVrfU1wdk8ResDel1V8r5XBBsj7wzie8NyLxgE7Q94nKL6h17B3Pglet%2B2N91hfK0%2BsxLEx0V9rgtfzM9feXdpsr6DRLI2V8SLw3PtmJ7xN58Z92rPqC37yeaPU7Pa%2FffDq0wSyGKPHXav%2B9OBxVD215HW64k1vSp7ZI6%2F%2Bd34DKO3EcR6BAbMAAAAASUVORK5CYII%3D&labelColor=eceafc&link=https%3A%2F%2Faqora.io%2Fcompetitions%2F{{competition}}%2F) ## {{title}} diff --git a/template/assets/use_case/template/README.md.hbs b/template/assets/use_case/template/README.md.hbs index 146ed58..ddde966 100644 --- a/template/assets/use_case/template/README.md.hbs +++ b/template/assets/use_case/template/README.md.hbs @@ -1,4 +1,4 @@ - + ![Aqora badge](https://img.shields.io/badge/aqora-4328e5?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAIWSURBVHgBjZI%2FbBJxFMe%2F7%2F5x3BE9D2waCeZiEwNxECcbHSwmGt3qZkwcnB2Q0cFAR6d2Mk6KgcHNQSeHwmJaJ06XYmICprEq1PbaUk7ugJ93p1ToYPpb3svv5fve5%2F2%2BP8IRjqqdS4MGiwDTQFx1f2stx4%2BKip7Mi3KsKIVj87Kq7zj2z%2FqoJoajK15IAzTtxVlRiREFIi2ZB6Hg57yoIHEpC0k7lTGL16oRLTk3JFQmGcjkgkA0H4gEBWfSWShyHFIoet%2B%2FEyCbh9GJ2B%2Bhx24JvILU2SwiYhyCA4Q5feb6I3bRskyLGMuN6ZriEAsBqo9z3nhYkY%2FF0Q8BQznUjSQuvBseV9sshNzbArU0zdAAWbOsejOYOmpzY7ZiSJL%2BhMJKQoqeXh%2BqkuOogCuj48h4tlyg5Qncw%2Fz3brKrjoLbfRlTfQWwQ46w6zRmHM7edn5Zd8xi5t9EH8MVVUN0B5aPcvcWmwKHxX2%2Bc7Jtm5ddsaf4K2z9WEXrYznT2axXOVlLGg7JNdYf1BxCI6ynHpReUYvv4aVrfU1wdk8ResDel1V8r5XBBsj7wzie8NyLxgE7Q94nKL6h17B3Pglet%2B2N91hfK0%2BsxLEx0V9rgtfzM9feXdpsr6DRLI2V8SLw3PtmJ7xN58Z92rPqC37yeaPU7Pa%2FffDq0wSyGKPHXav%2B9OBxVD215HW64k1vSp7ZI6%2F%2Bd34DKO3EcR6BAbMAAAAASUVORK5CYII%3D&labelColor=eceafc&link=https%3A%2F%2Faqora.io%2Fcompetitions%2F{{competition}}%2F) diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..11d9e36 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,55 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +# Sphinx documentation +docs/_build/ + +# aqora +.aqora +.venv +__aqora__/ diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..63954ed --- /dev/null +++ b/test/README.md @@ -0,0 +1,19 @@ +![Aqora Score Badge](https://img.shields.io/badge/score-0-4328e5?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAIWSURBVHgBjZI/bBJxFMe//5x3BE9D2waCeZiEwNxECcbHSwmGt3qZkwcnB2Q0cFAR6d2Mk6KgcHNQSeHwmJaJ06XYmICprEq1PbaUk7ugJ93p1ToYPpb3svv5fve5/2+P8IRjqqdS4MGiwDTQFx1f2stx4+Kip7Mi3KsKIVj87Kq7zj2z/qoJoajK15IAzTtxVlRiREFIi2ZB6Hg57yoIHEpC0k7lTGL16oRLTk3JFQmGcjkgkA0H4gEBWfSWShyHFIoet+/EyCbh9GJ2B+hx24JvILU2SwiYhyCA4Q5feb6I3bRskyLGMuN6ZriEAsBqo9z3nhYkY/F0Q8BQznUjSQuvBseV9sshNzbArU0zdAAWbOsejOYOmpzY7ZiSJL+hMJKQoqeXh+qkuOogCuj48h4tlyg5Qncw/z3brKrjoLbfRlTfQWwQ46w6zRmHM7edn5Zd8xi5t9EH8MVVUN0B5aPcvcWmwKHxX2+c7Jtm5ddsaf4K2z9WEXrYznT2axXOVlLGg7JNdYf1BxCI6ynHpReUYvv4aVrfU1wdk8ResDel1V8r5XBBsj7wzie8NyLxgE7Q94nKL6h17B3Pglet+2N91hfK0+sxLEx0V9rgtfzM9feXdpsr6DRLI2V8SLw3PtmJ7xN58Z92rPqC37yeaPU7Pa/ffDq0wSyGKPHXav+9OBxVD215HW64k1vSp7ZI6/+d34DKO3EcR6BAbMAAAAASUVORK5CYII=&labelColor=eceafc&link=http://localhost:8080/competitions/test) +rr +![Aqora badge](https://img.shields.io/badge/aqora-4328e5?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAIWSURBVHgBjZI%2FbBJxFMe%2F7%2F5x3BE9D2waCeZiEwNxECcbHSwmGt3qZkwcnB2Q0cFAR6d2Mk6KgcHNQSeHwmJaJ06XYmICprEq1PbaUk7ugJ93p1ToYPpb3svv5fve5%2F2%2BP8IRjqqdS4MGiwDTQFx1f2stx4%2BKip7Mi3KsKIVj87Kq7zj2z%2FqoJoajK15IAzTtxVlRiREFIi2ZB6Hg57yoIHEpC0k7lTGL16oRLTk3JFQmGcjkgkA0H4gEBWfSWShyHFIoet%2B%2FEyCbh9GJ2B%2Bhx24JvILU2SwiYhyCA4Q5feb6I3bRskyLGMuN6ZriEAsBqo9z3nhYkY%2FF0Q8BQznUjSQuvBseV9sshNzbArU0zdAAWbOsejOYOmpzY7ZiSJL%2BhMJKQoqeXh%2BqkuOogCuj48h4tlyg5Qncw%2Fz3brKrjoLbfRlTfQWwQ46w6zRmHM7edn5Zd8xi5t9EH8MVVUN0B5aPcvcWmwKHxX2%2Bc7Jtm5ddsaf4K2z9WEXrYznT2axXOVlLGg7JNdYf1BxCI6ynHpReUYvv4aVrfU1wdk8ResDel1V8r5XBBsj7wzie8NyLxgE7Q94nKL6h17B3Pglet%2B2N91hfK0%2BsxLEx0V9rgtfzM9feXdpsr6DRLI2V8SLw3PtmJ7xN58Z92rPqC37yeaPU7Pa%2FffDq0wSyGKPHXav%2B9OBxVD215HW64k1vSp7ZI6%2F%2Bd34DKO3EcR6BAbMAAAAASUVORK5CYII%3D&labelColor=eceafc&link=https%3A%2F%2Faqora.io%2Fcompetitions%2Ftest%2F) + +# test Submission + +You can find a template notebook in `submission/solution.ipynb`. +Fill in your solution. You can run the notebook locally to test your +solution by running the following in the terminal + +```bash +aqora test +``` + +And when you are ready to submit run + +```bash +aqora upload +``` diff --git a/test/pyproject.toml b/test/pyproject.toml new file mode 100644 index 0000000..39b3eb0 --- /dev/null +++ b/test/pyproject.toml @@ -0,0 +1,28 @@ +[project] +name = "submission" +version = "0.1.3" +requires-python = ">=3.8" +readme = "README.md" + +# You can add dependencies with `aqora add` +dependencies = [] + +[build-system] +requires = [ + "uv>=0.4.20", + "setuptools>=75", + "wheel>=0.44", +] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["."] +[tool.setuptools.package-data] +submission = ["*"] + +[tool.aqora] +type = "submission" +competition = "test" + +[tool.aqora.refs] +solution = { path = "submission.solution", notebook = true } diff --git a/test/submission/solution.ipynb b/test/submission/solution.ipynb new file mode 100644 index 0000000..f8a1b66 --- /dev/null +++ b/test/submission/solution.ipynb @@ -0,0 +1,60 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# test Submission\n", + "\n", + "Add your solution below!\n", + "\n", + "To run the notebook in VS Code, you can click on the `Run All` button in the toolbar. You may need to select the kernel to run. When prompted select `Python Environments` and then `.venv` (Should be starred).\n", + "\n", + "> Note: You can add dependencies to your virtual environment by running `aqora add ` or editing the `pyproject.toml` at the root of the project\n", + "\n", + "Once you're ready, you can open a terminal by clicking `Terminal` and then `New Terminal` in the context bar at the very top. To test your solution, run\n", + "\n", + "```bash\n", + "aqora test\n", + "```\n", + "\n", + "Finally, once all the tests are passing, to submit to **Aqora** run\n", + "\n", + "```bash\n", + "aqora upload\n", + "```\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "parameters" + ] + }, + "outputs": [], + "source": [ + "# Example input. Parameters will be injected here by aqora\n", + "input = None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set the output variable to submit the result of your algorithm\n", + "output = None" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}