diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 00000000..3cac8f56 --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,15 @@ +# Changes here will be overwritten by Copier +_commit: 2.3.0 +_src_path: gh:DiamondLightSource/python-copier-template +author_email: dallan@bnl.gov +author_name: Brookhaven National Lab +description: Data model used by the bluesky ecosystem. +distribution_name: event-model +docker: false +docs_type: sphinx +git_platform: github.com +github_org: bluesky +package_name: event_model +pypi: true +repo_name: event-model +type_checker: mypy diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..d3d639a5 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,46 @@ +// For format details, see https://containers.dev/implementors/json_reference/ +{ + "name": "Python 3 Developer Container", + "build": { + "dockerfile": "../Dockerfile", + "target": "developer" + }, + "remoteEnv": { + // Allow X11 apps to run inside the container + "DISPLAY": "${localEnv:DISPLAY}" + }, + "customizations": { + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "python.defaultInterpreterPath": "/venv/bin/python" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "github.vscode-github-actions", + "tamasfe.even-better-toml", + "redhat.vscode-yaml", + "ryanluker.vscode-coverage-gutters", + "charliermarsh.ruff", + "ms-azuretools.vscode-docker" + ] + } + }, + "features": { + // Some default things like git config + "ghcr.io/devcontainers/features/common-utils:2": { + "upgradePackages": false + } + }, + "runArgs": [ + // Allow the container to access the host X11 display and EPICS CA + "--net=host", + // Make sure SELinux does not disable with access to host filesystems like tmp + "--security-opt=label=disable" + ], + // Mount the parent as /workspaces so we can pip install peers as editable + "workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind", + // After the container is created, install the python project in editable form + "postCreateCommand": "pip install $([ -f dev-requirements.txt ] && echo '-c dev-requirements.txt') -e '.[dev]' && pre-commit install" +} diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst deleted file mode 100644 index 3303ec01..00000000 --- a/.github/CONTRIBUTING.rst +++ /dev/null @@ -1,35 +0,0 @@ -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/bluesky/event-model/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/bluesky/event-model/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://blueskyproject.io/event-model/main/developer/how-to/contribute.html diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..aa65892f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,21 @@ +--- +name: Bug Report +about: The template to use for reporting bugs and usability issues +title: " " +labels: 'bug' +assignees: '' + +--- + +Describe the bug, including a clear and concise description of the expected behavior, the actual behavior and the context in which you encountered it (ideally include details of your environment). + +## Steps To Reproduce +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + + +## Acceptance Criteria +- Specific criteria that will be used to judge if the issue is fixed diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md new file mode 100644 index 00000000..52c84dd8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -0,0 +1,13 @@ +--- +name: Issue +about: The standard template to use for feature requests, design discussions and tasks +title: " " +labels: '' +assignees: '' + +--- + +A brief description of the issue, including specific stakeholders and the business case where appropriate + +## Acceptance Criteria +- Specific criteria that will be used to judge if the issue is fixed diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..8200afe5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,8 @@ +Fixes #ISSUE + +### Instructions to reviewer on how to test: +1. Do thing x +2. Confirm thing y happens + +### Checks for reviewer +- [ ] Would the PR title make sense to a user on a set of release notes diff --git a/.github/actions/install_requirements/action.yml b/.github/actions/install_requirements/action.yml index cd5e50f5..d33e0805 100644 --- a/.github/actions/install_requirements/action.yml +++ b/.github/actions/install_requirements/action.yml @@ -1,62 +1,34 @@ name: Install requirements -description: Run pip install with requirements and upload resulting requirements +description: Install a version of python then call pip install and report what was installed inputs: - requirements_file: - description: Name of requirements file to use and upload - required: true - install_options: + python-version: + description: Python version to install, default is from Dockerfile + default: "dev" + pip-install: description: Parameters to pass to pip install - required: true - python_version: - description: Python version to install - default: "3.x" - jsonschema_version: - description: version of the jsonschema pip package to install - default: 4 + default: "$([ -f dev-requirements.txt ] && echo '-c dev-requirements.txt') -e .[dev]" 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 }} - pip install --upgrade "jsonschema==${{inputs.jsonschema_version}}.*" - shell: bash - - - name: Create lockfile + - name: Get version of python 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 '/file:/d' lockfiles/${{ inputs.requirements_file }} + PYTHON_VERSION="${{ inputs.python-version }}" + if [ $PYTHON_VERSION == "dev" ]; then + PYTHON_VERSION=$(sed -n "s/ARG PYTHON_VERSION=//p" Dockerfile) + fi + echo "PYTHON_VERSION=$PYTHON_VERSION" >> "$GITHUB_ENV" shell: bash - - name: Upload lockfiles - uses: actions/upload-artifact@v3 + - name: Setup python + uses: actions/setup-python@v5 with: - name: lockfiles - path: lockfiles + python-version: ${{ env.PYTHON_VERSION }} - # 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 + - name: Install packages + run: pip install ${{ inputs.pip-install }} shell: bash + - name: Report what was installed + run: pip freeze + shell: bash diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fb7c6ee6..184ba363 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,8 +9,16 @@ updates: directory: "/" schedule: interval: "weekly" + groups: + actions: + patterns: + - "*" - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" + groups: + dev-dependencies: + patterns: + - "*" diff --git a/.github/pages/index.html b/.github/pages/index.html index 80f0a009..c495f39f 100644 --- a/.github/pages/index.html +++ b/.github/pages/index.html @@ -8,4 +8,4 @@ - \ No newline at end of file + diff --git a/.github/pages/make_switcher.py b/.github/pages/make_switcher.py index 39c12772..c06813af 100755 --- a/.github/pages/make_switcher.py +++ b/.github/pages/make_switcher.py @@ -1,30 +1,32 @@ +"""Make switcher.json to allow docs to switch between different versions.""" + 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]: +def report_output(stdout: bytes, label: str) -> list[str]: + """Print and return something received frm stdout.""" ret = stdout.decode().strip().split("\n") print(f"{label}: {ret}") return ret -def get_branch_contents(ref: str) -> List[str]: +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]: +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]: +def get_versions(ref: str, add: str | None) -> 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: @@ -36,15 +38,12 @@ def get_versions(ref: str, add: Optional[str], remove: Optional[str]) -> List[st # 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] = [] + versions: list[str] = [] for version in ["master", "main"] + tags: if version in builds: versions.append(version) @@ -56,29 +55,27 @@ def get_versions(ref: str, add: Optional[str], remove: Optional[str]) -> List[st return versions -def write_json(path: Path, repository: str, versions: str): +def write_json(path: Path, repository: str, versions: list[str]): + """Write the JSON switcher to path.""" org, repo_name = repository.split("/") struct = [ - dict(version=version, url=f"https://{org}.github.io/{repo_name}/{version}/") + {"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) + path.write_text(text, encoding="utf-8") def main(args=None): + """Parse args and write switcher.""" parser = ArgumentParser( - description="Make a versions.txt file from gh-pages directories" + description="Make a versions.json 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", @@ -91,7 +88,7 @@ def main(args=None): args = parser.parse_args(args) # Write the versions file - versions = get_versions("origin/gh-pages", args.add, args.remove) + versions = get_versions("origin/gh-pages", args.add) write_json(args.output, args.repository, versions) diff --git a/.github/workflows/_check.yml b/.github/workflows/_check.yml new file mode 100644 index 00000000..a6139c19 --- /dev/null +++ b/.github/workflows/_check.yml @@ -0,0 +1,27 @@ +on: + workflow_call: + outputs: + branch-pr: + description: The PR number if the branch is in one + value: ${{ jobs.pr.outputs.branch-pr }} + +jobs: + pr: + runs-on: "ubuntu-latest" + outputs: + branch-pr: ${{ steps.script.outputs.result }} + steps: + - uses: actions/github-script@v7 + id: script + if: github.event_name == 'push' + with: + script: | + const prs = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + head: context.repo.owner + ':${{ github.ref_name }}' + }) + if (prs.data.length) { + console.log(`::notice ::Skipping CI on branch push as it is already run in PR #${prs.data[0]["number"]}`) + return prs.data[0]["number"] + } diff --git a/.github/workflows/_dist.yml b/.github/workflows/_dist.yml new file mode 100644 index 00000000..b1c4c93c --- /dev/null +++ b/.github/workflows/_dist.yml @@ -0,0 +1,36 @@ +on: + workflow_call: + +jobs: + build: + runs-on: "ubuntu-latest" + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Need this to get version number from last tag + fetch-depth: 0 + + - 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@v4 + with: + name: dist + path: dist + + - name: Check for packaging errors + run: pipx run twine check --strict dist/* + + - name: Install produced wheel + uses: ./.github/actions/install_requirements + with: + pip-install: dist/*.whl + + - 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 --hide='*.egg-info' src | head -1) --version diff --git a/.github/workflows/docs.yml b/.github/workflows/_docs.yml similarity index 74% rename from .github/workflows/docs.yml rename to .github/workflows/_docs.yml index f170cbec..a1cafcae 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/_docs.yml @@ -1,17 +1,13 @@ -name: Docs CI - on: - push: - pull_request: + workflow_call: jobs: - docs: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository + build: runs-on: ubuntu-latest steps: - name: Avoid git conflicts when tag and branch pushed at same time - if: startsWith(github.ref, 'refs/tags') + if: github.ref_type == 'tag' run: sleep 60 - name: Checkout @@ -21,18 +17,23 @@ jobs: fetch-depth: 0 - name: Install system packages - # Can delete this if you don't use graphviz in your docs run: sudo apt-get install graphviz - name: Install python packages uses: ./.github/actions/install_requirements - with: - requirements_file: requirements-dev-3.x.txt - install_options: -e .[dev] - name: Build docs run: tox -e docs + - name: Remove environment.pickle + run: rm build/html/.doctrees/environment.pickle + + - name: Upload built docs artifact + uses: actions/upload-artifact@v4 + with: + name: docs + path: build + - name: Sanitize ref name for docs version run: echo "DOCS_VERSION=${GITHUB_REF_NAME//[^A-Za-z0-9._-]/_}" >> $GITHUB_ENV @@ -43,11 +44,11 @@ jobs: 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' && github.actor != 'dependabot[bot]' + if: github.ref_type == 'tag' || github.ref_name == 'main' # 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@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: .github/pages - keep_files: true \ No newline at end of file + keep_files: true diff --git a/.github/workflows/_pypi.yml b/.github/workflows/_pypi.yml new file mode 100644 index 00000000..0c5258db --- /dev/null +++ b/.github/workflows/_pypi.yml @@ -0,0 +1,17 @@ +on: + workflow_call: + +jobs: + upload: + runs-on: ubuntu-latest + environment: release + + steps: + - name: Download dist artifact + uses: actions/download-artifact@v4 + with: + name: dist + path: dist + + - name: Publish to PyPI using trusted publishing + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml new file mode 100644 index 00000000..10d8ed87 --- /dev/null +++ b/.github/workflows/_release.yml @@ -0,0 +1,32 @@ +on: + workflow_call: + +jobs: + artifacts: + runs-on: ubuntu-latest + + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + merge-multiple: true + + - name: Zip up docs + run: | + set -vxeuo pipefail + if [ -d html ]; then + mv html $GITHUB_REF_NAME + zip -r docs.zip $GITHUB_REF_NAME + rm -rf $GITHUB_REF_NAME + fi + + - name: Create 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@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 + with: + prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }} + files: "*" + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml new file mode 100644 index 00000000..f652d414 --- /dev/null +++ b/.github/workflows/_test.yml @@ -0,0 +1,62 @@ +on: + workflow_call: + inputs: + python-version: + type: string + description: The version of python to install + required: true + runs-on: + type: string + description: The runner to run this job on + required: true + secrets: + CODECOV_TOKEN: + required: true + +env: + # https://github.com/pytest-dev/pytest/issues/2042 + PY_IGNORE_IMPORTMISMATCH: "1" + +jobs: + run: + runs-on: ${{ inputs.runs-on }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Need this to get version number from last tag + fetch-depth: 0 + + - if: inputs.python-version == 'dev' + name: Install dev versions of python packages + uses: ./.github/actions/install_requirements + + - if: inputs.python-version == 'dev' + name: Write the requirements as an artifact + run: pip freeze --exclude-editable > /tmp/dev-requirements.txt + + - if: inputs.python-version == 'dev' + name: Upload dev-requirements.txt + uses: actions/upload-artifact@v4 + with: + name: dev-requirements + path: /tmp/dev-requirements.txt + + - if: inputs.python-version != 'dev' + name: Install latest versions of python packages + uses: ./.github/actions/install_requirements + with: + python-version: ${{ inputs.python-version }} + pip-install: ".[dev]" + + - name: Run tests + run: tox -e tests + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + name: ${{ inputs.python-version }}/${{ inputs.runs-on }} + files: cov.xml + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/_tox.yml b/.github/workflows/_tox.yml new file mode 100644 index 00000000..a13536d3 --- /dev/null +++ b/.github/workflows/_tox.yml @@ -0,0 +1,22 @@ +on: + workflow_call: + inputs: + tox: + type: string + description: What to run under tox + required: true + + +jobs: + run: + runs-on: "ubuntu-latest" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install python packages + uses: ./.github/actions/install_requirements + + - name: Run tox + run: tox -e ${{ inputs.tox }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..3a391129 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI + +on: + push: + pull_request: + +jobs: + check: + uses: ./.github/workflows/_check.yml + + lint: + needs: check + if: needs.check.outputs.branch-pr == '' + uses: ./.github/workflows/_tox.yml + with: + tox: pre-commit,type-checking + + test: + needs: check + if: needs.check.outputs.branch-pr == '' + strategy: + matrix: + runs-on: ["ubuntu-latest"] # can add windows-latest, macos-latest + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + include: + # Include one that runs in the dev environment + - runs-on: "ubuntu-latest" + python-version: "dev" + fail-fast: false + uses: ./.github/workflows/_test.yml + with: + runs-on: ${{ matrix.runs-on }} + python-version: ${{ matrix.python-version }} + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + docs: + needs: check + if: needs.check.outputs.branch-pr == '' + uses: ./.github/workflows/_docs.yml + + dist: + needs: check + if: needs.check.outputs.branch-pr == '' + uses: ./.github/workflows/_dist.yml + + pypi: + if: github.ref_type == 'tag' + needs: dist + uses: ./.github/workflows/_pypi.yml + permissions: + id-token: write + + release: + if: github.ref_type == 'tag' + needs: [dist, docs] + uses: ./.github/workflows/_release.yml + permissions: + contents: write diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml deleted file mode 100644 index 19fb1feb..00000000 --- a/.github/workflows/code.yml +++ /dev/null @@ -1,144 +0,0 @@ -name: Code CI - -on: - push: - pull_request: -env: - # The target python version, which must match the Dockerfile version - CONTAINER_PYTHON: "3.12" - -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@v4 - - - name: Install python packages - uses: ./.github/actions/install_requirements - with: - requirements_file: requirements-dev-3.x.txt - install_options: -e .[dev] - - - 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"] # 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: - - os: "ubuntu-latest" - python: "3.8" - install: ".[dev]" - jsonschema: 4 - - runs-on: ${{ matrix.os }} - env: - # https://github.com/pytest-dev/pytest/issues/2042 - PY_IGNORE_IMPORTMISMATCH: "1" - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - # Need this to get version number from last tag - fetch-depth: 0 - - - name: Install python packages - uses: ./.github/actions/install_requirements - with: - python_version: ${{ matrix.python }} - requirements_file: requirements-test-${{ matrix.os }}-${{matrix.python }}-${{ matrix.jsonschema }}.txt - install_options: ${{ matrix.install }} - - - name: List dependency tree - run: pipdeptree - - - name: Run tests - run: pytest - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - name: ${{ matrix.python }}/${{ matrix.os }} - files: cov.xml - - dist: - 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@v4 - with: - # Need this to get version number from last tag - fetch-depth: 0 - - - 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@v4 - with: - name: dist - path: 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 - - - name: Test module --version works using the installed wheel - run: python -m event_model --version - - - release: - # upload to PyPI and make a release on every tag - needs: [lint, dist, test] - if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} - runs-on: ubuntu-latest - env: - HAS_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN != '' }} - - steps: - - uses: actions/download-artifact@v4 - - - 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@d99959edae48b5ffffd7b00da66dcdb0a33a52ee # 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: Publish to PyPI - if: ${{ env.HAS_PYPI_TOKEN }} - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/docs_clean.yml b/.github/workflows/docs_clean.yml deleted file mode 100644 index e324640e..00000000 --- a/.github/workflows/docs_clean.yml +++ /dev/null @@ -1,43 +0,0 @@ -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 1586799b..00000000 --- a/.github/workflows/linkcheck.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Link Check - -on: - workflow_dispatch: - schedule: - # Run weekly to check URL links still resolve - - cron: "0 8 * * WED" - -jobs: - docs: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install python packages - uses: ./.github/actions/install_requirements - with: - requirements_file: requirements-dev-3.x.txt - install_options: -e .[dev] - - - name: Check links - run: tox -e docs build -- -b linkcheck - - - name: Keepalive Workflow - uses: gautamkrishnar/keepalive-workflow@v2 \ No newline at end of file diff --git a/.github/workflows/periodic.yml b/.github/workflows/periodic.yml new file mode 100644 index 00000000..e2a0fd1b --- /dev/null +++ b/.github/workflows/periodic.yml @@ -0,0 +1,13 @@ +name: Periodic + +on: + workflow_dispatch: + schedule: + # Run weekly to check URL links still resolve + - cron: "0 8 * * WED" + +jobs: + linkcheck: + uses: ./.github/workflows/_tox.yml + with: + tox: docs build -- -b linkcheck diff --git a/.gitignore b/.gitignore index eb222117..0f33bf29 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ __pycache__/ # Distribution / packaging .Python env/ -.venv build/ develop-eggs/ dist/ @@ -23,9 +22,6 @@ var/ *.egg-info/ .installed.cfg *.egg -*.egg-info/ -.installed.cfg -*.egg **/_version.py # PyInstaller @@ -59,6 +55,7 @@ cov.xml # Sphinx documentation docs/_build/ +docs/_api # PyBuilder target/ @@ -70,17 +67,5 @@ venv* # further build artifacts lockfiles/ -# Editor files -#mac -.DS_Store -*~ - -#vim -*.swp -*.swo - -#pycharm -.idea/* - -#vscode -.vscode/* \ No newline at end of file +# ruff cache +.ruff_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 136b6801..60fc23f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,30 +1,24 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-yaml - id: check-merge-conflict + - id: end-of-file-fixer - repo: local hooks: - - id: black - name: Run black - stages: [commit] + - id: ruff + name: lint with ruff language: system - entry: black --check --diff + entry: ruff check --force-exclude types: [python] + require_serial: true - - id: flake8 - name: Run flake8 - stages: [commit] + - id: ruff-format + name: format with ruff language: system - entry: flake8 - types: [python] - - - id: mypy - name: Run mypy - stages: [commit] - language: system - entry: mypy --install-types --non-interactive + entry: ruff format --force-exclude types: [python] + require_serial: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..c4404eca --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +# The devcontainer should use the developer target and run as root with podman +# or docker with user namespaces. +ARG PYTHON_VERSION=3.11 +FROM python:${PYTHON_VERSION} as developer + +# Add any system dependencies for the developer/build environment here +RUN apt-get update && apt-get install -y --no-install-recommends \ + graphviz \ + && 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 diff --git a/docs/index.rst b/README.md similarity index 52% rename from docs/index.rst rename to README.md index 713e5d9f..bc464410 100644 --- a/docs/index.rst +++ b/README.md @@ -1,25 +1,26 @@ -:html_theme.sidebar_secondary.remove: + -.. include:: ../README.rst - :end-before: when included in index.rst +Data model used by the bluesky ecosystem. -=================================== - Bluesky Event Model Documentation -=================================== +[![CI](https://github.com/bluesky/event-model/actions/workflows/ci.yml/badge.svg)](https://github.com/bluesky/event-model/actions/workflows/ci.yml) +[![Coverage](https://codecov.io/gh/bluesky/event-model/branch/main/graph/badge.svg)](https://codecov.io/gh/bluesky/event-model) +[![PyPI](https://img.shields.io/pypi/v/event-model.svg)](https://pypi.org/project/event-model) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +# Event Model A primary design goal of bluesky is to enable better research by recording rich metadata alongside measured data for use in later analysis. Documents are how we do this. - This repository contains the formal schemas for bluesky's streaming data model and some Python tooling for composing, validating, and transforming documents in the model. -Where is my data? -================= +## Where is my data? -For the full details and schema please see the :ref:`data_model` section. This is a very quick guide to where +For the full details and schema please see the `data_model` section. This is a very quick guide to where you should look for / put different kinds of information * Information about your sample that you know before the measurement → *Start* Document @@ -33,27 +34,6 @@ you should look for / put different kinds of information * Device configuration data → *Event Descriptor* Document in the *configuration* entry -How the documentation is structured -=================================== - -The documentation is split into 2 sections: - -.. grid:: 2 - - .. grid-item-card:: :material-regular:`person;4em` - :link: user/index - :link-type: doc - - The User Guide contains documentation on how to install and use event-model. - - .. grid-item-card:: :material-regular:`code;4em` - :link: developer/index - :link-type: doc - - The Developer Guide contains documentation on how to develop and contribute changes back to event-model. - -.. toctree:: - :hidden: + - user/index - developer/index +See https://bluesky.github.io/event-model for more detailed documentation. diff --git a/README.rst b/README.rst deleted file mode 100644 index 01de2697..00000000 --- a/README.rst +++ /dev/null @@ -1,37 +0,0 @@ -Event Model -=========================== - -|code_ci| |docs_ci| |coverage| |pypi_version| |license| - -============== ============================================================== -PyPI ``pip install event-model`` -Source code https://github.com/bluesky/event-model -Documentation https://blueskyproject.io/event-model -Releases https://github.com/bluesky/event-model/releases -============== ============================================================== - -.. |code_ci| image:: https://github.com/bluesky/event-model/actions/workflows/code.yml/badge.svg?branch=main - :target: https://github.com/bluesky/event-model/actions/workflows/code.yml - :alt: Code CI - -.. |docs_ci| image:: https://github.com/bluesky/event-model/actions/workflows/docs.yml/badge.svg?branch=main - :target: https://github.com/bluesky/event-model/actions/workflows/docs.yml - :alt: Docs CI - -.. |coverage| image:: https://codecov.io/gh/bluesky/event-model/branch/main/graph/badge.svg - :target: https://codecov.io/gh/bluesky/event-model - :alt: Test Coverage - -.. |pypi_version| image:: https://img.shields.io/pypi/v/event-model.svg - :target: https://pypi.org/project/event-model - :alt: Latest PyPI version - -.. |license| image:: https://img.shields.io/badge/License-BSD-blue.svg - :target: https://opensource.org/license/bsd-3-clause/ - :alt: BSD License - -.. - Anything below this line is used when viewing README.rst and will be replaced - when included in index.rst - -See https://blueskyproject.io/event-model for more detailed documentation. diff --git a/docs/_api.rst b/docs/_api.rst new file mode 100644 index 00000000..c2f22557 --- /dev/null +++ b/docs/_api.rst @@ -0,0 +1,16 @@ +:orphan: + +.. + This page is not included in the TOC tree, but must exist so that the + autosummary pages are generated for event_model and all its + subpackages + +API +=== + +.. autosummary:: + :toctree: _api + :template: custom-module-template.rst + :recursive: + + event_model diff --git a/docs/_templates/custom-module-template.rst b/docs/_templates/custom-module-template.rst new file mode 100644 index 00000000..9aeca540 --- /dev/null +++ b/docs/_templates/custom-module-template.rst @@ -0,0 +1,37 @@ +{{ ('``' + fullname + '``') | underline }} + +{%- set filtered_members = [] %} +{%- for item in members %} + {%- if item in functions + classes + exceptions + attributes %} + {% set _ = filtered_members.append(item) %} + {%- endif %} +{%- endfor %} + +.. automodule:: {{ fullname }} + :members: + + {% block modules %} + {% if modules %} + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + {% for item in modules %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block members %} + {% if filtered_members %} + .. rubric:: Members + + .. autosummary:: + :nosignatures: + {% for item in filtered_members %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/docs/conf.py b/docs/conf.py index 7c501e44..88d3a5f6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,8 +1,9 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html +"""Configuration file for the Sphinx documentation builder. + +This file only contains a selection of the most common options. For a full +list see the documentation: +https://www.sphinx-doc.org/en/master/usage/configuration.html +""" import sys from pathlib import Path @@ -19,6 +20,7 @@ copyright = "2019, Brookhaven National Lab" author = "Brookhaven National Lab" +language = "en" # The full version, including alpha/beta/rc tags. release = event_model.__version__ @@ -32,16 +34,11 @@ else: version = release -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - extensions = [ # Use this for generating API docs "sphinx.ext.autodoc", + # and making summary tables at the top of API docs + "sphinx.ext.autosummary", # This can parse google style docstrings "sphinx.ext.napoleon", # For linking to external sphinx documentation @@ -54,6 +51,7 @@ "sphinx_copybutton", # For the card element "sphinx_design", + "sphinx_design", "sphinx.ext.autosummary", "sphinx.ext.mathjax", "sphinx.ext.githubpages", @@ -61,10 +59,14 @@ "sphinx_copybutton", "IPython.sphinxext.ipython_directive", "IPython.sphinxext.ipython_console_highlighting", + # So we can write markdown files + "myst_parser", ] napoleon_google_docstring = False napoleon_numpy_docstring = True +# So we can use the ::: syntax +myst_enable_extensions = ["colon_fence"] # If true, Sphinx will warn about all references where the target cannot # be found. @@ -95,6 +97,12 @@ # Don't inherit docstrings from baseclasses autodoc_inherit_docstrings = False +# Document only what is in __all__ +autosummary_ignore_module_all = False + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + # Output graphviz directive produced images in a scalable format graphviz_output_format = "svg" @@ -102,9 +110,6 @@ # role, that is, for text marked up `like this` default_role = "any" -# The suffix of source filenames. -source_suffix = ".rst" - # The master toctree document. master_doc = "index" @@ -119,7 +124,6 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" -# Example configuration for intersphinx: refer to the Python standard library. # This means you can link things like `str` and `asyncio` to the relevant # docs in the python documentation. intersphinx_mapping = { @@ -133,7 +137,7 @@ } # 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 = """ @@ -196,7 +200,7 @@ # a list of builtin themes. # html_theme = "pydata_sphinx_theme" -github_repo = project +github_repo = "event-model" github_user = "bluesky" switcher_json = f"https://blueskyproject.io/{github_repo}/switcher.json" switcher_exists = requests.get(switcher_json).ok @@ -212,40 +216,41 @@ # Theme options for pydata_sphinx_theme # We don't check switcher because there are 3 possible states for a repo: # 1. New project, docs are not published so there is no switcher -# 2. Existing project with latest skeleton, switcher exists and works -# 3. Existing project with old skeleton that makes broken switcher, +# 2. Existing project with latest copier template, switcher exists and works +# 3. Existing project with old copier template that makes broken switcher, # switcher exists but is broken -# Point 3 makes checking switcher difficult, because the updated skeleton +# Point 3 makes checking switcher difficult, because the updated copier template # will fix the switcher at the end of the docs workflow, but never gets a chance # to complete as the docs build warns and fails. -html_theme_options = dict( - logo=dict( - text=project, - ), - use_edit_page_button=True, - github_url=f"https://github.com/{github_user}/{github_repo}", - icon_links=[ - dict( - name="PyPI", - url=f"https://pypi.org/project/{project}", - icon="fas fa-cube", - ) +html_theme_options = { + "logo": { + "text": project, + }, + "use_edit_page_button": True, + "github_url": f"https://github.com/{github_user}/{github_repo}", + "icon_links": [ + { + "name": "PyPI", + "url": f"https://pypi.org/project/{project}", + "icon": "fas fa-cube", + } ], - switcher=dict( - json_url=switcher_json, - version_match=version, - ), - check_switcher=False, - navbar_end=["theme-switcher", "icon-links", "version-switcher"], -) + "switcher": { + "json_url": switcher_json, + "version_match": version, + }, + "check_switcher": False, + "navbar_end": ["theme-switcher", "icon-links", "version-switcher"], + "navigation_with_keys": False, +} # A dictionary of values to pass into the template engine’s context for all pages -html_context = dict( - github_user=github_user, - github_repo=project, - github_version=version, - doc_path="docs", -) +html_context = { + "github_user": github_user, + "github_repo": github_repo, + "github_version": version, + "doc_path": "docs", +} # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. html_show_sphinx = False @@ -254,5 +259,5 @@ html_show_copyright = False # Logo -html_logo = "images/bluesky-logo-dark.svg" -# html_favicon = "images/dls-favicon.ico" +html_logo = "images/event-model-logo.svg" +html_favicon = html_logo diff --git a/docs/developer/explanations/decisions.rst b/docs/developer/explanations/decisions.rst deleted file mode 100644 index 5841e6ea..00000000 --- a/docs/developer/explanations/decisions.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. This Source Code Form is subject to the terms of the Mozilla Public -.. License, v. 2.0. If a copy of the MPL was not distributed with this -.. file, You can obtain one at http://mozilla.org/MPL/2.0/. - -Architectural Decision Records -============================== - -We record major architectural decisions in Architecture Decision Records (ADRs), -as `described by Michael Nygard -`_. -Below is the list of our current ADRs. - -.. toctree:: - :maxdepth: 1 - :glob: - - decisions/* \ No newline at end of file diff --git a/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst b/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst deleted file mode 100644 index 96217d58..00000000 --- a/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst +++ /dev/null @@ -1,26 +0,0 @@ -1. Record architecture decisions -================================ - -Date: 2023-02-18 - -Status ------- - -Accepted - -Context -------- - -We need to record the architectural decisions made on this project. - -Decision --------- - -We will use Architecture Decision Records, as `described by Michael Nygard -`_. - -Consequences ------------- - -See Michael Nygard's article, linked above. To create new ADRs we will copy and -paste from existing ones. diff --git a/docs/developer/how-to/build-docs.rst b/docs/developer/how-to/build-docs.rst deleted file mode 100644 index a2a55ca3..00000000 --- a/docs/developer/how-to/build-docs.rst +++ /dev/null @@ -1,38 +0,0 @@ -Build the docs using sphinx -=========================== - -You can build the `sphinx`_ based docs from the project directory by running:: - - $ tox -e docs - -This will build the static docs on the ``docs`` directory, which includes API -docs that pull in docstrings from the code. - -.. seealso:: - - `documentation_standards` - -The docs will be built into the ``build/html`` directory, and can be opened -locally with a web browser:: - - $ firefox build/html/index.html - -Autobuild ---------- - -You can also run an autobuild process, which will watch your ``docs`` -directory for changes and rebuild whenever it sees changes, reloading any -browsers watching the pages:: - - $ tox -e docs autobuild - -You can view the pages at localhost:: - - $ firefox http://localhost:8000 - -If you are making changes to source code too, you can tell it to watch -changes in this directory too:: - - $ tox -e docs autobuild -- --watch event_model/ - -.. _sphinx: https://www.sphinx-doc.org/ \ No newline at end of file diff --git a/docs/developer/how-to/contribute.rst b/docs/developer/how-to/contribute.rst deleted file mode 100644 index 65b992f0..00000000 --- a/docs/developer/how-to/contribute.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../.github/CONTRIBUTING.rst diff --git a/docs/developer/how-to/lint.rst b/docs/developer/how-to/lint.rst deleted file mode 100644 index 099dec5f..00000000 --- a/docs/developer/how-to/lint.rst +++ /dev/null @@ -1,33 +0,0 @@ -Run linting using pre-commit -============================ - -Code linting is handled by black_, flake8_ and isort_ run under pre-commit_. - -Running pre-commit ------------------- - -You can run the above checks on all files with this command:: - - $ tox -e pre-commit - -Or you can install a pre-commit hook that will run each time you do a ``git -commit`` on just the files that have changed:: - - $ pre-commit install - -It is also possible to `automatically enable pre-commit on cloned repositories `_. -This will result in pre-commits being enabled on every repo your user clones from now on. - -Fixing issues -------------- - -If black reports an issue you can tell it to reformat all the files in the -repository:: - - $ black . - -Likewise with isort:: - - $ isort . - -If you get any flake8 issues you will have to fix those manually. \ No newline at end of file diff --git a/docs/developer/how-to/make-release.rst b/docs/developer/how-to/make-release.rst deleted file mode 100644 index 230025f0..00000000 --- a/docs/developer/how-to/make-release.rst +++ /dev/null @@ -1,16 +0,0 @@ -Make a release -============== - -To make a new release, please follow this checklist: - -- Choose a new PEP440 compliant release number (see https://peps.python.org/pep-0440/) -- Go to the GitHub release_ page -- Choose ``Draft New Release`` -- Click ``Choose Tag`` and supply the new tag you chose (click create new tag) -- Click ``Generate release notes``, review and edit these notes -- Choose a title and click ``Publish Release`` - -Note that tagging and pushing to the main branch has the same effect except that -you will not get the option to edit the release notes. - -.. _release: https://github.com/bluesky/event-model/releases diff --git a/docs/developer/how-to/pin-requirements.rst b/docs/developer/how-to/pin-requirements.rst deleted file mode 100644 index 89639623..00000000 --- a/docs/developer/how-to/pin-requirements.rst +++ /dev/null @@ -1,74 +0,0 @@ -Pinning Requirements -==================== - -Introduction ------------- - -By design this project only defines dependencies in one place, i.e. in -the ``requires`` table in ``pyproject.toml``. - -In the ``requires`` table it is possible to pin versions of some dependencies -as needed. For library projects it is best to leave pinning to a minimum so -that your library can be used by the widest range of applications. - -When CI builds the project it will use the latest compatible set of -dependencies available (after applying your pins and any dependencies' pins). - -This approach means that there is a possibility that a future build may -break because an updated release of a dependency has made a breaking change. - -The correct way to fix such an issue is to work out the minimum pinning in -``requires`` that will resolve the problem. However this can be quite hard to -do and may be time consuming when simply trying to release a minor update. - -For this reason we provide a mechanism for locking all dependencies to -the same version as a previous successful release. This is a quick fix that -should guarantee a successful CI build. - -Finding the lock files ----------------------- - -Every release of the project will have a set of requirements files published -as release assets. - -For example take a look at the release page for python3-pip-skeleton-cli here: -https://github.com/DiamondLightSource/python3-pip-skeleton-cli/releases/tag/3.3.0 - -There is a list of requirements*.txt files showing as assets on the release. - -There is one file for each time the CI installed the project into a virtual -environment. There are multiple of these as the CI creates a number of -different environments. - -The files are created using ``pip freeze`` and will contain a full list -of the dependencies and sub-dependencies with pinned versions. - -You can download any of these files by clicking on them. It is best to use -the one that ran with the lowest Python version as this is more likely to -be compatible with all the versions of Python in the test matrix. -i.e. ``requirements-test-ubuntu-latest-3.8.txt`` in this example. - -Applying the lock file ----------------------- - -To apply a lockfile: - -- copy the requirements file you have downloaded to the root of your - repository -- rename it to requirements.txt -- commit it into the repo -- push the changes - -The CI looks for a requirements.txt in the root and will pass it to pip -when installing each of the test environments. pip will then install exactly -the same set of packages as the previous release. - -Removing dependency locking from CI ------------------------------------ - -Once the reasons for locking the build have been resolved it is a good idea -to go back to an unlocked build. This is because you get an early indication -of any incoming problems. - -To restore unlocked builds in CI simply remove requirements.txt from the root -of the repo and push. diff --git a/docs/developer/how-to/run-tests.rst b/docs/developer/how-to/run-tests.rst deleted file mode 100644 index d2e03644..00000000 --- a/docs/developer/how-to/run-tests.rst +++ /dev/null @@ -1,12 +0,0 @@ -Run the tests using pytest -========================== - -Testing is done with pytest_. It will find functions in the project that `look -like tests`_, and run them to check for errors. You can run it with:: - - $ tox -e pytest - -It will also report coverage to the commandline and to ``cov.xml``. - -.. _pytest: https://pytest.org/ -.. _look like tests: https://docs.pytest.org/explanation/goodpractices.html#test-discovery diff --git a/docs/developer/how-to/static-analysis.rst b/docs/developer/how-to/static-analysis.rst deleted file mode 100644 index 065920e1..00000000 --- a/docs/developer/how-to/static-analysis.rst +++ /dev/null @@ -1,8 +0,0 @@ -Run static analysis using mypy -============================== - -Static type analysis is done with mypy_. It checks type definition in source -files without running them, and highlights potential issues where types do not -match. You can run it with:: - - $ tox -e mypy diff --git a/docs/developer/how-to/update-tools.rst b/docs/developer/how-to/update-tools.rst deleted file mode 100644 index 7c78f94a..00000000 --- a/docs/developer/how-to/update-tools.rst +++ /dev/null @@ -1,16 +0,0 @@ -Update the tools -================ - -This module is merged with the python3-pip-skeleton_. This is a generic -Python project structure which provides a means to keep tools and -techniques in sync between multiple Python projects. To update to the -latest version of the skeleton, run:: - - $ git pull --rebase=false https://github.com/bluesky/python3-pip-skeleton - -Any merge conflicts will indicate an area where something has changed that -conflicts with the setup of the current module. Check the `closed pull requests -`_ -of the skeleton module for more details. - -.. _python3-pip-skeleton: https://blueskyproject.io/python3-pip-skeleton diff --git a/docs/developer/index.rst b/docs/developer/index.rst deleted file mode 100644 index 08d01270..00000000 --- a/docs/developer/index.rst +++ /dev/null @@ -1,63 +0,0 @@ -Developer Guide -=============== - -Documentation is split into four categories, also accessible from links in the -side-bar. - -.. grid:: 2 - :gutter: 4 - - .. grid-item-card:: :material-regular:`directions_run;3em` - - .. toctree:: - :caption: Tutorials - :maxdepth: 1 - - tutorials/dev-install - - +++ - - Tutorials for getting up and running as a developer. - - .. grid-item-card:: :material-regular:`task;3em` - - .. toctree:: - :caption: How-to Guides - :maxdepth: 1 - - how-to/contribute - how-to/build-docs - how-to/run-tests - how-to/static-analysis - how-to/lint - how-to/update-tools - how-to/make-release - how-to/pin-requirements - - +++ - - Practical step-by-step guides for day-to-day dev tasks. - - .. grid-item-card:: :material-regular:`apartment;3em` - - .. toctree:: - :caption: Explanations - :maxdepth: 1 - - explanations/decisions - - +++ - - Explanations of how and why the architecture is why it is. - - .. grid-item-card:: :material-regular:`description;3em` - - .. toctree:: - :caption: Reference - :maxdepth: 1 - - reference/standards - - +++ - - Technical reference material on standards in use. diff --git a/docs/developer/reference/standards.rst b/docs/developer/reference/standards.rst deleted file mode 100644 index 01265b71..00000000 --- a/docs/developer/reference/standards.rst +++ /dev/null @@ -1,144 +0,0 @@ -Standards -========= - -This document defines the code and documentation standards used in this -repository. - -Code Standards --------------- - -The code in this repository conforms to standards set by the following tools: - -- black_ for code formatting -- flake8_ for style checks -- isort_ for import ordering -- mypy_ for static type checking - -.. seealso:: - - How-to guides `../how-to/lint` and `../how-to/static-analysis` - -.. _documentation_standards: - -Documentation Standards ------------------------ - -Docstrings are pre-processed using the Sphinx Napoleon extension. As such, -numpydoc-style_ is considered as standard for this repository. Please use type -hints in the function signature for types. For example: - -.. code:: python - - def foo(var1, var2, *args, long_var_name="hi", only_seldom_used_keyword=0, **kwargs): - r"""Summarize the function in one line. - - Several sentences providing an extended description. Refer to - variables using back-ticks, e.g. `var`. - - Parameters - ---------- - var1 : array_like - Array_like means all those objects -- lists, nested lists, etc. -- - that can be converted to an array. We can also refer to - variables like `var1`. - var2 : int - The type above can either refer to an actual Python type - (e.g. ``int``), or describe the type of the variable in more - detail, e.g. ``(N,) ndarray`` or ``array_like``. - *args : iterable - Other arguments. - long_var_name : {'hi', 'ho'}, optional - Choices in brackets, default first when optional. - - Returns - ------- - type - Explanation of anonymous return value of type ``type``. - describe : type - Explanation of return value named `describe`. - out : type - Explanation of `out`. - type_without_description - - Other Parameters - ---------------- - only_seldom_used_keyword : int, optional - Infrequently used parameters can be described under this optional - section to prevent cluttering the Parameters section. - **kwargs : dict - Other infrequently used keyword arguments. Note that all keyword - arguments appearing after the first parameter specified under the - Other Parameters section, should also be described under this - section. - - Raises - ------ - BadException - Because you shouldn't have done that. - - See Also - -------- - numpy.array : Relationship (optional). - numpy.ndarray : Relationship (optional), which could be fairly long, in - which case the line wraps here. - numpy.dot, numpy.linalg.norm, numpy.eye - - Notes - ----- - Notes about the implementation algorithm (if needed). - - This can have multiple paragraphs. - - You may include some math: - - .. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n} - - And even use a Greek symbol like :math:`\omega` inline. - - References - ---------- - Cite the relevant literature, e.g. [1]_. You may also cite these - references in the notes section above. - - .. [1] O. McNoleg, "The integration of GIS, remote sensing, - expert systems and adaptive co-kriging for environmental habitat - modelling of the Highland Haggis using object-oriented, fuzzy-logic - and neural-network techniques," Computers & Geosciences, vol. 22, - pp. 585-588, 1996. - - Examples - -------- - These are written in doctest format, and should illustrate how to - use the function. - - >>> a = [1, 2, 3] - >>> print([x + 3 for x in a]) - [4, 5, 6] - >>> print("a\nb") - a - b - - """ - # There should be no blank lines after closing the docstring for functions, - # methods, and modules. - pass - -.. _numpydoc-style: https://numpydoc.readthedocs.io/en/latest/format.html - -Documentation is contained in the ``docs`` directory and extracted from -docstrings of the API. - -Docs follow the underlining convention:: - - Headling 1 (page title) - ======================= - - Heading 2 - --------- - - Heading 3 - ~~~~~~~~~ - -.. seealso:: - - How-to guide `../how-to/build-docs` \ No newline at end of file diff --git a/docs/developer/tutorials/dev-install.rst b/docs/developer/tutorials/dev-install.rst deleted file mode 100644 index 0fc66de5..00000000 --- a/docs/developer/tutorials/dev-install.rst +++ /dev/null @@ -1,45 +0,0 @@ -Developer install -================= - -These instructions will take you through the minimal steps required to get a dev -environment setup, so you can run the tests locally. - -Clone the repository --------------------- - -First clone the repository locally using `Git -`_:: - - $ git clone git://github.com/bluesky/event-model.git - -Install dependencies --------------------- - -You should install into a `venv` (which requires python 3.8 or later): -.. code:: - - $ cd event-model - $ python3 -m venv venv - $ source venv/bin/activate - $ pip install -e '.[dev]' - -See what was installed ----------------------- - -To see a graph of the python package dependency tree type:: - - $ pipdeptree - -Build and test --------------- - -Now you have a development environment you can run the tests in a terminal:: - - $ tox -p - -This will run in parallel the following checks: - -- `../how-to/build-docs` -- `../how-to/run-tests` -- `../how-to/static-analysis` -- `../how-to/lint` diff --git a/docs/explanations.md b/docs/explanations.md new file mode 100644 index 00000000..73ab289b --- /dev/null +++ b/docs/explanations.md @@ -0,0 +1,10 @@ +# Explanations + +Explanations of how it works and why it works that way. + +```{toctree} +:maxdepth: 1 +:glob: + +explanations/* +``` diff --git a/docs/user/explanations/data-model.rst b/docs/explanations/data-model.rst similarity index 95% rename from docs/user/explanations/data-model.rst rename to docs/explanations/data-model.rst index d5efeb7c..162eeaa9 100644 --- a/docs/user/explanations/data-model.rst +++ b/docs/explanations/data-model.rst @@ -59,7 +59,7 @@ Resource and Datum document types manage references to externally-stored data. Example Runs ============ -.. image:: ../../images/document-generation-timeline.svg +.. image:: ../images/document-generation-timeline.svg :width: 100% :align: center @@ -162,7 +162,7 @@ experimetal and subject to backward-incompatible changes in future releases. The run start document formal schema: -.. literalinclude:: ../../../event_model/schemas/run_start.json +.. literalinclude:: ../../src/event_model/schemas/run_start.json .. _descriptor: @@ -241,7 +241,7 @@ Typical example: Formal schema: -.. literalinclude:: ../../../event_model/schemas/event_descriptor.json +.. literalinclude:: ../../src/event_model/schemas/event_descriptor.json .. _event: @@ -298,7 +298,7 @@ overall event 'time' is often more useful. Formal schema: -.. literalinclude:: ../../../event_model/schemas/event.json +.. literalinclude:: ../../src/event_model/schemas/event.json .. _event_page: @@ -326,7 +326,7 @@ the example Event above structured as an Event Page with a single row: Formal Event Page schema: -.. literalinclude:: ../../../event_model/schemas/event_page.json +.. literalinclude:: ../../src/event_model/schemas/event_page.json It is intentional that the values in the "data" and "timestamps" dictionaries do not have structure. The values may be numeric, bool, null (``None``), or a @@ -375,7 +375,7 @@ Typical example: Formal schema: -.. literalinclude:: ../../../event_model/schemas/run_stop.json +.. literalinclude:: ../../src/event_model/schemas/run_stop.json .. _resource: @@ -413,7 +413,7 @@ Typical example: Formal schema: -.. literalinclude:: ../../../event_model/schemas/resource.json +.. literalinclude:: ../../src/event_model/schemas/resource.json .. _datum: @@ -446,7 +446,7 @@ It is an implementation detail that ``datum_id`` is often formatted as Formal schema: -.. literalinclude:: ../../../event_model/schemas/datum.json +.. literalinclude:: ../../src/event_model/schemas/datum.json .. _datum_page: @@ -466,7 +466,7 @@ strucuted as a Datum Page with one row: Formal Datum Page schema: -.. literalinclude:: ../../../event_model/schemas/datum_page.json +.. literalinclude:: ../../src/event_model/schemas/datum_page.json .. _stream_resource: @@ -491,7 +491,7 @@ Typical example: Formal schema: -.. literalinclude:: ../../../event_model/schemas/stream_resource.json +.. literalinclude:: ../../src/event_model/schemas/stream_resource.json .. _stream_datum: @@ -516,7 +516,7 @@ Typical example: Formal schema: -.. literalinclude:: ../../../event_model/schemas/stream_datum.json +.. literalinclude:: ../../src/event_model/schemas/stream_datum.json .. _bulk_events: @@ -526,7 +526,7 @@ Formal schema: This is another representation of Events. This representation is deprecated. Use EventPage instead. -.. literalinclude:: ../../../event_model/schemas/bulk_events.json +.. literalinclude:: ../../src/event_model/schemas/bulk_events.json .. _bulk_datum: @@ -536,4 +536,4 @@ Use EventPage instead. This is another representation of Datum. This representation is deprecated. Use DatumPage instead. -.. literalinclude:: ../../../event_model/schemas/bulk_datum.json +.. literalinclude:: ../../src/event_model/schemas/bulk_datum.json diff --git a/docs/explanations/decisions.md b/docs/explanations/decisions.md new file mode 100644 index 00000000..0533b98d --- /dev/null +++ b/docs/explanations/decisions.md @@ -0,0 +1,12 @@ +# Architectural Decision Records + +Architectural decisions are made throughout a project's lifetime. As a way of keeping track of these decisions, we record these decisions in Architecture Decision Records (ADRs) listed below. + +```{toctree} +:glob: true +:maxdepth: 1 + +decisions/* +``` + +For more information on ADRs see this [blog by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). diff --git a/docs/explanations/decisions/0001-record-architecture-decisions.md b/docs/explanations/decisions/0001-record-architecture-decisions.md new file mode 100644 index 00000000..44d234ef --- /dev/null +++ b/docs/explanations/decisions/0001-record-architecture-decisions.md @@ -0,0 +1,18 @@ +# 1. Record architecture decisions + +## Status + +Accepted + +## Context + +We need to record the architectural decisions made on this project. + +## Decision + +We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. To create new ADRs we will copy and +paste from existing ones. diff --git a/docs/developer/explanations/decisions/0002-switched-to-using-a-python-skeleton.rst b/docs/explanations/decisions/0002-switched-to-using-a-python-skeleton.rst similarity index 96% rename from docs/developer/explanations/decisions/0002-switched-to-using-a-python-skeleton.rst rename to docs/explanations/decisions/0002-switched-to-using-a-python-skeleton.rst index 996eb13f..9ed49e9b 100644 --- a/docs/developer/explanations/decisions/0002-switched-to-using-a-python-skeleton.rst +++ b/docs/explanations/decisions/0002-switched-to-using-a-python-skeleton.rst @@ -29,4 +29,4 @@ We will use the Consequences ------------ -CI workflows will change, even jobs providing the same feature as the older checks. \ No newline at end of file +CI workflows will change, even jobs providing the same feature as the older checks. diff --git a/docs/developer/explanations/decisions/0003-exposed-documents-on-event_model.rst b/docs/explanations/decisions/0003-exposed-documents-on-event_model.rst similarity index 88% rename from docs/developer/explanations/decisions/0003-exposed-documents-on-event_model.rst rename to docs/explanations/decisions/0003-exposed-documents-on-event_model.rst index 73b564e2..d3dfdb4e 100644 --- a/docs/developer/explanations/decisions/0003-exposed-documents-on-event_model.rst +++ b/docs/explanations/decisions/0003-exposed-documents-on-event_model.rst @@ -21,4 +21,4 @@ Accepted Consequences ------------ -Repositories downstream will be able to simplify their imports. \ No newline at end of file +Repositories downstream will be able to simplify their imports. diff --git a/docs/explanations/decisions/0004-switched-to-python-copier-template.md b/docs/explanations/decisions/0004-switched-to-python-copier-template.md new file mode 100644 index 00000000..e2c1d3d8 --- /dev/null +++ b/docs/explanations/decisions/0004-switched-to-python-copier-template.md @@ -0,0 +1,28 @@ +# 4. Adopt python-copier-template for project structure + +## Status + +Accepted + +## Context + +We should use the following [python-copier-template](https://github.com/DiamondLightSource/python-copier-template). +The template will ensure consistency in developer +environments and package management. + +## Decision + +We have switched to using the template. + +## Consequences + +This module will use a fixed set of tools as developed in `python-copier-template` +and can pull from this template to update the packaging to the latest techniques. + +As such, the developer environment may have changed, the following could be +different: + +- linting +- formatting +- pip venv setup +- CI/CD diff --git a/docs/explanations/decisions/COPYME b/docs/explanations/decisions/COPYME new file mode 100644 index 00000000..b466c792 --- /dev/null +++ b/docs/explanations/decisions/COPYME @@ -0,0 +1,19 @@ +# 3. Short descriptive title + +Date: Today's date + +## Status + +Accepted + +## Context + +Background to allow us to make the decision, to show how we arrived at our conclusions. + +## Decision + +What decision we made. + +## Consequences + +What we will do as a result of this decision. diff --git a/docs/user/explanations/event-descriptors.rst b/docs/explanations/event-descriptors.rst similarity index 100% rename from docs/user/explanations/event-descriptors.rst rename to docs/explanations/event-descriptors.rst diff --git a/docs/user/explanations/external.rst b/docs/explanations/external.rst similarity index 100% rename from docs/user/explanations/external.rst rename to docs/explanations/external.rst diff --git a/docs/explanations/schema-generation.rst b/docs/explanations/schema-generation.rst new file mode 100644 index 00000000..8c948503 --- /dev/null +++ b/docs/explanations/schema-generation.rst @@ -0,0 +1,22 @@ +.. _schema_generation: + +***************** +Schema Generation +***************** + +To allow for python typing of documents, we define them as `TypedDict` in `event_model.documents`. + +.. literalinclude:: ../../src/event_model/documents/datum.py + :language: python + +We then use pydantic to convert these python types into the jsonschema in `event_model.schemas`. + +After changing any of the documents it's necessary to regenerate the schemas. This can be done by running: + +.. code-block:: bash + + regenerate-schema + +which is a python environment script in a dev install of event-model. + +This ensures we can have accurate typing across the bluesky codebase, but also doesn't limit us to python for validating documents. diff --git a/docs/genindex.md b/docs/genindex.md new file mode 100644 index 00000000..73f1191b --- /dev/null +++ b/docs/genindex.md @@ -0,0 +1,3 @@ +# Index + + diff --git a/docs/genindex.rst b/docs/genindex.rst deleted file mode 100644 index 93eb8b29..00000000 --- a/docs/genindex.rst +++ /dev/null @@ -1,5 +0,0 @@ -API Index -========= - -.. - https://stackoverflow.com/a/42310803 diff --git a/docs/how-to.md b/docs/how-to.md new file mode 100644 index 00000000..6b161417 --- /dev/null +++ b/docs/how-to.md @@ -0,0 +1,10 @@ +# How-to Guides + +Practical step-by-step guides for the more experienced user. + +```{toctree} +:maxdepth: 1 +:glob: + +how-to/* +``` diff --git a/docs/how-to/contribute.md b/docs/how-to/contribute.md new file mode 100644 index 00000000..6e419797 --- /dev/null +++ b/docs/how-to/contribute.md @@ -0,0 +1,2 @@ +```{include} ../../.github/CONTRIBUTING.md +``` diff --git a/docs/user/how-to/use-cases.rst b/docs/how-to/use-cases.rst similarity index 100% rename from docs/user/how-to/use-cases.rst rename to docs/how-to/use-cases.rst diff --git a/docs/images/bluesky-logo-dark.svg b/docs/images/bluesky-logo-dark.svg deleted file mode 100644 index 0ec00500..00000000 --- a/docs/images/bluesky-logo-dark.svg +++ /dev/null @@ -1 +0,0 @@ -Bluesky_Logo_Final \ No newline at end of file diff --git a/docs/images/document-generation-timeline.svg b/docs/images/document-generation-timeline.svg index fb06851c..edfdb81e 100755 --- a/docs/images/document-generation-timeline.svg +++ b/docs/images/document-generation-timeline.svg @@ -1 +1 @@ -Data_AcquisitionSoftware_Graphic_Rev0617Example 1: Simplest Possible RunExample 2: A Simple ScanRead motor positionand trigger and read detector(s)Triggerand readMove a motorMoveetc.Triggerand readMoveetc.etc.Read motor positionand trigger and read detector(s)Move a motorMonitor beamcurrentRecordnew valueetc.Recordnew valueMonitortemperatureExample 3: Asynchronously Monitor During a ScanRun Start: Metadata about this run, including everything we know in advance: time, type of experiment, sample info., etc.Event Descriptor: Metadata about the readings in the event (units, precision, etc.) and the relevant hardwareEvent: Readings and timestampsRun Stop: Additional metadata known at the end: what time it completed and its exit status (success, aborted, failed)Do nothing - this is the simplest possible experiment! \ No newline at end of file +Data_AcquisitionSoftware_Graphic_Rev0617Example 1: Simplest Possible RunExample 2: A Simple ScanRead motor positionand trigger and read detector(s)Triggerand readMove a motorMoveetc.Triggerand readMoveetc.etc.Read motor positionand trigger and read detector(s)Move a motorMonitor beamcurrentRecordnew valueetc.Recordnew valueMonitortemperatureExample 3: Asynchronously Monitor During a ScanRun Start: Metadata about this run, including everything we know in advance: time, type of experiment, sample info., etc.Event Descriptor: Metadata about the readings in the event (units, precision, etc.) and the relevant hardwareEvent: Readings and timestampsRun Stop: Additional metadata known at the end: what time it completed and its exit status (success, aborted, failed)Do nothing - this is the simplest possible experiment! diff --git a/docs/images/event-model-logo.svg b/docs/images/event-model-logo.svg new file mode 100644 index 00000000..b7190ae3 --- /dev/null +++ b/docs/images/event-model-logo.svg @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..0ecb86f4 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,55 @@ +--- +html_theme.sidebar_secondary.remove: true +--- + +```{include} ../README.md +:end-before: + +::::{grid} 2 +:gutter: 4 + +:::{grid-item-card} {material-regular}`directions_walk;2em` +```{toctree} +:maxdepth: 2 +tutorials +``` ++++ +Tutorials for installation and typical usage. New users start here. +::: + +:::{grid-item-card} {material-regular}`directions;2em` +```{toctree} +:maxdepth: 2 +how-to +``` ++++ +Practical step-by-step guides for the more experienced user. +::: + +:::{grid-item-card} {material-regular}`info;2em` +```{toctree} +:maxdepth: 2 +explanations +``` ++++ +Explanations of how it works and why it works that way. +::: + +:::{grid-item-card} {material-regular}`menu_book;2em` +```{toctree} +:maxdepth: 2 +reference +``` ++++ +Technical reference material including APIs and release notes. +::: + +:::: diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 00000000..44c8ec53 --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,12 @@ +# Reference + +Technical reference material including APIs and release notes. + +```{toctree} +:maxdepth: 1 +:glob: + +API <_api/event_model> +genindex +reference/* +``` diff --git a/docs/user/reference/release-history.rst b/docs/reference/release-history.rst similarity index 100% rename from docs/user/reference/release-history.rst rename to docs/reference/release-history.rst diff --git a/docs/tutorials.md b/docs/tutorials.md new file mode 100644 index 00000000..1fe66c54 --- /dev/null +++ b/docs/tutorials.md @@ -0,0 +1,10 @@ +# Tutorials + +Tutorials for installation and typical usage. New users start here. + +```{toctree} +:maxdepth: 1 +:glob: + +tutorials/* +``` diff --git a/docs/tutorials/installation.md b/docs/tutorials/installation.md new file mode 100644 index 00000000..657f3eb0 --- /dev/null +++ b/docs/tutorials/installation.md @@ -0,0 +1,42 @@ +# Installation + +## Check your version of python + +You will need python 3.8 or later. You can check your version of python by +typing into a terminal: + +``` +$ python3 --version +``` + +## Create a virtual environment + +It is recommended that you install into a “virtual environment” so this +installation will not interfere with any existing Python software: + +``` +$ python3 -m venv /path/to/venv +$ source /path/to/venv/bin/activate +``` + +## Installing the library + +You can now use `pip` to install the library and its dependencies: + +``` +$ python3 -m pip install event-model +``` + +If you require a feature that is not currently released you can also install +from github: + +``` +$ python3 -m pip install git+https://github.com/bluesky/event-model.git +``` + +The library should now be installed and the commandline interface on your path. +You can check the version that has been installed by typing: + +``` +$ event-model --version +``` diff --git a/docs/user/explanations/docs-structure.rst b/docs/user/explanations/docs-structure.rst deleted file mode 100644 index e3487c27..00000000 --- a/docs/user/explanations/docs-structure.rst +++ /dev/null @@ -1,18 +0,0 @@ -About The Documentation ------------------------ - - :material-regular:`format_quote;2em` - - The Grand Unified Theory of Documentation - - -- David Laing - -There is a secret that needs to be understood in order to write good software -documentation: there isn't one thing called *documentation*, there are four. - -They are: *tutorials*, *how-to guides*, *technical reference* and *explanation*. -They represent four different purposes or functions, and require four different -approaches to their creation. Understanding the implications of this will help -improve most documentation - often immensely. - -`More information on this topic. `_ diff --git a/docs/user/index.rst b/docs/user/index.rst deleted file mode 100644 index bf49113a..00000000 --- a/docs/user/index.rst +++ /dev/null @@ -1,61 +0,0 @@ -User Guide -========== - -Documentation is split into four categories, also accessible from links in the -side-bar. - -.. grid:: 2 - :gutter: 4 - - .. grid-item-card:: :material-regular:`directions_walk;3em` - - .. toctree:: - :caption: Tutorials - :maxdepth: 1 - - tutorials/installation - - +++ - - Tutorials for installation and typical usage. New users start here. - - .. grid-item-card:: :material-regular:`directions;3em` - - .. toctree:: - :caption: How-to Guides - :maxdepth: 1 - - how-to/use-cases - - - +++ - - Practical step-by-step guides for the more experienced user. - - .. grid-item-card:: :material-regular:`info;3em` - - .. toctree:: - :caption: Explanations - :maxdepth: 1 - - explanations/docs-structure - explanations/data-model - explanations/external - - +++ - - Explanations of how the library works and why it works that way. - - .. grid-item-card:: :material-regular:`menu_book;3em` - - .. toctree:: - :caption: Reference - :maxdepth: 1 - - reference/api - reference/release-history - ../genindex - - +++ - - Technical reference material including APIs and release notes. diff --git a/docs/user/reference/api.rst b/docs/user/reference/api.rst deleted file mode 100644 index ce598f80..00000000 --- a/docs/user/reference/api.rst +++ /dev/null @@ -1,106 +0,0 @@ -================= -API Documentation -================= - -This is the internal API reference for event_model - -.. data:: event_model.__version__ - :type: str - - Version number as calculated by https://github.com/pypa/setuptools_scm - - -Schemas and Names -================= - -The ``event-model`` Python package contains tooling for composing, validating, -and transforming documents in the model. - -.. autoclass:: event_model.DocumentNames - :members: - :undoc-members: - -There are two dictionaries, :data:`event_model.schemas` and -:data:`event_model.schema_validators`, which are keyed on the members of the -:class:`event_model.DocumentNames` enum and which are mapped, respectively, to -a schema and an associated :class:`jsonschema.IValidator`. - - -Routers -======= - - -.. autoclass:: event_model.RunRouter - :members: - :undoc-members: - -.. autoclass:: event_model.SingleRunDocumentRouter - :members: - :undoc-members: - -.. autoclass:: event_model.DocumentRouter - :members: - :undoc-members: - -.. autoclass:: event_model.Filler - :members: - -.. autoclass:: event_model.NoFiller - :members: - -.. autofunction:: event_model.register_coercion - -.. autofunction:: event_model.as_is - - -.. autofunction:: event_model.force_numpy - - -Document Minting -================ - -To use these functions start with :func:`.compose_run` which will -return a :obj:`.ComposeRunBundle`. - -.. autofunction:: event_model.compose_run - -.. autoclass:: event_model.ComposeRunBundle - -.. autofunction:: event_model.compose_descriptor - -.. autoclass:: event_model.ComposeDescriptorBundle - -.. autofunction:: event_model.compose_event - -.. autofunction:: event_model.compose_event_page - -.. autofunction:: event_model.compose_resource - -.. autoclass:: event_model.ComposeResourceBundle - -.. autofunction:: event_model.compose_datum - -.. autofunction:: event_model.compose_datum_page - - -.. autofunction:: event_model.compose_stop - - -Document Munging -================ - - -.. autofunction:: event_model.pack_event_page - -.. autofunction:: event_model.unpack_event_page - -.. autofunction:: event_model.pack_datum_page - -.. autofunction:: event_model.unpack_datum_page - -.. autofunction:: event_model.sanitize_doc - -.. autofunction:: event_model.verify_filled - -.. autoclass:: event_model.NumpyEncoder - :members: \ No newline at end of file diff --git a/docs/user/tutorials/installation.rst b/docs/user/tutorials/installation.rst deleted file mode 100644 index e4732962..00000000 --- a/docs/user/tutorials/installation.rst +++ /dev/null @@ -1,38 +0,0 @@ -Installation -============ - -Check your version of python ----------------------------- - -You will need python 3.8 or later. You can check your version of python by -typing into a terminal:: - - $ python3 --version - - -Create a virtual environment ----------------------------- - -It is recommended that you install into a “virtual environment” so this -installation will not interfere with any existing Python software:: - - $ python3 -m venv /path/to/venv - $ source /path/to/venv/bin/activate - - -Installing the library ----------------------- - -You can now use ``pip`` to install the library and its dependencies:: - - $ python3 -m pip install event-model - -If you require a feature that is not currently released you can also install -from github:: - - $ python3 -m pip install git+https://github.com/bluesky/event-model.git - -The library should now be installed and the commandline interface on your path. -You can check the version that has been installed by typing:: - - $ python -m event_model --version diff --git a/pyproject.toml b/pyproject.toml index 48c4ae8c..4b3c5e23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=64", "setuptools_scm[toml]>=6.2", "wheel"] +requires = ["setuptools>=64", "setuptools_scm[toml]>=8"] build-backend = "setuptools.build_meta" [project] @@ -13,7 +13,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] -description = "Data model used by the bluesky ecosystem" +description = "Data model used by the bluesky ecosystem." dependencies = [ "importlib-resources", "jsonschema>=4", @@ -22,29 +22,27 @@ dependencies = [ ] dynamic = ["version"] license.file = "LICENSE" -readme = "README.rst" +readme = "README.md" requires-python = ">=3.8" [project.optional-dependencies] dev = [ - "dask[array]", - "black", "mypy", - "flake8", - "flake8-isort", - "Flake8-pyproject", + "myst-parser", "pipdeptree", "pre-commit", "pydata-sphinx-theme>=0.12", "pytest", "pytest-cov", - "sphinx", + "ruff", "sphinx-autobuild", "sphinx-copybutton", "sphinx-design", "tox-direct", "types-mock", - "types-requests", + + # Until https://github.com/copier-org/copier/issues/1819 is released. + "copier==9.3.1", # These are dependencies of various sphinx extensions for documentation. "ipython", @@ -56,7 +54,7 @@ dev = [ ] [project.scripts] -# event_model = "event_model.__main__:main" +regenerate-schema = "event_model.documents.generate.__main__:main" [project.urls] GitHub = "https://github.com/bluesky/event-model" @@ -66,49 +64,28 @@ email = "dallan@bnl.gov" name = "Brookhaven National Lab" [tool.setuptools_scm] +version_file = "src/event_model/_version.py" [tool.mypy] -# Ignore missing stubs in imported modules -ignore_missing_imports = true - - -[tool.isort] -float_to_top = true -profile = "black" - -[tool.flake8] -extend-ignore = [ - # See https://github.com/PyCQA/pycodestyle/issues/373 - "E203", - # support typing.overload decorator - "F811", - # allow Annotated[typ, some_func("some string")] - "F722", - # allow one line class definitions `class X(Y): ...` - # black formats this way, but flake8 is behind - "E701", -] -max-line-length = 88 -exclude = [".tox", ".venv", "venv"] - +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 - --cov=event_model --cov-report term --cov-report xml:cov.xml + --cov=src/event_model --cov-report term --cov-report xml:cov.xml """ # https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings -filterwarnings = [ - "error", - "ignore::DeprecationWarning" -] +filterwarnings = "error" # Doctest python code in docs, python code in src docstrings, test functions in tests -testpaths = "event_model/tests" +testpaths = "src/event_model/tests" + +[tool.coverage.run] +data_file = "/tmp/event_model.coverage" [tool.coverage.paths] # Tests are run from installed location, map back to the src directory -source = ["event_model", "**/site-packages/"] +source = ["src", "**/site-packages/"] # tox must currently be configured via an embedded ini string # See: https://github.com/tox-dev/tox/issues/999 @@ -117,7 +94,7 @@ legacy_tox_ini = """ [tox] skipsdist=True -[testenv:{pre-commit,mypy,pytest,docs}] +[testenv:{pre-commit,type-checking,tests,docs}] # Don't create a virtualenv for the command, requires tox-direct plugin direct = True passenv = * @@ -128,8 +105,27 @@ allowlist_externals = sphinx-build sphinx-autobuild commands = - pytest: pytest {posargs} - mypy: mypy event_model {posargs} - pre-commit: pre-commit run --all-files {posargs} + pre-commit: pre-commit run --all-files --show-diff-on-failure {posargs} + type-checking: mypy src/event_model {posargs} + tests: pytest --cov=src/event_model --cov-report term --cov-report xml:cov.xml {posargs} docs: sphinx-{posargs:build -E --keep-going} -T docs build/html """ + +[tool.ruff] +src = ["src"] +line-length = 88 +lint.select = [ + "B", # flake8-bugbear - https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + "C4", # flake8-comprehensions - https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + "E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e + "F", # pyflakes rules - https://docs.astral.sh/ruff/rules/#pyflakes-f + "W", # pycodestyle warnings - https://docs.astral.sh/ruff/rules/#warning-w + "I", # isort - https://docs.astral.sh/ruff/rules/#isort-i + "UP", # pyupgrade - https://docs.astral.sh/ruff/rules/#pyupgrade-up + "SLF", # self - https://docs.astral.sh/ruff/settings/#lintflake8-self +] + +[tool.ruff.lint.per-file-ignores] +# By default, private member access is allowed in tests +# See https://github.com/DiamondLightSource/python-copier-template/issues/154 +"src/event_model/tests/**" = ["SLF001"] diff --git a/event_model/__init__.py b/src/event_model/__init__.py similarity index 97% rename from event_model/__init__.py rename to src/event_model/__init__.py index ac3c466a..3a255c7e 100644 --- a/event_model/__init__.py +++ b/src/event_model/__init__.py @@ -13,7 +13,6 @@ from collections import defaultdict, deque from dataclasses import dataclass from enum import Enum -from importlib.metadata import version as importlib_version from typing import ( Any, Callable, @@ -58,10 +57,7 @@ else: import importlib.resources as importlib_resources -__version__ = importlib_version("event-model") - -del importlib_version - +from ._version import __version__ __all__ = [ # Document types @@ -152,10 +148,10 @@ def __init__(self, *, emit: Optional[Callable] = None) -> None: try: # Does this function accept two positional arguments? sig.bind(None, None) - except TypeError: + except TypeError as error: raise ValueError( "emit must accept two positional arguments, name and doc" - ) + ) from error # Stash a weak reference to `emit`. if inspect.ismethod(emit): self._emit_ref = weakref.WeakMethod(emit) @@ -299,7 +295,8 @@ def bulk_events(self, doc: dict) -> None: # Do not modify this in a subclass. Use event_page. warnings.warn( "The document type 'bulk_events' has been deprecated in favor of " - "'event_page', whose structure is a transpose of 'bulk_events'." + "'event_page', whose structure is a transpose of 'bulk_events'.", + stacklevel=2, ) for page in bulk_events_to_event_pages(doc): self.event_page(page) @@ -308,7 +305,8 @@ def bulk_datum(self, doc: dict) -> None: # Do not modify this in a subclass. Use event_page. warnings.warn( "The document type 'bulk_datum' has been deprecated in favor of " - "'datum_page', whose structure is a transpose of 'bulk_datum'." + "'datum_page', whose structure is a transpose of 'bulk_datum'.", + stacklevel=2, ) self.datum_page(bulk_datum_to_datum_page(doc)) @@ -321,7 +319,7 @@ class SingleRunDocumentRouter(DocumentRouter): def __init__(self) -> None: super().__init__() self._start_doc: Optional[dict] = None - self._descriptors: dict = dict() + self._descriptors: dict = {} def __call__( self, name: str, doc: dict, validate: bool = False @@ -657,26 +655,29 @@ def __init__( stream_resource_cache: Optional[dict] = None, stream_datum_cache: Optional[dict] = None, inplace: Optional[bool] = None, - retry_intervals: List = [ - 0.001, - 0.002, - 0.004, - 0.008, - 0.016, - 0.032, - 0.064, - 0.128, - 0.256, - 0.512, - 1.024, - ], + retry_intervals: Optional[List[float]] = None, ) -> None: + if retry_intervals is None: + retry_intervals = [ + 0.001, + 0.002, + 0.004, + 0.008, + 0.016, + 0.032, + 0.064, + 0.128, + 0.256, + 0.512, + 1.024, + ] if inplace is None: self._inplace = True warnings.warn( "'inplace' argument not specified. It is recommended to " "specify True or False. In future releases, 'inplace' " - "will default to False." + "will default to False.", + stacklevel=2, ) else: self._inplace = inplace @@ -689,11 +690,11 @@ def __init__( ) try: self._coercion_func = _coercion_registry[coerce] - except KeyError: + except KeyError as error: raise EventModelKeyError( f"The option coerce={coerce!r} was given to event_model.Filler. " f"The valid options are {set(_coercion_registry)}." - ) + ) from error self._coerce = coerce # See comments on coerision functions above for the use of @@ -710,6 +711,7 @@ def __init__( "In a future release of event-model, the argument `include` " "will be removed from Filler.", DeprecationWarning, + stacklevel=2, ) self.include = include if exclude is not None: @@ -717,6 +719,7 @@ def __init__( "In a future release of event-model, the argument `exclude` " "will be removed from Filler.", DeprecationWarning, + stacklevel=2, ) self.exclude = exclude self.root_map = root_map or {} @@ -761,21 +764,21 @@ def __eq__(self, other: Any) -> bool: ) def __getstate__(self) -> dict: - return dict( - inplace=self._inplace, - coercion_func=self._coerce, - handler_registry=self._unpatched_handler_registry, - include=self.include, - exclude=self.exclude, - root_map=self.root_map, - handler_cache=self._handler_cache, - resource_cache=self._resource_cache, - datum_cache=self._datum_cache, - descriptor_cache=self._descriptor_cache, - stream_resource_cache=self._stream_resource_cache, - stream_datum_cache=self._stream_datum_cache, - retry_intervals=self.retry_intervals, - ) + return { + "inplace": self._inplace, + "coercion_func": self._coerce, + "handler_registry": self._unpatched_handler_registry, + "include": self.include, + "exclude": self.exclude, + "root_map": self.root_map, + "handler_cache": self._handler_cache, + "resource_cache": self._resource_cache, + "datum_cache": self._datum_cache, + "descriptor_cache": self._descriptor_cache, + "stream_resource_cache": self._stream_resource_cache, + "stream_datum_cache": self._stream_datum_cache, + "retry_intervals": self.retry_intervals, + } def __setstate__(self, d: dict) -> None: self._inplace = d["inplace"] @@ -1517,7 +1520,7 @@ def __init__( # Map RunStart UID to RunStart document. This is used to send # RunStart documents to subfactory callbacks. - self._start_to_start_doc: dict = dict() + self._start_to_start_doc: dict = {} # Map RunStart UID to the list EventDescriptor. This is used to # facilitate efficient cleanup of the caches above. @@ -1577,7 +1580,8 @@ def start(self, start_doc: RunStart) -> None: warnings.warn( DOCS_PASSED_IN_1_14_0_WARNING.format( callback=callback, name="start", err=err - ) + ), + stacklevel=2, ) raise err self._factory_cbs_by_start[uid].extend(callbacks) @@ -1613,7 +1617,8 @@ def descriptor(self, descriptor_doc: EventDescriptor) -> None: warnings.warn( DOCS_PASSED_IN_1_14_0_WARNING.format( callback=callback, name="start", err=err - ) + ), + stacklevel=2, ) raise err try: @@ -1622,7 +1627,8 @@ def descriptor(self, descriptor_doc: EventDescriptor) -> None: warnings.warn( DOCS_PASSED_IN_1_14_0_WARNING.format( callback=callback, name="descriptor", err=err - ) + ), + stacklevel=2, ) raise err @@ -1643,12 +1649,12 @@ def datum_page(self, doc: DatumPage) -> None: resource_uid = doc["resource"] try: start_uid = self._resources[resource_uid] - except KeyError: + except KeyError as error: if resource_uid not in self._unlabeled_resources: raise UnresolvableForeignKeyError( resource_uid, f"DatumPage refers to unknown Resource uid {resource_uid}", - ) + ) from error # Old Resources do not have a reference to a RunStart document, # so in turn we cannot immediately tell which run these datum # documents belong to. @@ -1859,7 +1865,7 @@ def __call__(self, datum_kwargs: Dict[str, Any], validate: bool = True) -> Datum doc = Datum( resource=resource_uid, datum_kwargs=datum_kwargs, - datum_id="{}/{}".format(resource_uid, next(self.counter)), + datum_id=f"{resource_uid}/{next(self.counter)}", ) if validate: schema_validators[DocumentNames.datum].validate(doc) @@ -1891,9 +1897,7 @@ def __call__(self, datum_kwargs: dict, validate: bool = True) -> DatumPage: doc = DatumPage( resource=resource_uid, datum_kwargs=datum_kwargs, - datum_id=[ - "{}/{}".format(resource_uid, next(self.counter)) for _ in range(N) - ], + datum_id=[f"{resource_uid}/{next(self.counter)}" for _ in range(N)], ) if validate: schema_validators[DocumentNames.datum_page].validate(doc) @@ -2049,6 +2053,7 @@ def compose_stream_datum( warnings.warn( "compose_stream_datum() will be removed in the minor version.", DeprecationWarning, + stacklevel=2, ) return ComposeStreamDatum(stream_resource, counter)( seq_nums, @@ -2150,8 +2155,9 @@ def __call__( ) -> RunStop: if self.poison_pill: raise EventModelError( - "Already composed a RunStop document for run " - "{!r}.".format(self.start["uid"]) + "Already composed a RunStop document for run " "{!r}.".format( + self.start["uid"] + ) ) self.poison_pill.append(object()) if uid is None: @@ -2194,7 +2200,7 @@ def compose_stop( def length_of_value(dictionary: Dict[str, List], error_msg: str) -> Optional[int]: length = None - for k, v in dictionary.items(): + for _, v in dictionary.items(): v_len = len(v) if length is not None: if v_len != length: @@ -2280,8 +2286,9 @@ def __call__( ) if set(filled) - set(data): raise EventModelValidationError( - "Keys in event['filled'] {} must be a subset of those in " - "event['data'] {}".format(filled.keys(), data.keys()) + f"Keys in event['filled'] {filled.keys()} " + "must be a subset of those in " + f"event['data'] {data.keys()}" ) self.event_counters[self.descriptor["name"]] += len(seq_num) return doc @@ -2382,8 +2389,9 @@ def __call__( ) if set(filled) - set(data): raise EventModelValidationError( - "Keys in event['filled'] {} must be a subset of those in " - "event['data'] {}".format(filled.keys(), data.keys()) + f"Keys in event['filled'] {filled.keys()} " + "must be a subset of those in " + f"event['data'] {data.keys()}" ) self.event_counters[self.descriptor["name"]] = seq_num + 1 return doc @@ -2472,10 +2480,10 @@ def __call__( if validate: if name in self.streams and self.streams[name] != set(data_keys): raise EventModelValidationError( - "A descriptor with the name {} has already been composed with " - "data_keys {}. The requested data_keys were {}. All " - "descriptors in a given stream must have the same " - "data_keys.".format(name, self.streams[name], set(data_keys)) + f"A descriptor with the name {name} has already been composed with " + f"data_keys {self.streams[name]}. The requested data_keys were " + f"{set(data_keys)}. All descriptors in a given stream must have " + "the same data_keys." ) schema_validators[DocumentNames.descriptor].validate(doc) @@ -2830,20 +2838,20 @@ def merge_event_pages(event_pages: Iterable[EventPage]) -> EventPage: if len(pages) == 1: return pages[0] - doc = dict( - descriptor=pages[0]["descriptor"], - seq_num=list( + doc = { + "descriptor": pages[0]["descriptor"], + "seq_num": list( itertools.chain.from_iterable([page["seq_num"] for page in pages]) ), - time=list(itertools.chain.from_iterable([page["time"] for page in pages])), - uid=list(itertools.chain.from_iterable([page["uid"] for page in pages])), - data={ + "time": list(itertools.chain.from_iterable([page["time"] for page in pages])), + "uid": list(itertools.chain.from_iterable([page["uid"] for page in pages])), + "data": { key: list( itertools.chain.from_iterable([page["data"][key] for page in pages]) ) for key in pages[0]["data"].keys() }, - timestamps={ + "timestamps": { key: list( itertools.chain.from_iterable( [page["timestamps"][key] for page in pages] @@ -2851,13 +2859,13 @@ def merge_event_pages(event_pages: Iterable[EventPage]) -> EventPage: ) for key in pages[0]["data"].keys() }, - filled={ + "filled": { key: list( itertools.chain.from_iterable([page["filled"][key] for page in pages]) ) for key in pages[0]["filled"].keys() }, - ) + } return cast(EventPage, doc) @@ -3005,6 +3013,7 @@ def bulk_datum_to_datum_page(bulk_datum: dict) -> DatumPage: Note: There is only one known usage of BulkDatum "in the wild", and the BulkDatum layout has been deprecated in favor of DatumPage. """ + datum_page = DatumPage( datum_id=bulk_datum["datum_ids"], resource=bulk_datum["resource"], diff --git a/event_model/__main__.py b/src/event_model/__main__.py similarity index 76% rename from event_model/__main__.py rename to src/event_model/__main__.py index cc35ce8a..5f25bcc4 100644 --- a/event_model/__main__.py +++ b/src/event_model/__main__.py @@ -1,6 +1,6 @@ from argparse import ArgumentParser -from event_model import __version__ +from . import __version__ __all__ = ["main"] @@ -11,6 +11,5 @@ def main(args=None): args = parser.parse_args(args) -# test with: python -m python3_pip_skeleton if __name__ == "__main__": main() diff --git a/event_model/documents/__init__.py b/src/event_model/documents/__init__.py similarity index 100% rename from event_model/documents/__init__.py rename to src/event_model/documents/__init__.py diff --git a/event_model/documents/datum.py b/src/event_model/documents/datum.py similarity index 100% rename from event_model/documents/datum.py rename to src/event_model/documents/datum.py diff --git a/event_model/documents/datum_page.py b/src/event_model/documents/datum_page.py similarity index 100% rename from event_model/documents/datum_page.py rename to src/event_model/documents/datum_page.py diff --git a/event_model/documents/event.py b/src/event_model/documents/event.py similarity index 100% rename from event_model/documents/event.py rename to src/event_model/documents/event.py diff --git a/event_model/documents/event_descriptor.py b/src/event_model/documents/event_descriptor.py similarity index 100% rename from event_model/documents/event_descriptor.py rename to src/event_model/documents/event_descriptor.py diff --git a/event_model/documents/event_page.py b/src/event_model/documents/event_page.py similarity index 100% rename from event_model/documents/event_page.py rename to src/event_model/documents/event_page.py diff --git a/event_model/documents/generate/__main__.py b/src/event_model/documents/generate/__main__.py similarity index 92% rename from event_model/documents/generate/__main__.py rename to src/event_model/documents/generate/__main__.py index f111e24f..f457bec2 100644 --- a/event_model/documents/generate/__main__.py +++ b/src/event_model/documents/generate/__main__.py @@ -3,7 +3,12 @@ from event_model.documents import ALL_DOCUMENTS from event_model.documents.generate.typeddict_to_schema import typeddict_to_schema -if __name__ == "__main__": + +def main(): schema_dir = Path(__file__).parent.parent.parent / "schemas" for document in ALL_DOCUMENTS: typeddict_to_schema(document, schema_dir) + + +if __name__ == "__main__": + main() diff --git a/event_model/documents/generate/type_wrapper.py b/src/event_model/documents/generate/type_wrapper.py similarity index 100% rename from event_model/documents/generate/type_wrapper.py rename to src/event_model/documents/generate/type_wrapper.py diff --git a/event_model/documents/generate/typeddict_to_schema.py b/src/event_model/documents/generate/typeddict_to_schema.py similarity index 97% rename from event_model/documents/generate/typeddict_to_schema.py rename to src/event_model/documents/generate/typeddict_to_schema.py index c153bc3c..45bf11c4 100644 --- a/event_model/documents/generate/typeddict_to_schema.py +++ b/src/event_model/documents/generate/typeddict_to_schema.py @@ -15,7 +15,7 @@ def sort_alphabetically(schema: Dict) -> Dict: """Sorts the schema alphabetically by key name, exchanging the properties dicts for OrderedDicts""" - schema = OrderedDict(sorted(list(schema.items()), key=lambda x: x[0])) + schema = OrderedDict(sorted(schema.items(), key=lambda x: x[0])) return schema @@ -36,7 +36,7 @@ def sort_schema(document_schema: Dict) -> Dict: assert isinstance(document_schema, dict) document_schema = OrderedDict( sorted( - list(document_schema.items()), + document_schema.items(), key=lambda x: SortOrder.get(x[0], len(SortOrder)), ) ) diff --git a/event_model/documents/resource.py b/src/event_model/documents/resource.py similarity index 100% rename from event_model/documents/resource.py rename to src/event_model/documents/resource.py diff --git a/event_model/documents/run_start.py b/src/event_model/documents/run_start.py similarity index 100% rename from event_model/documents/run_start.py rename to src/event_model/documents/run_start.py diff --git a/event_model/documents/run_stop.py b/src/event_model/documents/run_stop.py similarity index 100% rename from event_model/documents/run_stop.py rename to src/event_model/documents/run_stop.py diff --git a/event_model/documents/stream_datum.py b/src/event_model/documents/stream_datum.py similarity index 100% rename from event_model/documents/stream_datum.py rename to src/event_model/documents/stream_datum.py diff --git a/event_model/documents/stream_resource.py b/src/event_model/documents/stream_resource.py similarity index 100% rename from event_model/documents/stream_resource.py rename to src/event_model/documents/stream_resource.py diff --git a/event_model/schemas/bulk_datum.json b/src/event_model/schemas/bulk_datum.json similarity index 100% rename from event_model/schemas/bulk_datum.json rename to src/event_model/schemas/bulk_datum.json diff --git a/event_model/schemas/bulk_events.json b/src/event_model/schemas/bulk_events.json similarity index 100% rename from event_model/schemas/bulk_events.json rename to src/event_model/schemas/bulk_events.json diff --git a/event_model/schemas/datum.json b/src/event_model/schemas/datum.json similarity index 99% rename from event_model/schemas/datum.json rename to src/event_model/schemas/datum.json index ac0a9999..a85c6cf8 100644 --- a/event_model/schemas/datum.json +++ b/src/event_model/schemas/datum.json @@ -25,4 +25,4 @@ "resource" ], "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/schemas/datum_page.json b/src/event_model/schemas/datum_page.json similarity index 99% rename from event_model/schemas/datum_page.json rename to src/event_model/schemas/datum_page.json index 7d34f988..768adb26 100644 --- a/event_model/schemas/datum_page.json +++ b/src/event_model/schemas/datum_page.json @@ -37,4 +37,4 @@ "resource" ], "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/schemas/event.json b/src/event_model/schemas/event.json similarity index 99% rename from event_model/schemas/event.json rename to src/event_model/schemas/event.json index bc175b93..af2f6eff 100644 --- a/event_model/schemas/event.json +++ b/src/event_model/schemas/event.json @@ -58,4 +58,4 @@ "uid" ], "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/schemas/event_descriptor.json b/src/event_model/schemas/event_descriptor.json similarity index 99% rename from event_model/schemas/event_descriptor.json rename to src/event_model/schemas/event_descriptor.json index 91968ef1..e453536a 100644 --- a/event_model/schemas/event_descriptor.json +++ b/src/event_model/schemas/event_descriptor.json @@ -278,4 +278,4 @@ } }, "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/schemas/event_page.json b/src/event_model/schemas/event_page.json similarity index 99% rename from event_model/schemas/event_page.json rename to src/event_model/schemas/event_page.json index 7c987427..0469c607 100644 --- a/event_model/schemas/event_page.json +++ b/src/event_model/schemas/event_page.json @@ -81,4 +81,4 @@ "uid" ], "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/schemas/resource.json b/src/event_model/schemas/resource.json similarity index 99% rename from event_model/schemas/resource.json rename to src/event_model/schemas/resource.json index ccbb4924..1a3b6d9d 100644 --- a/event_model/schemas/resource.json +++ b/src/event_model/schemas/resource.json @@ -51,4 +51,4 @@ "uid" ], "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/schemas/run_start.json b/src/event_model/schemas/run_start.json similarity index 99% rename from event_model/schemas/run_start.json rename to src/event_model/schemas/run_start.json index 852e44b1..d698d5cc 100644 --- a/event_model/schemas/run_start.json +++ b/src/event_model/schemas/run_start.json @@ -357,4 +357,4 @@ } }, "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/schemas/run_stop.json b/src/event_model/schemas/run_stop.json similarity index 99% rename from event_model/schemas/run_stop.json rename to src/event_model/schemas/run_stop.json index b0d64458..f401bcea 100644 --- a/event_model/schemas/run_stop.json +++ b/src/event_model/schemas/run_stop.json @@ -63,4 +63,4 @@ } }, "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/schemas/stream_datum.json b/src/event_model/schemas/stream_datum.json similarity index 99% rename from event_model/schemas/stream_datum.json rename to src/event_model/schemas/stream_datum.json index 75b53ed7..35204f6a 100644 --- a/event_model/schemas/stream_datum.json +++ b/src/event_model/schemas/stream_datum.json @@ -58,4 +58,4 @@ "uid" ], "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/schemas/stream_resource.json b/src/event_model/schemas/stream_resource.json similarity index 99% rename from event_model/schemas/stream_resource.json rename to src/event_model/schemas/stream_resource.json index 3eb93b23..440768c3 100644 --- a/event_model/schemas/stream_resource.json +++ b/src/event_model/schemas/stream_resource.json @@ -42,4 +42,4 @@ "uri" ], "additionalProperties": false -} \ No newline at end of file +} diff --git a/event_model/tests/__init__.py b/src/event_model/tests/__init__.py similarity index 100% rename from event_model/tests/__init__.py rename to src/event_model/tests/__init__.py diff --git a/event_model/tests/test_auth.py b/src/event_model/tests/test_auth.py similarity index 100% rename from event_model/tests/test_auth.py rename to src/event_model/tests/test_auth.py diff --git a/event_model/tests/test_em.py b/src/event_model/tests/test_em.py similarity index 100% rename from event_model/tests/test_em.py rename to src/event_model/tests/test_em.py diff --git a/event_model/tests/test_emit.py b/src/event_model/tests/test_emit.py similarity index 100% rename from event_model/tests/test_emit.py rename to src/event_model/tests/test_emit.py diff --git a/event_model/tests/test_filler.py b/src/event_model/tests/test_filler.py similarity index 100% rename from event_model/tests/test_filler.py rename to src/event_model/tests/test_filler.py diff --git a/event_model/tests/test_projections.py b/src/event_model/tests/test_projections.py similarity index 100% rename from event_model/tests/test_projections.py rename to src/event_model/tests/test_projections.py diff --git a/event_model/tests/test_run_router.py b/src/event_model/tests/test_run_router.py similarity index 99% rename from event_model/tests/test_run_router.py rename to src/event_model/tests/test_run_router.py index b25b6391..29a269bb 100644 --- a/event_model/tests/test_run_router.py +++ b/src/event_model/tests/test_run_router.py @@ -340,7 +340,7 @@ def exception_callback_factory(start_doc_name, start_doc): rr("start", start_doc) descriptor_doc = {"run_start": "abcdef", "uid": "ghijkl"} - with pytest.raises(Exception): + with pytest.raises(UserWarning): rr("descriptor", descriptor_doc) assert rr._start_to_descriptors["abcdef"] == ["ghijkl"] diff --git a/event_model/tests/test_schema_generation.py b/src/event_model/tests/test_schema_generation.py similarity index 94% rename from event_model/tests/test_schema_generation.py rename to src/event_model/tests/test_schema_generation.py index 19a1fb8e..45f6a766 100644 --- a/event_model/tests/test_schema_generation.py +++ b/src/event_model/tests/test_schema_generation.py @@ -23,10 +23,10 @@ def test_generated_json_matches_typed_dict(typed_dict_class, tmpdir): with open(generated_file_path) as generated_file, open(old_file_path) as old_file: try: assert json.load(generated_file) == json.load(old_file) - except AssertionError: + except AssertionError as error: raise Exception( f"`{typed_dict_class.__name__}` can generate a json schema, but " f"it doesn't match the schema in `{SCHEMA_PATH}`. Did you forget " "to run `python event_model/documents/generate` after changes " f"to `{typed_dict_class.__name__}`?" - ) + ) from error