diff --git a/.env.sample b/.env.sample index dc3b2fe..acb8230 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1 @@ -# set API TOKEN so that the cli is authorized to use the public api -CS_TOKEN= -TEAM_ID= \ No newline at end of file +export TEAM_ID=$(cs list workspaces | grep $WORKSPACE_ID | awk '{print $2}') \ No newline at end of file diff --git a/.github/workflows/backend-app.yml b/.github/workflows/backend-app.yml index 73ebfe0..50c6a6c 100644 --- a/.github/workflows/backend-app.yml +++ b/.github/workflows/backend-app.yml @@ -1,10 +1,10 @@ -name: Python server tests +name: Python backend server tests on: pull_request: types: [opened, synchronize, reopened, ready_for_review, draft] paths: - - 'server/**' + - 'backend/**' - '.github/workflows/backend-app.yml' permissions: @@ -15,9 +15,6 @@ jobs: pytest: runs-on: ubuntu-latest - env: - UV_CACHE_DIR: ${{ github.workspace }}/backend/.uv_cache - permissions: contents: read pull-requests: write @@ -29,18 +26,15 @@ jobs: - name: Install uv package manager uses: astral-sh/setup-uv@v6 with: - enable-cache: true - cache-dependency-glob: backend/uv.lock activate-environment: true working-directory: backend - cache-local-path: ${{ github.workspace }}/backend/.uv_cache - name: Install dependencies using uv run: | - cd server && uv sync + cd backend && uv sync shell: bash - - name: Run Bandit security check on server code + - name: Run Bandit security check on backend code id: bandit_check run: | echo "Running Bandit security check..." @@ -61,7 +55,7 @@ jobs: COMMENT_BODY_FILE="bandit-comment-body.md" echo "COMMENT_BODY_FILE=${COMMENT_BODY_FILE}" >> $GITHUB_ENV - echo "### 🛡️ Bandit Security Scan Results" > $COMMENT_BODY_FILE + echo "### 🛡️ Bandit Backend Security Scan Results" > $COMMENT_BODY_FILE echo "" >> $COMMENT_BODY_FILE echo "" >> $COMMENT_BODY_FILE echo "" >> $COMMENT_BODY_FILE @@ -81,7 +75,7 @@ jobs: with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' - body-includes: Bandit Security Scan Results + body-includes: Bandit Backend Security Scan Results - name: Post Bandit results as PR comment if: github.event_name == 'pull_request' && always() @@ -104,11 +98,12 @@ jobs: if: github.event_name == 'pull_request' && always() uses: MishaKav/pytest-coverage-comment@main with: + unique-id-for-comment: backend-coverage-report pytest-xml-coverage-path: backend/coverage.xml pytest-coverage-path: backend/pytest-coverage.txt junitxml-path: backend/junit/test-results.xml - title: Pytest Coverage Report - junitxml-title: Test Execution Summary + title: Pytest Coverage Report - Backend + junitxml-title: Test Execution Summary - Backend - name: Minimize uv cache run: uv cache prune --ci diff --git a/.github/workflows/frontend-app.yml b/.github/workflows/frontend-app.yml index 983ce38..c3e5f3f 100644 --- a/.github/workflows/frontend-app.yml +++ b/.github/workflows/frontend-app.yml @@ -1,11 +1,11 @@ -name: Python server tests +name: Python frontend server tests on: pull_request: types: [opened, synchronize, reopened, ready_for_review, draft] paths: - - 'server/**' - - '.github/workflows/python-app.yml' + - 'frontend/**' + - '.github/workflows/frontend-app.yml' permissions: contents: write @@ -15,9 +15,6 @@ jobs: pytest: runs-on: ubuntu-latest - env: - UV_CACHE_DIR: ${{ github.workspace }}/frontend/.uv_cache - permissions: contents: read pull-requests: write @@ -29,15 +26,12 @@ jobs: - name: Install uv package manager uses: astral-sh/setup-uv@v6 with: - enable-cache: true - cache-dependency-glob: frontend/uv.lock activate-environment: true working-directory: frontend - cache-local-path: ${{ github.workspace }}/frontend/.uv_cache - name: Install dependencies using uv run: | - cd server && uv sync + cd frontend && uv sync shell: bash - name: Run Bandit security check on server code @@ -61,7 +55,7 @@ jobs: COMMENT_BODY_FILE="bandit-comment-body.md" echo "COMMENT_BODY_FILE=${COMMENT_BODY_FILE}" >> $GITHUB_ENV - echo "### 🛡️ Bandit Security Scan Results" > $COMMENT_BODY_FILE + echo "### 🛡️ Bandit Frontend Security Scan Results" > $COMMENT_BODY_FILE echo "" >> $COMMENT_BODY_FILE echo "" >> $COMMENT_BODY_FILE echo "" >> $COMMENT_BODY_FILE @@ -81,7 +75,7 @@ jobs: with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' - body-includes: Bandit Security Scan Results + body-includes: Bandit Frontend Security Scan Results - name: Post Bandit results as PR comment if: github.event_name == 'pull_request' && always() @@ -104,11 +98,12 @@ jobs: if: github.event_name == 'pull_request' && always() uses: MishaKav/pytest-coverage-comment@main with: - pytest-xml-coverage-path: server/coverage.xml - pytest-coverage-path: server/pytest-coverage.txt - junitxml-path: server/junit/test-results.xml - title: Pytest Coverage Report - junitxml-title: Test Execution Summary + unique-id-for-comment: frontend-coverage-report + pytest-xml-coverage-path: frontend/coverage.xml + pytest-coverage-path: frontend/pytest-coverage.txt + junitxml-path: frontend/junit/test-results.xml + title: Pytest Coverage Report - Frontend + junitxml-title: Test Execution Summary - Frontend - name: Minimize uv cache run: uv cache prune --ci diff --git a/.github/workflows/preview-deployment.yml b/.github/workflows/preview-deployment.yml index 0d539ad..c955972 100644 --- a/.github/workflows/preview-deployment.yml +++ b/.github/workflows/preview-deployment.yml @@ -12,10 +12,6 @@ permissions: pull-requests: read deployments: write -env: - ON_DEMAND='true' - WORKSPACE_PLAN='Boost' - jobs: deploy: concurrency: codesphere @@ -32,5 +28,8 @@ jobs: email: ${{ secrets.CODESPHERE_EMAIL }} password: ${{ secrets.CODESPHERE_PASSWORD }} team: ${{ secrets.CODESPHERE_TEAM }} - plan: $WORKSPACE_PLAN - onDemand: $ON_DEMAND + plan: 'Boost' + onDemand: 'true' + env: | + TEAM_ID=${{ secrets.CODESPHERE_TEAM_ID }} + CS_TOKEN=${{ secrets.CODESPHERE_TOKEN }} diff --git a/.gitignore b/.gitignore index 34e3c93..6c76b6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ .env +env +env.fish .github/* + +uv +uvx \ No newline at end of file diff --git a/README.md b/README.md index 984e2f9..67f1d82 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -# python-demo -# python-demo +# python-demo test diff --git a/backend/.gitignore b/backend/.gitignore index f77e0d5..0de087e 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -3,4 +3,7 @@ coverage.xml pytest-coverage.txt .coverage -__pycache__/* \ No newline at end of file +__pycache__/* +tests/__pycache__/* + +bandit-results.txt \ No newline at end of file diff --git a/backend/pyproject.toml b/backend/pyproject.toml index a5fac2c..b8ab3f4 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "numpy>=2.3.0", "pandas>=2.3.0", "pytest-cov>=6.1.1", + "pytest-profiling>=1.8.1", ] [dependency-groups] diff --git a/backend/tests/__pycache__/test_app.cpython-312-pytest-8.4.0.pyc b/backend/tests/__pycache__/test_app.cpython-312-pytest-8.4.0.pyc deleted file mode 100644 index 77488fd..0000000 Binary files a/backend/tests/__pycache__/test_app.cpython-312-pytest-8.4.0.pyc and /dev/null differ diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/uv.lock b/backend/uv.lock index a622367..7a3940b 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -34,6 +34,7 @@ dependencies = [ { name = "numpy" }, { name = "pandas" }, { name = "pytest-cov" }, + { name = "pytest-profiling" }, ] [package.dev-dependencies] @@ -48,6 +49,7 @@ requires-dist = [ { name = "numpy", specifier = ">=2.3.0" }, { name = "pandas", specifier = ">=2.3.0" }, { name = "pytest-cov", specifier = ">=6.1.1" }, + { name = "pytest-profiling", specifier = ">=1.8.1" }, ] [package.metadata.requires-dev] @@ -214,6 +216,15 @@ standard = [ { name = "uvicorn", extra = ["standard"] }, ] +[[package]] +name = "gprof2dot" +version = "2025.4.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/fd/cad13fa1f7a463a607176432c4affa33ea162f02f58cc36de1d40d3e6b48/gprof2dot-2025.4.14.tar.gz", hash = "sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce", size = 39536, upload-time = "2025-04-14T07:21:45.76Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/ed/89d760cb25279109b89eb52975a7b5479700d3114a2421ce735bfb2e7513/gprof2dot-2025.4.14-py3-none-any.whl", hash = "sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e", size = 37555, upload-time = "2025-04-14T07:21:43.319Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -636,6 +647,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, ] +[[package]] +name = "pytest-profiling" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gprof2dot" }, + { name = "pytest" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/74/806cafd6f2108d37979ec71e73b2ff7f7db88eabd19d3b79c5d6cc229c36/pytest-profiling-1.8.1.tar.gz", hash = "sha256:3f171fa69d5c82fa9aab76d66abd5f59da69135c37d6ae5bf7557f1b154cb08d", size = 33135, upload-time = "2024-11-29T19:34:13.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/ac/c428c66241a144617a8af7a28e2e055e1438d23b949b62ac4b401a69fb79/pytest_profiling-1.8.1-py3-none-any.whl", hash = "sha256:3dd8713a96298b42d83de8f5951df3ada3e61b3e5d2a06956684175529e17aea", size = 9929, upload-time = "2024-11-29T19:33:02.111Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" diff --git a/ci.yml b/ci.yml index 0c5dac3..5b96d5e 100644 --- a/ci.yml +++ b/ci.yml @@ -1,43 +1,46 @@ schemaVersion: v0.2 prepare: steps: - - name: install nix dependencies - command: nix-env -iA nixpkgs.uv - name: install Codesphere cli tool command: mkdir -p $HOME/.local/bin && wget -qO- 'https://api.github.com/repos/codesphere-cloud/cs-go/releases/latest' | grep linux_amd64 | grep browser_download_url | sed s/.*https/https/ | sed s/\".*$// | xargs wget -O $HOME/.local/bin/cs && chmod +x $HOME/.local/bin/cs + - name: install uv standalone version + command: curl -LsSf https://astral.sh/uv/install.sh | env + UV_INSTALL_DIR="$HOME/app" sh && chmod +x $HOME/app/uv - name: copy template .env file command: cp -n .env.sample .env - name: "set ev vars " - command: . .env && export $CS_TOKEN && cs set-env -w $CODESPHERE_APP_ID -t - $TEAM_ID --env-var UV_CACHE_DIR=$PWD/.codesphere-internal/.uv --env-var + command: . .env && cs set-env -w $WORKSPACE_ID -t $TEAM_ID --env-var + UV_PYTHON_INSTALL_DIR=$PWD/.codesphere-internal/.uv-python-install + --env-var UV_PYTHON_BIN_DIR=$PWD/.codesphere-internal/.uv-python-bin + --env-var UV_CACHE_DIR=$PWD/.codesphere-internal/.uv --env-var UV_PYTHON_CACHE_DIR=$PWD/.codesphere-internal/.uv-python - name: install frontend environment - command: cd frontend && uv sync + command: . ./env && cd frontend && { [ ! -d ".venv" ] && uv venv; uv sync; } - name: install backend environment - command: "cd backend && uv sync " + command: . ./env && cd backend && { [ ! -d ".venv" ] && uv venv; uv sync; } test: steps: - name: test frontend - command: cd frontend && uv run pytest --junitxml=junit/test-results.xml - --cov-report=xml --cov-report=html --cov=. | tee pytest-coverage.txt + command: . ./env && cd frontend && uv run pytest - name: test backend - command: cd backend && uv run pytest --junitxml=junit/test-results.xml - --cov-report=xml --cov-report=html --cov=. | tee pytest-coverage.txt + command: ". ./env && cd backend && uv run pytest " - name: security check frontend - command: 'bandit -r . -c pyproject.toml --format=custom --msg-template - "{abspath}:{line}: {test_id}[{severity}]: {msg}" -o bandit-results.txt' + command: '. ./env && cd frontend && uv run bandit -r . -c pyproject.toml + --format=custom --msg-template "{abspath}:{line}: {test_id}[{severity}]: + {msg}" -o bandit-results.txt' - name: security check backend - command: 'bandit -r . -c pyproject.toml --format=custom --msg-template - "{abspath}:{line}: {test_id}[{severity}]: {msg}" -o bandit-results.txt' + command: '. ./env && cd backend && uv run bandit -r . -c pyproject.toml + --format=custom --msg-template "{abspath}:{line}: {test_id}[{severity}]: + {msg}" -o bandit-results.txt' run: frontend: steps: - name: start frontend application - command: cd frontend && uv run streamlit run --server.address 0.0.0.0 + command: . ./env && cd frontend && uv run streamlit run --server.address 0.0.0.0 --server.port 3000 main.py plan: 8 replicas: 1 @@ -48,15 +51,16 @@ run: backend: steps: - name: start backend application - command: cd backend && uv run uvicorn app:app --reload --port 3000 --host - 0.0.0.0 + command: . ./env && cd backend && uv run uvicorn app:app --reload --port 3000 + --host 0.0.0.0 plan: 8 replicas: 1 isPublic: false api-docs: steps: - name: deploy docs - command: cd backend && uv run uvicorn doc:app_docs --host 0.0.0.0 --port 3000 + command: . ./env && cd backend && uv run uvicorn doc:app_docs --host 0.0.0.0 + --port 3000 plan: 8 replicas: 1 isPublic: true diff --git a/frontend/.gitignore b/frontend/.gitignore index f77e0d5..0de087e 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -3,4 +3,7 @@ coverage.xml pytest-coverage.txt .coverage -__pycache__/* \ No newline at end of file +__pycache__/* +tests/__pycache__/* + +bandit-results.txt \ No newline at end of file diff --git a/frontend/main.py b/frontend/main.py index d3fce37..fdceefc 100644 --- a/frontend/main.py +++ b/frontend/main.py @@ -31,7 +31,7 @@ @st.cache_data def fetch_data(url: str, points: int): try: - response = requests.get(url, params={"points": points}) + response = requests.get(url, params={"points": points}, timeout=3) response.raise_for_status() data = response.json() diff --git a/frontend/pyproject.toml b/frontend/pyproject.toml index ddcfb0b..4794001 100644 --- a/frontend/pyproject.toml +++ b/frontend/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "pandas>=2.3.0", "plotly>=6.1.2", "pytest-cov>=6.1.1", + "pytest-profiling>=1.8.1", "requests>=2.32.4", "streamlit>=1.45.1", ] diff --git a/frontend/tests/__pycache__/conftest.cpython-310-pytest-8.4.0.pyc b/frontend/tests/__pycache__/conftest.cpython-310-pytest-8.4.0.pyc deleted file mode 100644 index 606eb9f..0000000 Binary files a/frontend/tests/__pycache__/conftest.cpython-310-pytest-8.4.0.pyc and /dev/null differ diff --git a/frontend/tests/__pycache__/test_main.cpython-310-pytest-8.4.0.pyc b/frontend/tests/__pycache__/test_main.cpython-310-pytest-8.4.0.pyc deleted file mode 100644 index 0f007d7..0000000 Binary files a/frontend/tests/__pycache__/test_main.cpython-310-pytest-8.4.0.pyc and /dev/null differ diff --git a/frontend/tests/conftest.py b/frontend/tests/conftest.py deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/uv.lock b/frontend/uv.lock index 95ecd49..aca464b 100644 --- a/frontend/uv.lock +++ b/frontend/uv.lock @@ -247,6 +247,7 @@ dependencies = [ { name = "pandas" }, { name = "plotly" }, { name = "pytest-cov" }, + { name = "pytest-profiling" }, { name = "requests" }, { name = "streamlit" }, ] @@ -263,6 +264,7 @@ requires-dist = [ { name = "pandas", specifier = ">=2.3.0" }, { name = "plotly", specifier = ">=6.1.2" }, { name = "pytest-cov", specifier = ">=6.1.1" }, + { name = "pytest-profiling", specifier = ">=1.8.1" }, { name = "requests", specifier = ">=2.32.4" }, { name = "streamlit", specifier = ">=1.45.1" }, ] @@ -297,6 +299,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" }, ] +[[package]] +name = "gprof2dot" +version = "2025.4.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/fd/cad13fa1f7a463a607176432c4affa33ea162f02f58cc36de1d40d3e6b48/gprof2dot-2025.4.14.tar.gz", hash = "sha256:35743e2d2ca027bf48fa7cba37021aaf4a27beeae1ae8e05a50b55f1f921a6ce", size = 39536, upload-time = "2025-04-14T07:21:45.76Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/ed/89d760cb25279109b89eb52975a7b5479700d3114a2421ce735bfb2e7513/gprof2dot-2025.4.14-py3-none-any.whl", hash = "sha256:0742e4c0b4409a5e8777e739388a11e1ed3750be86895655312ea7c20bd0090e", size = 37555, upload-time = "2025-04-14T07:21:43.319Z" }, +] + [[package]] name = "idna" version = "3.10" @@ -859,6 +870,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, ] +[[package]] +name = "pytest-profiling" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gprof2dot" }, + { name = "pytest" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/74/806cafd6f2108d37979ec71e73b2ff7f7db88eabd19d3b79c5d6cc229c36/pytest-profiling-1.8.1.tar.gz", hash = "sha256:3f171fa69d5c82fa9aab76d66abd5f59da69135c37d6ae5bf7557f1b154cb08d", size = 33135, upload-time = "2024-11-29T19:34:13.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/ac/c428c66241a144617a8af7a28e2e055e1438d23b949b62ac4b401a69fb79/pytest_profiling-1.8.1-py3-none-any.whl", hash = "sha256:3dd8713a96298b42d83de8f5951df3ada3e61b3e5d2a06956684175529e17aea", size = 9929, upload-time = "2024-11-29T19:33:02.111Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0"