diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index af6666d9..5a12f276 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,68 +1,50 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.231.6/containers/python-3 +// For format details, see https://containers.dev/implementors/json_reference/ { - "name": "Python 3", + "name": "Python 3 Developer Container", "build": { - "dockerfile": "Dockerfile", - "target": "developer", - "context": "..", - "args": {} + "dockerfile": "../Dockerfile", + "target": "build", + // Only upgrade pip, we will install the project below + "args": { + "PIP_OPTIONS": "--upgrade pip" + } }, "remoteEnv": { "DISPLAY": "${localEnv:DISPLAY}" }, - // Set *default* container specific settings.json values on container create. - "settings": { - "python.defaultInterpreterPath": "/usr/local/bin/python", - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", - "python.formatting.blackPath": "/usr/local/py-utils/bin/black", - "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", - "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", - "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", - "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", - "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", - "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", - "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" - }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "streetsidesoftware.code-spell-checker", - "ryanluker.vscode-coverage-gutters", - "mhutchie.git-graph", - "eamodio.gitlens", - "gruntfuggly.todo-tree", - "redhat.vscode-yaml", - "nsd.vscode-epics", - "alefragnani.bookmarks" - ], + // Add the URLs of features you want added when the container is built. "features": { - //"docker-from-docker": "20.10", - "git": "os-provided" + "ghcr.io/devcontainers/features/common-utils:1": { + "username": "none", + "upgradePackages": false + } + }, + "customizations": { + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "tamasfe.even-better-toml", + "redhat.vscode-yaml", + "ryanluker.vscode-coverage-gutters" + ] + } }, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - // "remoteUser": "vscode", // Make sure the files we are mapping into the container exist on the host - "initializeCommand": "bash -c 'for i in $HOME/.inputrc $HOME/.bashrc_dev; do [ -f $i ] || touch $i; done'", + "initializeCommand": "bash -c 'for i in $HOME/.inputrc; do [ -f $i ] || touch $i; done'", "runArgs": [ - "--privileged", "--net=host", - "-v=${localEnv:HOME}/.ssh:/root/.ssh", - "-v=${localEnv:HOME}/.bashrc_dev:/root/.bashrc", - "-v=${localEnv:HOME}/.inputrc:/root/.inputrc" + "--security-opt=label=type:container_runtime_t" ], "mounts": [ - // map in home directory - not strictly necessary but may be useful + "source=${localEnv:HOME}/.ssh,target=/root/.ssh,type=bind", + "source=${localEnv:HOME}/.inputrc,target=/root/.inputrc,type=bind", + // map in home directory - not strictly necessary but useful "source=${localEnv:HOME},target=${localEnv:HOME},type=bind,consistency=cached" ], - "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind", - "workspaceFolder": "/workspace", + // make the workspace folder the same inside and outside of the container + "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", + "workspaceFolder": "${localWorkspaceFolder}", // After the container is created, install the python project in editable form - // This installs into the system python of the container - "postCreateCommand": "pip install $([ -f requirements_dev.txt ] && echo -r requirements_dev.txt ) -e .[dev]" + "postCreateCommand": "pip install -e '.[dev]'" } \ No newline at end of file diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst new file mode 100644 index 00000000..a3f9dd1d --- /dev/null +++ b/.github/CONTRIBUTING.rst @@ -0,0 +1,35 @@ +Contributing to the project +=========================== + +Contributions and issues are most welcome! All issues and pull requests are +handled through GitHub_. Also, please check for any existing issues before +filing a new one. If you have a great idea but it involves big changes, please +file a ticket before making a pull request! We want to make sure you don't spend +your time coding something that might not fit the scope of the project. + +.. _GitHub: https://github.com/gilesknap/gphotos-sync/issues + +Issue or Discussion? +-------------------- + +Github also offers discussions_ as a place to ask questions and share ideas. If +your issue is open ended and it is not obvious when it can be "closed", please +raise it as a discussion instead. + +.. _discussions: https://github.com/gilesknap/gphotos-sync/discussions + +Code coverage +------------- + +While 100% code coverage does not make a library bug-free, it significantly +reduces the number of easily caught bugs! Please make sure coverage remains the +same or is improved by a pull request! + +Developer guide +--------------- + +The `Developer Guide`_ contains information on setting up a development +environment, running the tests and what standards the code and documentation +should follow. + +.. _Developer Guide: https://diamondlightsource.github.io/gphotos-sync/main/developer/how-to/contribute.html diff --git a/.github/actions/install_requirements/action.yml b/.github/actions/install_requirements/action.yml new file mode 100644 index 00000000..20d7a3ad --- /dev/null +++ b/.github/actions/install_requirements/action.yml @@ -0,0 +1,57 @@ +name: Install requirements +description: Run pip install with requirements and upload resulting requirements +inputs: + requirements_file: + description: Name of requirements file to use and upload + required: true + install_options: + description: Parameters to pass to pip install + required: true + python_version: + description: Python version to install + default: "3.x" + +runs: + using: composite + + steps: + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.python_version }} + + - name: Pip install + run: | + touch ${{ inputs.requirements_file }} + # -c uses requirements.txt as constraints, see 'Validate requirements file' + pip install -c ${{ inputs.requirements_file }} ${{ inputs.install_options }} + shell: bash + + - name: Create lockfile + run: | + mkdir -p lockfiles + pip freeze --exclude-editable > lockfiles/${{ inputs.requirements_file }} + # delete the self referencing line and make sure it isn't blank + sed -i'' -e '/file:/d' lockfiles/${{ inputs.requirements_file }} + shell: bash + + - name: Upload lockfiles + uses: actions/upload-artifact@v3 + with: + name: lockfiles + path: lockfiles + + # This eliminates the class of problems where the requirements being given no + # longer match what the packages themselves dictate. E.g. In the rare instance + # where I install some-package which used to depend on vulnerable-dependency + # but now uses good-dependency (despite being nominally the same version) + # pip will install both if given a requirements file with -r + - name: If requirements file exists, check it matches pip installed packages + run: | + if [ -s ${{ inputs.requirements_file }} ]; then + if ! diff -u ${{ inputs.requirements_file }} lockfiles/${{ inputs.requirements_file }}; then + echo "Error: ${{ inputs.requirements_file }} need the above changes to be exhaustive" + exit 1 + fi + fi + shell: bash diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..fb7c6ee6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/pages/index.html b/.github/pages/index.html index bd856272..c495f39f 100644 --- a/.github/pages/index.html +++ b/.github/pages/index.html @@ -2,10 +2,10 @@ - Redirecting to main branch - - - + Redirecting to main branch + + + - \ No newline at end of file + diff --git a/.github/pages/make_switcher.py b/.github/pages/make_switcher.py new file mode 100755 index 00000000..ae227ab7 --- /dev/null +++ b/.github/pages/make_switcher.py @@ -0,0 +1,99 @@ +import json +import logging +from argparse import ArgumentParser +from pathlib import Path +from subprocess import CalledProcessError, check_output +from typing import List, Optional + + +def report_output(stdout: bytes, label: str) -> List[str]: + ret = stdout.decode().strip().split("\n") + print(f"{label}: {ret}") + return ret + + +def get_branch_contents(ref: str) -> List[str]: + """Get the list of directories in a branch.""" + stdout = check_output(["git", "ls-tree", "-d", "--name-only", ref]) + return report_output(stdout, "Branch contents") + + +def get_sorted_tags_list() -> List[str]: + """Get a list of sorted tags in descending order from the repository.""" + stdout = check_output(["git", "tag", "-l", "--sort=-v:refname"]) + return report_output(stdout, "Tags list") + + +def get_versions(ref: str, add: Optional[str], remove: Optional[str]) -> List[str]: + """Generate the file containing the list of all GitHub Pages builds.""" + # Get the directories (i.e. builds) from the GitHub Pages branch + try: + builds = set(get_branch_contents(ref)) + except CalledProcessError: + builds = set() + logging.warning(f"Cannot get {ref} contents") + + # Add and remove from the list of builds + if add: + builds.add(add) + if remove: + assert remove in builds, f"Build '{remove}' not in {sorted(builds)}" + builds.remove(remove) + + # Get a sorted list of tags + tags = get_sorted_tags_list() + + # Make the sorted versions list from main branches and tags + versions: List[str] = [] + for version in ["master", "main"] + tags: + if version in builds: + versions.append(version) + builds.remove(version) + + # Add in anything that is left to the bottom + versions += sorted(builds) + print(f"Sorted versions: {versions}") + return versions + + +def write_json(path: Path, repository: str, versions: str): + org, repo_name = repository.split("/") + struct = [ + {"version": version, "url": f"https://{org}.github.io/{repo_name}/{version}/"} + for version in versions + ] + text = json.dumps(struct, indent=2) + print(f"JSON switcher:\n{text}") + path.write_text(text, encoding="utf-8") + + +def main(args=None): + parser = ArgumentParser( + description="Make a versions.txt file from gh-pages directories" + ) + parser.add_argument( + "--add", + help="Add this directory to the list of existing directories", + ) + parser.add_argument( + "--remove", + help="Remove this directory from the list of existing directories", + ) + parser.add_argument( + "repository", + help="The GitHub org and repository name: ORG/REPO", + ) + parser.add_argument( + "output", + type=Path, + help="Path of write switcher.json to", + ) + args = parser.parse_args(args) + + # Write the versions file + versions = get_versions("origin/gh-pages", args.add, args.remove) + write_json(args.output, args.repository, versions) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml index c427bdb5..e6470a1b 100644 --- a/.github/workflows/code.yml +++ b/.github/workflows/code.yml @@ -3,224 +3,229 @@ name: Code CI on: push: pull_request: +env: + # The target python version, which must match the Dockerfile version + CONTAINER_PYTHON: "3.11" jobs: lint: # pull requests are a duplicate of a branch push if within the same repo. if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository - runs-on: "ubuntu-latest" - steps: - - name: checkout - uses: actions/checkout@v2 - - - name: lint - run: | - pip install --upgrade pip - pip install --user .[dev] - tox -e pre-commit,mypy - - wheel: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest"] - python: ["3.8"] + runs-on: ubuntu-latest - runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 - - name: Create Sdist and Wheel - # Set SOURCE_DATE_EPOCH from git commit for reproducible build - # https://reproducible-builds.org/ - run: | - SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) pipx run build --sdist --wheel - - - name: Upload Wheel and Sdist as artifacts - uses: actions/upload-artifact@v2 - with: - name: dist - path: dist - - - name: Install minimum python version - uses: actions/setup-python@v2 + - name: Install python packages + uses: ./.github/actions/install_requirements with: - python-version: ${{ matrix.python }} + python_version: "3.12" + requirements_file: requirements-dev-3.x.txt + install_options: -e .[dev] - - name: Install wheel in a venv and check cli works - # ${GITHUB_REPOSITORY##*/} is the repo name without org - # Replace this with the cli command if different to the repo name - run: pipx run --python $(which python${{ matrix.python }}) --spec dist/*.whl ${GITHUB_REPOSITORY##*/} --version + - name: Lint + run: tox -e pre-commit,mypy test: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository strategy: fail-fast: false matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] - # keep matrix tight for now to avoid HTTP 429 errors - # TODO: restore macos and 3.9 and 3.10 - python: ["3.8", "3.9"] - lock: [false] - + os: ["ubuntu-latest"] # can add windows-latest, macos-latest + python: ["3.9", "3.10", "3.11", "3.12"] + install: ["-e .[dev]"] + # Make one version be non-editable to test both paths of version code include: - # Add an extra Python3.10 runner to use the lockfile - os: "ubuntu-latest" - python: "3.10" - lock: true + python: "3.8" + install: ".[dev]" runs-on: ${{ matrix.os }} env: # https://github.com/pytest-dev/pytest/issues/2042 PY_IGNORE_IMPORTMISMATCH: "1" - # enable QT tests with no X Display - QT_QPA_PLATFORM: "offscreen" steps: - - name: checkout - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 with: + # Need this to get version number from last tag fetch-depth: 0 - - name: setup python ${{ matrix.python }} - uses: actions/setup-python@v2 + - name: Install python packages + uses: ./.github/actions/install_requirements with: - python-version: ${{ matrix.python }} + python_version: ${{ matrix.python }} + requirements_file: requirements-test-${{ matrix.os }}-${{ matrix.python }}.txt + install_options: ${{ matrix.install }} - - name: install with locked dependencies - if: matrix.lock - run: | - touch requirements.txt requirements_dev.txt - pip install -r requirements.txt -e . - pip freeze --exclude-editable > requirements.txt - pip install -r requirements_dev.txt -e .[dev] - pip freeze --exclude-editable > requirements_dev.txt + - name: List dependency tree + run: pipdeptree - - name: install with latest dependencies - if: ${{ ! matrix.lock }} - run: pip install -e .[dev] - - - name: run tests - run: pytest tests - - - name: create requirements_dev.txt - run: | - pip freeze --exclude-editable > requirements_dev.txt + - name: Run tests + run: tox -e pytest - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: - name: ${{ matrix.python }}/${{ matrix.os }}/${{ matrix.lock }} + name: ${{ matrix.python }}/${{ matrix.os }} files: cov.xml - - name: Upload lockfiles - if: matrix.lock - uses: actions/upload-artifact@v2 - with: - name: lockfiles - path: | - requirements.txt - requirements_dev.txt + dist: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository + runs-on: "ubuntu-latest" - release: - needs: [lint, wheel, test] - runs-on: ubuntu-latest - # upload to PyPI and make a release on every tag - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') steps: - - uses: actions/download-artifact@v2 + - name: Checkout + uses: actions/checkout@v4 with: - path: artifacts + # Need this to get version number from last tag + fetch-depth: 0 - - name: Github Release - # We pin to the SHA, not the tag, for security reasons. - # https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions - uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 # v0.1.14 + - name: Build sdist and wheel + run: | + export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) && \ + pipx run build + + - name: Upload sdist and wheel as artifacts + uses: actions/upload-artifact@v3 with: - files: | - artifacts/dist/* - artifacts/lockfiles/* - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: dist + path: dist - - name: Publish to PyPI - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.pypi_token }} - run: pipx run twine upload artifacts/dist/* + - name: Check for packaging errors + run: pipx run twine check --strict dist/* + + - name: Install python packages + uses: ./.github/actions/install_requirements + with: + python_version: ${{env.CONTAINER_PYTHON}} + requirements_file: requirements.txt + install_options: dist/*.whl - make-container: - needs: [lint, wheel, test] + - name: Test module --version works using the installed wheel + # If more than one module in src/ replace with module name to test + run: python -m $(ls src | head -1) --version + + container: + needs: [lint, dist, test] runs-on: ubuntu-latest + permissions: contents: read packages: write - steps: - - name: checkout - uses: actions/checkout@v2 + env: + TEST_TAG: "testing" - - uses: actions/download-artifact@v2 - with: - name: dist - path: dist + steps: + - name: Checkout + uses: actions/checkout@v4 - - name: Cache Docker layers - uses: actions/cache@v2 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- + # image names must be all lower case + - name: Generate image repo name + run: echo IMAGE_REPOSITORY=ghcr.io/$(tr '[:upper:]' '[:lower:]' <<< "${{ github.repository }}") >> $GITHUB_ENV - - name: Login to Docker Hub - if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + - name: Download wheel and lockfiles + uses: actions/download-artifact@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + path: artifacts/ - name: Log in to GitHub Docker Registry if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker meta + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and export to Docker local cache + uses: docker/build-push-action@v5 + with: + # Note build-args, context, file, and target must all match between this + # step and the later build-push-action, otherwise the second build-push-action + # will attempt to build the image again + build-args: | + PIP_OPTIONS=-r lockfiles/requirements.txt dist/*.whl + context: artifacts/ + file: ./Dockerfile + target: runtime + load: true + tags: ${{ env.TEST_TAG }} + # If you have a long docker build (2+ minutes), uncomment the + # following to turn on caching. For short build times this + # makes it a little slower + #cache-from: type=gha + #cache-to: type=gha,mode=max + + - name: Test cli works in cached runtime image + run: docker run docker.io/library/${{ env.TEST_TAG }} --version + + - name: Create tags for publishing image id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: - images: | - ghcr.io/${{ github.repository }} - # github repo and dockerhub tag must match for this to work - ${{ github.repository }} + images: ${{ env.IMAGE_REPOSITORY }} tags: | - type=ref,event=branch type=ref,event=tag - type=raw,value=latest + type=raw,value=latest, enable=${{ github.ref_type == 'tag' }} + # type=edge,branch=main + # Add line above to generate image for every commit to given branch, + # and uncomment the end of if clause in next step + + - name: Push cached image to container registry + if: github.ref_type == 'tag' # || github.ref_name == 'main' + uses: docker/build-push-action@v5 + # This does not build the image again, it will find the image in the + # Docker cache and publish it + with: + # Note build-args, context, file, and target must all match between this + # step and the previous build-push-action, otherwise this step will + # attempt to build the image again + build-args: | + PIP_OPTIONS=-r lockfiles/requirements.txt dist/*.whl + context: artifacts/ + file: ./Dockerfile + target: runtime + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} - # required for multi-arch build - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + release: + # upload to PyPI and make a release on every tag + needs: [lint, dist, test] + if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }} + runs-on: ubuntu-latest + env: + HAS_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN != '' }} - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 + steps: + - uses: actions/download-artifact@v3 + + - name: Fixup blank lockfiles + # Github release artifacts can't be blank + run: for f in lockfiles/*; do [ -s $f ] || echo '# No requirements' >> $f; done + + - name: Github Release + # We pin to the SHA, not the tag, for security reasons. + # https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 + with: + prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }} + files: | + dist/* + lockfiles/* + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Build runtime image - uses: docker/build-push-action@v3 + - name: Publish to PyPI + if: ${{ env.HAS_PYPI_TOKEN }} + uses: pypa/gh-action-pypi-publish@release/v1 with: - file: .devcontainer/Dockerfile - context: . - platforms: linux/amd64,linux/arm/v7,linux/arm64/v8 - # only push tagged builds - push: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} - build-args: BASE=python:3.10-slim - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache + password: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cbc045f1..d89a0862 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,23 +2,11 @@ name: Docs CI on: push: - branches: - # Add more branches here to publish docs from other branches - - master - - main - tags: - - "*" pull_request: jobs: docs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest"] - python: ["3.10"] - runs-on: ubuntu-latest steps: @@ -26,40 +14,39 @@ jobs: if: startsWith(github.ref, 'refs/tags') run: sleep 60 - - name: Install python version - uses: actions/setup-python@v2 + - name: Checkout + uses: actions/checkout@v4 with: - python-version: ${{ matrix.python }} + # Need this to get version number from last tag + fetch-depth: 0 - - name: Install Packages + - name: Install system packages # Can delete this if you don't use graphviz in your docs run: sudo apt-get install graphviz - - name: checkout - uses: actions/checkout@v2 + - name: Install python packages + uses: ./.github/actions/install_requirements with: - fetch-depth: 0 - - - name: install dependencies - run: | - touch requirements_dev.txt - pip install -r requirements_dev.txt -e .[dev] + requirements_file: requirements-dev-3.x.txt + install_options: -e .[dev] - name: Build docs run: tox -e docs + - name: Sanitize ref name for docs version + run: echo "DOCS_VERSION=${GITHUB_REF_NAME//[^A-Za-z0-9._-]/_}" >> $GITHUB_ENV + - name: Move to versioned directory - # e.g. master or 0.1.2 - run: mv build/html ".github/pages/${GITHUB_REF##*/}" + run: mv build/html .github/pages/$DOCS_VERSION - - name: Write versions.txt - run: sphinx_rtd_theme_github_versions .github/pages + - name: Write switcher.json + run: python .github/pages/make_switcher.py --add $DOCS_VERSION ${{ github.repository }} .github/pages/switcher.json - name: Publish Docs to gh-pages - if: github.event_name == 'push' + if: github.event_name == 'push' && github.actor != 'dependabot[bot]' # We pin to the SHA, not the tag, for security reasons. # https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions - uses: peaceiris/actions-gh-pages@068dc23d9710f1ba62e86896f84735d869951305 # v3.8.0 + uses: peaceiris/actions-gh-pages@64b46b4226a4a12da2239ba3ea5aa73e3163c75b # v3.9.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: .github/pages diff --git a/.github/workflows/docs_clean.yml b/.github/workflows/docs_clean.yml new file mode 100644 index 00000000..e324640e --- /dev/null +++ b/.github/workflows/docs_clean.yml @@ -0,0 +1,43 @@ +name: Docs Cleanup CI + +# delete branch documentation when a branch is deleted +# also allow manually deleting a documentation version +on: + delete: + workflow_dispatch: + inputs: + version: + description: "documentation version to DELETE" + required: true + type: string + +jobs: + remove: + if: github.event.ref_type == 'branch' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: gh-pages + + - name: removing documentation for branch ${{ github.event.ref }} + if: ${{ github.event_name != 'workflow_dispatch' }} + run: echo "REF_NAME=${{ github.event.ref }}" >> $GITHUB_ENV + + - name: manually removing documentation version ${{ github.event.inputs.version }} + if: ${{ github.event_name == 'workflow_dispatch' }} + run: echo "REF_NAME=${{ github.event.inputs.version }}" >> $GITHUB_ENV + + - name: Sanitize ref name for docs version + run: echo "DOCS_VERSION=${REF_NAME//[^A-Za-z0-9._-]/_}" >> $GITHUB_ENV + + - name: update index and push changes + run: | + rm -r $DOCS_VERSION + python make_switcher.py --remove $DOCS_VERSION ${{ github.repository }} switcher.json + git config --global user.name 'GitHub Actions Docs Cleanup CI' + git config --global user.email 'GithubActionsCleanup@noreply.github.com' + git commit -am "Removing redundant docs version $DOCS_VERSION" + git push diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml deleted file mode 100644 index 04fc956e..00000000 --- a/.github/workflows/linkcheck.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Link Check - -on: - schedule: - # Run every Monday at 8am to check URL links still resolve - - cron: "0 8 * * MON" - -jobs: - docs: - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest"] - python: ["3.10"] - - runs-on: ubuntu-latest - - steps: - - name: Avoid git conflicts when tag and branch pushed at same time - if: startsWith(github.ref, 'refs/tags') - run: sleep 60 - - - name: checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Install python version - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - - - name: install dependencies - run: | - touch requirements_dev.txt - pip install -r requirements_dev.txt -e .[dev] - - - name: check links - run: tox -e docs -- -b linkcheck - diff --git a/.gitignore b/.gitignore index 85c66027..26b15f77 100644 --- a/.gitignore +++ b/.gitignore @@ -60,8 +60,12 @@ docs/_build/ # PyBuilder target/ -# DLS build dir and virtual environment -/prefix/ -/venv/ -/lightweight-venv/ -/installed.files +# likely venv names +.venv* +venv* + +# further build artifacts +lockfiles/ + +# ruff cache +.ruff_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 027b9fd6..5bc9f001 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: v4.4.0 hooks: - id: check-added-large-files - id: check-yaml @@ -15,12 +15,9 @@ repos: entry: black --check --diff types: [python] - - id: flake8 - name: Run flake8 + - id: ruff + name: Run ruff stages: [commit] language: system - entry: flake8 + entry: ruff types: [python] - - - diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 734f215e..e3b582fd 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,10 @@ { "recommendations": [ - "ms-python.vscode-pylance", + "ms-vscode-remote.remote-containers", "ms-python.python", - "ryanluker.vscode-coverage-gutters" + "tamasfe.even-better-toml", + "redhat.vscode-yaml", + "ryanluker.vscode-coverage-gutters", + "charliermarsh.Ruff" ] } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 1d1fa7c9..f65cb376 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,34 +4,6 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { - "name": "Test gPhotos trace", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/gphotos-sync", - "args": [ - "/tmp/test-gphotos", - "--secret", - "/home/giles/github//gphotos-sync/test/test_credentials/client_secret.json", - "--log-level", - "trace" - ], - "console": "integratedTerminal" - }, - { - "name": "Test gPhotos debug", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/gphotos-sync", - "args": [ - "/tmp/test-gphotos", - "--secret", - "/home/giles/github//gphotos-sync/test/test_credentials/client_secret.json", - "--log-level", - "debug" - ], - "console": "integratedTerminal" - }, { "name": "Debug Unit Test", "type": "python", @@ -43,7 +15,7 @@ ], "console": "integratedTerminal", "env": { - // The default config in setup.cfg's "[tool:pytest]" adds coverage. + // The default config in pyproject.toml's "[tool.pytest.ini_options]" adds coverage. // Cannot have coverage and debugging at the same time. // https://github.com/microsoft/vscode-python/issues/693 "PYTEST_ADDOPTS": "--no-cov" diff --git a/.vscode/settings.json b/.vscode/settings.json index 474dd61d..68d07bf0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,16 @@ { - "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": true, - "python.linting.mypyEnabled": true, - "python.linting.enabled": true, - "python.testing.pytestArgs": [], + "python.testing.pytestArgs": [ + "--cov=python3_pip_skeleton", + "--cov-report", + "xml:cov.xml" + ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - "python.formatting.provider": "black", - "python.languageServer": "Pylance", "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true - }, - "cSpell.words": [ - "gphotos" - ] + "[python]": { + "editor.codeActionsOnSave": { + "source.fixAll.ruff": "never", + "source.organizeImports.ruff": "explicit" + } + } } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..c999e864 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,16 @@ +// See https://go.microsoft.com/fwlink/?LinkId=733558 +// for the documentation about the tasks.json format +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "label": "Tests, lint and docs", + "command": "tox -p", + "options": { + "cwd": "${workspaceRoot}" + }, + "problemMatcher": [], + } + ] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..192d44a9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# This file is for use as a devcontainer and a runtime container +# +# The devcontainer should use the build target and run as root with podman +# or docker with user namespaces. +# +FROM python:3.11 as build + +ARG PIP_OPTIONS=. + +# Add any system dependencies for the developer/build environment here e.g. +# RUN apt-get update && apt-get upgrade -y && \ +# apt-get install -y --no-install-recommends \ +# desired-packages \ +# && rm -rf /var/lib/apt/lists/* + +# set up a virtual environment and put it in PATH +RUN python -m venv /venv +ENV PATH=/venv/bin:$PATH + +# Copy any required context for the pip install over +COPY . /context +WORKDIR /context + +# install python package into /venv +RUN pip install ${PIP_OPTIONS} + +FROM python:3.11-slim as runtime + +# Add apt-get system dependecies for runtime here if needed + +# copy the virtual environment from the build stage and put it in PATH +COPY --from=build /venv/ /venv/ +ENV PATH=/venv/bin:$PATH + +# change this entrypoint if it is not the same as the repo +ENTRYPOINT ["gphotos-sync"] +CMD ["--version"] diff --git a/README.rst b/README.rst index 5af4b0d1..4fd922c0 100644 --- a/README.rst +++ b/README.rst @@ -14,13 +14,13 @@ Releases https://github.com/gilesknap/gphotos-sync/releases Intro ===== -Google Photos Sync downloads all photos and videos the user has uploaded to -Google Photos. It also organizes the media in the local file system using -album information. Additional Google Photos 'Creations' such as +Google Photos Sync downloads all photos and videos the user has uploaded to +Google Photos. It also organizes the media in the local file system using +album information. Additional Google Photos 'Creations' such as animations, panoramas, movies, effects and collages are also backed up. This software is read only and never modifies your cloud library in any way, -so there is no risk of damaging your data. +so there is no risk of damaging your data. Warning: Google API Issues ========================== diff --git a/docs/conf.py b/docs/conf.py index 24ee3c15..744109c3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -77,10 +77,10 @@ # This means you can link things like `str` and `asyncio` to the relevant # docs in the python documentation. -intersphinx_mapping = dict(python=("https://docs.python.org/3/", None)) +intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} # A dictionary of graphviz graph attributes for inheritance diagrams. -inheritance_graph_attrs = dict(rankdir="TB") +inheritance_graph_attrs = {"rankdir": "TB"} # Common links that should be available on every page rst_epilog = """ @@ -99,7 +99,7 @@ html_theme = "sphinx_rtd_theme_github_versions" # Options for the sphinx rtd theme, use DLS blue -html_theme_options = dict(style_nav_header_background="rgb(7, 43, 93)") +html_theme_options = {"style_nav_header_background": "rgb(7, 43, 93)"} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/pyproject.toml b/pyproject.toml index 5c6c2939..978c71a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,129 @@ [build-system] -# To get a reproducible wheel, wheel must be pinned to the same version as in -# dls-python3, and setuptools must produce the same dist-info. Cap setuptools -# to the last version that didn't add License-File to METADATA -requires = ["setuptools<57", "wheel==0.33.1"] +requires = ["setuptools>=64", "setuptools_scm[toml]>=6.2", "wheel"] build-backend = "setuptools.build_meta" +[project] +name = "gphotos-sync" +classifiers = [ + "Development Status :: 3 - Alpha", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +description = "Google Photos and Albums backup tool" +dependencies = [ + "attrs", + "exif", + "appdirs", + "pyyaml", + "psutil", + "google_auth_oauthlib", +] # Add project dependencies here, e.g. ["click", "numpy"] +dynamic = ["version"] +license.file = "LICENSE" +readme = "README.rst" +requires-python = ">=3.8" + +[project.optional-dependencies] +dev = [ + "black", + "mypy", + "pipdeptree", + "pre-commit", + "pydata-sphinx-theme>=0.12", + "pytest", + "pytest-cov", + "ruff", + "sphinx-autobuild", + "sphinx-copybutton", + "sphinx-design", + "sphinx_rtd_theme_github_versions", + "tox-direct", + "types-mock", + "mock", + "types-setuptools", + "types-requests", + "types-PyYAML", +] + +[project.scripts] +gphotos-sync = "gphotos_sync.__main__:main" + +[project.urls] +GitHub = "https://github.com/gilesknap/gphotos-sync" + +[[project.authors]] # Further authors may be added by duplicating this section +email = "gilesknap@gmail.com" +name = "Giles Knap" + + [tool.setuptools_scm] write_to = "src/gphotos_sync/_version.py" + +[tool.mypy] +ignore_missing_imports = true # Ignore missing stubs in imported modules + +[tool.pytest.ini_options] +# Run pytest with all our checkers, and don't spam us with massive tracebacks on error +addopts = """ + --tb=native -vv --doctest-modules --doctest-glob="*.rst" + """ +# https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings +filterwarnings = [ + "error", + "ignore:.*socket.*:ResourceWarning", + # this deprecation is rather serious for gphotos-sync as it relies on sqlite + # doing date conversion quite heavily - will ignore the deprection for now + # DeprecationWarning: The default datetime adapter is deprecated as of Python 3.12; + "ignore:.*sqlite3.*:DeprecationWarning:", + # like the above + "ignore:.*datetime.utcfromtimestamp:DeprecationWarning:" +] +# Doctest python code in docs, python code in src docstrings, test functions in tests +testpaths = "docs src tests" + +[tool.coverage.run] +data_file = "/tmp/photos_sync.coverage" + +[tool.coverage.paths] +# Tests are run from installed location, map back to the src directory +source = ["src", "**/site-packages/"] + +# tox must currently be configured via an embedded ini string +# See: https://github.com/tox-dev/tox/issues/999 +[tool.tox] +legacy_tox_ini = """ +[tox] +skipsdist=True + +[testenv:{pre-commit,mypy,pytest,docs}] +# Don't create a virtualenv for the command, requires tox-direct plugin +direct = True +passenv = * +allowlist_externals = + pytest + pre-commit + mypy + sphinx-build + sphinx-autobuild +commands = + pytest: pytest --cov=photos_sync --cov-report term --cov-report xml:cov.xml {posargs} + mypy: mypy src tests {posargs} + pre-commit: pre-commit run --all-files {posargs} + docs: sphinx-{posargs:build -EW --keep-going} -T docs build/html +""" + + +[tool.ruff] +src = ["src", "tests"] +line-length = 88 +select = [ + "C4", # flake8-comprehensions - https://beta.ruff.rs/docs/rules/#flake8-comprehensions-c4 + "E", # pycodestyle errors - https://beta.ruff.rs/docs/rules/#error-e + "F", # pyflakes rules - https://beta.ruff.rs/docs/rules/#pyflakes-f + "W", # pycodestyle warnings - https://beta.ruff.rs/docs/rules/#warning-w + "I001", # isort +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 1b89c1e4..00000000 --- a/setup.cfg +++ /dev/null @@ -1,141 +0,0 @@ -[metadata] -name = gphotos-sync -description = Google Photos and Albums backup tool -url = https://github.com/gilesknap/gphotos-sync -author = Giles Knap -author_email = gilesknap@gmail.com -license = Apache License 2.0 -long_description = file: README.rst -long_description_content_type = text/x-rst -classifiers = - License :: OSI Approved :: Apache Software License - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 -plaforrms = - Linux - Windows - Mac - -[options] -python_requires = >=3.7 -packages = find: -# =src is interpreted as {"": "src"} -# as per recommendation here https://hynek.me/articles/testing-packaging/ -package_dir = - =src - -setup_requires = - setuptools_scm[toml]>=6.2 - -install_requires = - attrs==22.2.0 - exif==1.6.0 - appdirs==1.4.4 - pyyaml==6.0.1 - psutil==5.9.4 - google-auth-oauthlib==1.0.0 - -[options.extras_require] -# For development tests/docs -dev = - black==22.3.0 - flake8-isort - isort>5.0 - mypy - pre-commit - pytest-cov - sphinx-rtd-theme-github-versions - tox - tox-direct - setuptools_scm[toml]>=6.2 - mock - types-setuptools - types-requests - types-PyYAML - -[options.packages.find] -where = src -# Don't include our tests directory in the distribution -exclude = tests - -# Specify any package data to be included in the wheel below. -[options.package_data] -gphotos_sync = - sql/gphotos_create.sql - -[options.entry_points] -# Include a command line script -console_scripts = - gphotos-sync = gphotos_sync.Main:main - -[mypy] -# Ignore missing stubs for modules we use -ignore_missing_imports = True - -[isort] -profile=black -float_to_top=true - -[flake8] -# Make flake8 respect black's line length (default 88), -max-line-length = 88 -extend-ignore = - # See https://github.com/PyCQA/pycodestyle/issues/373 - E203, - # support typing.overload decorator - F811, - # allow Annotated[typ, some_func("some string")] - F722, -exclude = - .tox - .venv - -[tool:pytest] -# Run pytest with all our checkers, and don't spam us with massive tracebacks on error -addopts = - --tb=native -vv --doctest-modules --doctest-glob="*.rst" - --cov=gphotos_sync --cov-report term --cov-report xml:cov.xml -# https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings -filterwarnings = - error - # its difficult to ensure all sockets are closed in tests so ignore - ignore:.*socket.*:ResourceWarning -# Doctest python code in docs, python code in src docstrings, test functions in tests -testpaths = - tests - -[coverage:run] -# This is covered in the versiongit test suite so exclude it here -omit = */_version_git.py -data_file = /tmp/gphotos_sync.coverage - -[coverage:paths] -# Tests are run from installed location, map back to the src directory -source = - src - **/site-packages/ - - -# Use tox to provide parallel linting and testing -# NOTE that we pre-install all tools in the dev dependencies (including tox). -# Hence the use of allowlist_externals instead of using the tox virtualenvs. -# This ensures a match between developer time tools in the IDE and tox tools. -[tox:tox] -skipsdist = True - -[testenv:{pre-commit,mypy,pytest,docs}] -# Don't create a virtualenv for the command, requires tox-direct plugin -direct = True -passenv = * -allowlist_externals = - pytest - pre-commit - mypy - sphinx-build - sphinx-autobuild -commands = - pytest: pytest {posargs} - mypy: mypy src tests {posargs} - pre-commit: pre-commit run --all-files {posargs} - docs: sphinx-{posargs:build -EW --keep-going} -T docs build/html diff --git a/src/gphotos_sync/BadIds.py b/src/gphotos_sync/BadIds.py index e4c0e8ef..d1ad8c6f 100644 --- a/src/gphotos_sync/BadIds.py +++ b/src/gphotos_sync/BadIds.py @@ -40,7 +40,7 @@ def store_ids(self): safe_dump(self.items, stream, default_flow_style=False) def add_id(self, path: str, gid: str, product_url: str, e: Exception): - item = dict(path=str(path), product_url=product_url) + item = {"path": str(path), "product_url": product_url} self.items[gid] = item log.debug("BAD ID %s for %s", gid, path, exc_info=e) diff --git a/src/gphotos_sync/__main__.py b/src/gphotos_sync/__main__.py index 43366329..235986b2 100644 --- a/src/gphotos_sync/__main__.py +++ b/src/gphotos_sync/__main__.py @@ -1,7 +1,6 @@ from argparse import ArgumentParser from . import __version__ -from .hello import HelloClass, say_hello_lots __all__ = ["main"] @@ -9,10 +8,7 @@ def main(args=None): parser = ArgumentParser() parser.add_argument("--version", action="version", version=__version__) - parser.add_argument("name", help="Name of the person to greet") - parser.add_argument("--times", type=int, default=5, help="Number of times to greet") args = parser.parse_args(args) - say_hello_lots(HelloClass(args.name), args.times) # test with: python -m gphotos_sync diff --git a/tests/test_boilerplate_removed.py b/tests/test_boilerplate_removed.py index ca8086cb..e9e85f96 100644 --- a/tests/test_boilerplate_removed.py +++ b/tests/test_boilerplate_removed.py @@ -2,14 +2,19 @@ This file checks that all the example boilerplate text has been removed. It can be deleted when all the contained tests pass """ -import configparser +import sys from pathlib import Path +if sys.version_info < (3, 8): + from importlib_metadata import metadata # noqa +else: + from importlib.metadata import metadata # noqa + ROOT = Path(__file__).parent.parent def skeleton_check(check: bool, text: str): - if ROOT.name == "dls-python3-skeleton": + if ROOT.name == "python3-pip-skeleton" or str(ROOT) == "/project": # In the skeleton module the check should fail check = not check text = f"Skeleton didn't raise: {text}" @@ -24,19 +29,12 @@ def assert_not_contains_text(path: str, text: str, explanation: str): skeleton_check(text in contents, f"Please change ./{path} {explanation}") -def assert_not_exists(path: str, explanation: str): - exists = (ROOT / path).exists() - skeleton_check(exists, f"Please delete ./{path} {explanation}") - - -# setup.cfg -def test_module_description(): - conf = configparser.ConfigParser() - conf.read("setup.cfg") - description = conf["metadata"]["description"] +# pyproject.toml +def test_module_summary(): + summary = metadata("gphotos-sync")["summary"] skeleton_check( - "One line description of your module" in description, - "Please change description in ./setup.cfg " + "One line description of your module" in summary, + "Please change project.description in ./pyproject.toml " "to be a one line description of your module", ) @@ -50,30 +48,17 @@ def test_changed_README_intro(): ) -def test_changed_README_body(): +def test_removed_adopt_skeleton(): assert_not_contains_text( "README.rst", - "This is where you should put some images or code snippets", - "to include some features and why people should use it", + "This project contains template code only", + "remove the note at the start", ) -# Docs -def test_docs_ref_api_changed(): +def test_changed_README_body(): assert_not_contains_text( - "docs/reference/api.rst", - "You can mix verbose text with docstring and signature", - "to introduce the API for your module", - ) - - -def test_how_tos_written(): - assert_not_exists( - "docs/how-to/accomplish-a-task.rst", "and write some docs/how-tos" - ) - - -def test_explanations_written(): - assert_not_exists( - "docs/explanations/why-is-something-so.rst", "and write some docs/explanations" + "README.rst", + "This is where you should put some images or code snippets", + "to include some features and why people should use it", ) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..77586a54 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,9 @@ +import subprocess +import sys + +from gphotos_sync import __version__ + + +def test_cli_version(): + cmd = [sys.executable, "-m", "gphotos_sync", "--version"] + assert subprocess.check_output(cmd).decode().strip() == __version__ diff --git a/tests/test_credentials/.gphotos.token b/tests/test_credentials/.gphotos.token index 0993c29a..7cf84e76 100644 --- a/tests/test_credentials/.gphotos.token +++ b/tests/test_credentials/.gphotos.token @@ -1 +1 @@ -{"access_token": "ya29.a0ARrdaM-gnPDJz9yQsqsqSk9JuTjWOEmcHOhAaOK4kqaq-TvXKdyiq3poSywztKprh1LOVR_jzMUZzFN5WsHpT9tGS0NxNbf-JWaix3iqKEF5Y0vZhs6u9iEee-drUkoBpLVeIdNR70w-KDiWzDx73MSnU7RHCx4", "expires_in": 3599, "scope": ["https://www.googleapis.com/auth/photoslibrary.readonly", "https://www.googleapis.com/auth/photoslibrary.sharing"], "token_type": "Bearer", "expires_at": 1653085052.2558572, "refresh_token": "1//03CEqAzsnP-8PCgYIARAAGAMSNwF-L9Irz4_ilhRw0HIwVImT4gTCUPlV8YaCTYQiIjD4juWOI5eQh_-Rzh9nTmBND0jliOnabq4"} \ No newline at end of file +{"access_token": "ya29.a0AfB_byAAnSEyVYmlxFIXG7A7lH8jkrSrd4RSnunI4rYyHfzdG-ZjSQiFxo5-OHV5rBgEnHoFMKK1Rj5PKxLCXVCEr-0yMiJs61wCCfzg36-UWQegZTV3kBotQH_Qk7HTkZcphp11fW5lbc3POClog-aOCfJLIbOe2UfA63MaCgYKAZsSARMSFQHGX2Mi3joogOa2oHnOerg_ojwUQg0174", "expires_in": 3599, "scope": ["https://www.googleapis.com/auth/photoslibrary.sharing", "https://www.googleapis.com/auth/photoslibrary.readonly"], "token_type": "Bearer", "expires_at": 1703971275.0828457, "refresh_token": "1//03CEqAzsnP-8PCgYIARAAGAMSNwF-L9Irz4_ilhRw0HIwVImT4gTCUPlV8YaCTYQiIjD4juWOI5eQh_-Rzh9nTmBND0jliOnabq4"} \ No newline at end of file diff --git a/tests/test_system/test_system.py b/tests/test_system/test_system.py index 0f69d041..69ee1eb4 100644 --- a/tests/test_system/test_system.py +++ b/tests/test_system/test_system.py @@ -464,7 +464,6 @@ def test_skip_video_on_album(self): @patch.object(GooglePhotosDownload, "do_download_file") def test_bad_ids(self, do_download_file): - do_download_file.side_effect = HTTPError(Mock(status=500), "ouch!") with ts.SetupDbAndCredentials() as s: args = [ diff --git a/tests/test_units/test_local_scan.py b/tests/test_units/test_local_scan.py index accbc798..23fb4932 100644 --- a/tests/test_units/test_local_scan.py +++ b/tests/test_units/test_local_scan.py @@ -9,7 +9,6 @@ class TestLocalScan(TestCase): def test_local_duplicate_names(self): - ps = "PIC00002 (2).jpg" p = Path(test_data) / Path(ps)