mirror of
https://github.com/bec-project/ophyd_devices.git
synced 2025-06-06 20:00:41 +02:00
ci: add github actions
This commit is contained in:
parent
a44b07c5da
commit
a458d6942d
41
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: File a bug report.
|
||||||
|
title: "[BUG]: "
|
||||||
|
labels: ["bug"]
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Bug report:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Provide a brief description of the bug.
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Describe what you expected to happen and what actually happened.
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: Outline the steps that lead to the bug's occurrence. Be specific and provide a clear sequence of actions.
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: bec_widgets version
|
||||||
|
description: which version of BEC widgets was running?
|
||||||
|
- type: input
|
||||||
|
id: bec-version
|
||||||
|
attributes:
|
||||||
|
label: bec core version
|
||||||
|
description: which version of BEC core was running?
|
||||||
|
- type: textarea
|
||||||
|
id: extra
|
||||||
|
attributes:
|
||||||
|
label: Any extra info / data? e.g. log output...
|
||||||
|
- type: input
|
||||||
|
id: issues
|
||||||
|
attributes:
|
||||||
|
label: Related issues
|
||||||
|
description: please tag any related issues
|
37
.github/ISSUE_TEMPLATE/documentation_update.md
vendored
Normal file
37
.github/ISSUE_TEMPLATE/documentation_update.md
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: Documentation update request
|
||||||
|
about: Suggest an update to the docs
|
||||||
|
title: '[DOCS]: '
|
||||||
|
type: documentation
|
||||||
|
label: documentation
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation Section
|
||||||
|
|
||||||
|
[Specify the section or page of the documentation that needs updating]
|
||||||
|
|
||||||
|
## Current Information
|
||||||
|
|
||||||
|
[Provide the current information in the documentation that needs to be updated]
|
||||||
|
|
||||||
|
## Proposed Update
|
||||||
|
|
||||||
|
[Describe the proposed update or correction. Be specific about the changes that need to be made]
|
||||||
|
|
||||||
|
## Reason for Update
|
||||||
|
|
||||||
|
[Explain the reason for the documentation update. Include any recent changes, new features, or corrections that necessitate the update]
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
|
||||||
|
[Include any additional context or information that can help the documentation team understand the update better]
|
||||||
|
|
||||||
|
## Attachments
|
||||||
|
|
||||||
|
[Attach any files, screenshots, or references that can assist in making the documentation update]
|
||||||
|
|
||||||
|
## Priority
|
||||||
|
|
||||||
|
[Assign a priority level to the documentation update based on its urgency. Use a scale such as Low, Medium, High]
|
49
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
49
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: '[FEAT]: '
|
||||||
|
type: feature
|
||||||
|
label: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Summary
|
||||||
|
|
||||||
|
[Provide a brief and clear summary of the new feature you are requesting]
|
||||||
|
|
||||||
|
## Problem Description
|
||||||
|
|
||||||
|
[Explain the problem or need that this feature aims to address. Be specific about the issues or gaps in the current functionality]
|
||||||
|
|
||||||
|
## Use Case
|
||||||
|
|
||||||
|
[Describe a real-world scenario or use case where this feature would be beneficial. Explain how it would improve the user experience or workflow]
|
||||||
|
|
||||||
|
## Proposed Solution
|
||||||
|
|
||||||
|
[If you have a specific solution in mind, describe it here. Explain how it would work and how it would address the problem described above]
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
[Explain the benefits and advantages of implementing this feature. Highlight how it adds value to the product or improves user satisfaction]
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
[If you've considered alternative solutions or workarounds, mention them here. Explain why the proposed feature is the preferred option]
|
||||||
|
|
||||||
|
## Impact on Existing Functionality
|
||||||
|
|
||||||
|
[Discuss how the new feature might impact or interact with existing features. Address any potential conflicts or dependencies]
|
||||||
|
|
||||||
|
## Priority
|
||||||
|
|
||||||
|
[Assign a priority level to the feature request based on its importance. Use a scale such as Low, Medium, High]
|
||||||
|
|
||||||
|
## Attachments
|
||||||
|
|
||||||
|
[Include any relevant attachments, such as sketches, diagrams, or references that can help the development team understand your feature request better]
|
||||||
|
|
||||||
|
## Additional Information
|
||||||
|
|
||||||
|
[Provide any additional information that might be relevant to the feature request, such as user feedback, market trends, or similar features in other products]
|
33
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal file
33
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
## Description
|
||||||
|
|
||||||
|
[Provide a brief description of the changes introduced by this pull request.]
|
||||||
|
|
||||||
|
## Related Issues
|
||||||
|
|
||||||
|
[Cite any related issues or feature requests that are addressed or resolved by this pull request. Link the associated issue, for example, with `fixes #123` or `closes #123`.]
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
|
||||||
|
- Change 1
|
||||||
|
- Change 2
|
||||||
|
|
||||||
|
## How to test
|
||||||
|
|
||||||
|
- Run unit tests
|
||||||
|
- Open [widget] in designer and play around with the properties
|
||||||
|
|
||||||
|
## Potential side effects
|
||||||
|
|
||||||
|
[Describe any potential side effects or risks of merging this PR.]
|
||||||
|
|
||||||
|
## Screenshots / GIFs (if applicable)
|
||||||
|
|
||||||
|
[Include any relevant screenshots or GIFs to showcase the changes made.]
|
||||||
|
|
||||||
|
## Additional Comments
|
||||||
|
|
||||||
|
[Add any additional comments or information that may be helpful for reviewers.]
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Documentation is up-to-date.
|
||||||
|
|
45
.github/actions/ophyd_devices_install/action.yml
vendored
Normal file
45
.github/actions/ophyd_devices_install/action.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
name: "Ophyd Devices Install"
|
||||||
|
description: "Install BEC Widgets and related os dependencies"
|
||||||
|
inputs:
|
||||||
|
BEC_CORE_BRANCH: # id of input
|
||||||
|
required: false
|
||||||
|
default: "main"
|
||||||
|
description: "Branch of BEC Core to install"
|
||||||
|
OPHYD_DEVICES_BRANCH: # id of input
|
||||||
|
required: false
|
||||||
|
default: "main"
|
||||||
|
description: "Branch of Ophyd Devices to install"
|
||||||
|
PYTHON_VERSION: # id of input
|
||||||
|
required: false
|
||||||
|
default: "3.11"
|
||||||
|
description: "Python version to use"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ inputs.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- name: Checkout BEC Core
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: bec-project/bec
|
||||||
|
ref: ${{ inputs.BEC_CORE_BRANCH }}
|
||||||
|
path: ./bec
|
||||||
|
|
||||||
|
- name: Checkout Ophyd Devices
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: bec-project/ophyd_devices
|
||||||
|
ref: ${{ inputs.OPHYD_DEVICES_BRANCH }}
|
||||||
|
path: ./ophyd_devices
|
||||||
|
|
||||||
|
- name: Install Python dependencies
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
pip install uv
|
||||||
|
uv pip install --system -e ./ophyd_devices[dev]
|
||||||
|
uv pip install --system -e ./bec/bec_lib[dev]
|
||||||
|
uv pip install --system -e ./bec/bec_server[dev]
|
342
.github/scripts/pr_issue_sync/pr_issue_sync.py
vendored
Normal file
342
.github/scripts/pr_issue_sync/pr_issue_sync.py
vendored
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
import functools
|
||||||
|
import os
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from github import Github
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class GHConfig(BaseModel):
|
||||||
|
token: str
|
||||||
|
organization: str
|
||||||
|
repository: str
|
||||||
|
project_number: int
|
||||||
|
graphql_url: str
|
||||||
|
rest_url: str
|
||||||
|
headers: dict
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectItemHandler:
|
||||||
|
"""
|
||||||
|
A class to handle GitHub project items.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, gh_config: GHConfig):
|
||||||
|
self.gh_config = gh_config
|
||||||
|
self.gh = Github(gh_config.token)
|
||||||
|
self.repo = self.gh.get_repo(f"{gh_config.organization}/{gh_config.repository}")
|
||||||
|
self.project_node_id = self.get_project_node_id()
|
||||||
|
|
||||||
|
def set_issue_status(
|
||||||
|
self,
|
||||||
|
status: Literal[
|
||||||
|
"Selected for Development",
|
||||||
|
"Weekly Backlog",
|
||||||
|
"In Development",
|
||||||
|
"Ready For Review",
|
||||||
|
"On Hold",
|
||||||
|
"Done",
|
||||||
|
],
|
||||||
|
issue_number: int | None = None,
|
||||||
|
issue_node_id: str | None = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Set the status field of a GitHub issue in the project.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status (str): The status to set. Must be one of the predefined statuses.
|
||||||
|
issue_number (int, optional): The issue number. If not provided, issue_node_id must be provided.
|
||||||
|
issue_node_id (str, optional): The issue node ID. If not provided, issue_number must be provided.
|
||||||
|
"""
|
||||||
|
if not issue_number and not issue_node_id:
|
||||||
|
raise ValueError("Either issue_number or issue_node_id must be provided.")
|
||||||
|
if issue_number and issue_node_id:
|
||||||
|
raise ValueError("Only one of issue_number or issue_node_id must be provided.")
|
||||||
|
if issue_number is not None:
|
||||||
|
issue = self.repo.get_issue(issue_number)
|
||||||
|
issue_id = self.get_issue_info(issue.node_id)[0]["id"]
|
||||||
|
else:
|
||||||
|
issue_id = issue_node_id
|
||||||
|
field_id, option_id = self.get_status_field_id(field_name=status)
|
||||||
|
self.set_field_option(issue_id, field_id, option_id)
|
||||||
|
|
||||||
|
def run_graphql(self, query: str, variables: dict) -> dict:
|
||||||
|
"""
|
||||||
|
Execute a GraphQL query against the GitHub API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): The GraphQL query to execute.
|
||||||
|
variables (dict): The variables to pass to the query.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The response from the GitHub API.
|
||||||
|
"""
|
||||||
|
response = requests.post(
|
||||||
|
self.gh_config.graphql_url,
|
||||||
|
json={"query": query, "variables": variables},
|
||||||
|
headers=self.gh_config.headers,
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"Query failed with status code {response.status_code}: {response.text}"
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def get_project_node_id(self):
|
||||||
|
"""
|
||||||
|
Retrieve the project node ID from the GitHub API.
|
||||||
|
"""
|
||||||
|
query = """
|
||||||
|
query($owner: String!, $number: Int!) {
|
||||||
|
organization(login: $owner) {
|
||||||
|
projectV2(number: $number) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
variables = {"owner": self.gh_config.organization, "number": self.gh_config.project_number}
|
||||||
|
resp = self.run_graphql(query, variables)
|
||||||
|
return resp["data"]["organization"]["projectV2"]["id"]
|
||||||
|
|
||||||
|
def get_issue_info(self, issue_node_id: str):
|
||||||
|
"""
|
||||||
|
Get the project-related information for a given issue node ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
issue_node_id (str): The node ID of the issue. Please note that this is not the issue number and typically starts with "I".
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[dict]: A list of project items associated with the issue.
|
||||||
|
"""
|
||||||
|
query = """
|
||||||
|
query($issueId: ID!) {
|
||||||
|
node(id: $issueId) {
|
||||||
|
... on Issue {
|
||||||
|
projectItems(first: 10) {
|
||||||
|
nodes {
|
||||||
|
project {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
}
|
||||||
|
id
|
||||||
|
fieldValues(first: 20) {
|
||||||
|
nodes {
|
||||||
|
... on ProjectV2ItemFieldSingleSelectValue {
|
||||||
|
name
|
||||||
|
field {
|
||||||
|
... on ProjectV2SingleSelectField {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
variables = {"issueId": issue_node_id}
|
||||||
|
resp = self.run_graphql(query, variables)
|
||||||
|
return resp["data"]["node"]["projectItems"]["nodes"]
|
||||||
|
|
||||||
|
def get_status_field_id(
|
||||||
|
self,
|
||||||
|
field_name: Literal[
|
||||||
|
"Selected for Development",
|
||||||
|
"Weekly Backlog",
|
||||||
|
"In Development",
|
||||||
|
"Ready For Review",
|
||||||
|
"On Hold",
|
||||||
|
"Done",
|
||||||
|
],
|
||||||
|
) -> tuple[str, str]:
|
||||||
|
"""
|
||||||
|
Get the status field ID and option ID for the given field name in the project.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field_name (str): The name of the field to retrieve.
|
||||||
|
Must be one of the predefined statuses.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[str, str]: A tuple containing the field ID and option ID.
|
||||||
|
"""
|
||||||
|
field_id = None
|
||||||
|
option_id = None
|
||||||
|
project_fields = self.get_project_fields()
|
||||||
|
for field in project_fields:
|
||||||
|
if field["name"] != "Status":
|
||||||
|
continue
|
||||||
|
field_id = field["id"]
|
||||||
|
for option in field["options"]:
|
||||||
|
if option["name"] == field_name:
|
||||||
|
option_id = option["id"]
|
||||||
|
break
|
||||||
|
if not field_id or not option_id:
|
||||||
|
raise ValueError(f"Field '{field_name}' not found in project fields.")
|
||||||
|
|
||||||
|
return field_id, option_id
|
||||||
|
|
||||||
|
def set_field_option(self, item_id, field_id, option_id):
|
||||||
|
"""
|
||||||
|
Set the option of a project item for a single-select field.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_id (str): The ID of the project item to update.
|
||||||
|
field_id (str): The ID of the field to update.
|
||||||
|
option_id (str): The ID of the option to set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
mutation = """
|
||||||
|
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
||||||
|
updateProjectV2ItemFieldValue(
|
||||||
|
input: {
|
||||||
|
projectId: $projectId
|
||||||
|
itemId: $itemId
|
||||||
|
fieldId: $fieldId
|
||||||
|
value: { singleSelectOptionId: $optionId }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
projectV2Item {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
variables = {
|
||||||
|
"projectId": self.project_node_id,
|
||||||
|
"itemId": item_id,
|
||||||
|
"fieldId": field_id,
|
||||||
|
"optionId": option_id,
|
||||||
|
}
|
||||||
|
return self.run_graphql(mutation, variables)
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=1)
|
||||||
|
def get_project_fields(self) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Get the available fields in the project.
|
||||||
|
This method caches the result to avoid multiple API calls.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[dict]: A list of fields in the project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
query = """
|
||||||
|
query($projectId: ID!) {
|
||||||
|
node(id: $projectId) {
|
||||||
|
... on ProjectV2 {
|
||||||
|
fields(first: 50) {
|
||||||
|
nodes {
|
||||||
|
... on ProjectV2SingleSelectField {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
options {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
variables = {"projectId": self.project_node_id}
|
||||||
|
resp = self.run_graphql(query, variables)
|
||||||
|
return list(filter(bool, resp["data"]["node"]["fields"]["nodes"]))
|
||||||
|
|
||||||
|
def get_pull_request_linked_issues(self, pr_number: int) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Get the linked issues of a pull request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pr_number (int): The pull request number.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[dict]: A list of linked issues.
|
||||||
|
"""
|
||||||
|
query = """
|
||||||
|
query($number: Int!, $owner: String!, $repo: String!) {
|
||||||
|
repository(owner: $owner, name: $repo) {
|
||||||
|
pullRequest(number: $number) {
|
||||||
|
id
|
||||||
|
closingIssuesReferences(first: 50) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
body
|
||||||
|
number
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
variables = {
|
||||||
|
"number": pr_number,
|
||||||
|
"owner": self.gh_config.organization,
|
||||||
|
"repo": self.gh_config.repository,
|
||||||
|
}
|
||||||
|
resp = self.run_graphql(query, variables)
|
||||||
|
edges = resp["data"]["repository"]["pullRequest"]["closingIssuesReferences"]["edges"]
|
||||||
|
return [edge["node"] for edge in edges if edge.get("node")]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# GitHub settings
|
||||||
|
token = os.getenv("TOKEN")
|
||||||
|
org = os.getenv("ORG")
|
||||||
|
repo = os.getenv("REPO")
|
||||||
|
project_number = os.getenv("PROJECT_NUMBER")
|
||||||
|
pr_number = os.getenv("PR_NUMBER")
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
raise ValueError("GitHub token is not set. Please set the TOKEN environment variable.")
|
||||||
|
if not org:
|
||||||
|
raise ValueError("GitHub organization is not set. Please set the ORG environment variable.")
|
||||||
|
if not repo:
|
||||||
|
raise ValueError("GitHub repository is not set. Please set the REPO environment variable.")
|
||||||
|
if not project_number:
|
||||||
|
raise ValueError(
|
||||||
|
"GitHub project number is not set. Please set the PROJECT_NUMBER environment variable."
|
||||||
|
)
|
||||||
|
if not pr_number:
|
||||||
|
raise ValueError(
|
||||||
|
"Pull request number is not set. Please set the PR_NUMBER environment variable."
|
||||||
|
)
|
||||||
|
|
||||||
|
project_number = int(project_number)
|
||||||
|
pr_number = int(pr_number)
|
||||||
|
|
||||||
|
gh_config = GHConfig(
|
||||||
|
token=token,
|
||||||
|
organization=org,
|
||||||
|
repository=repo,
|
||||||
|
project_number=project_number,
|
||||||
|
graphql_url="https://api.github.com/graphql",
|
||||||
|
rest_url=f"https://api.github.com/repos/{org}/{repo}/issues",
|
||||||
|
headers={"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"},
|
||||||
|
)
|
||||||
|
project_item_handler = ProjectItemHandler(gh_config=gh_config)
|
||||||
|
|
||||||
|
# Get PR info
|
||||||
|
pr = project_item_handler.repo.get_pull(pr_number)
|
||||||
|
|
||||||
|
# Get the linked issues of the pull request
|
||||||
|
linked_issues = project_item_handler.get_pull_request_linked_issues(pr_number=pr_number)
|
||||||
|
print(f"Linked issues: {linked_issues}")
|
||||||
|
|
||||||
|
target_status = "In Development" if pr.draft else "Ready For Review"
|
||||||
|
print(f"Target status: {target_status}")
|
||||||
|
for issue in linked_issues:
|
||||||
|
project_item_handler.set_issue_status(issue_number=issue["number"], status=target_status)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
2
.github/scripts/pr_issue_sync/requirements.txt
vendored
Normal file
2
.github/scripts/pr_issue_sync/requirements.txt
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pydantic
|
||||||
|
pygithub
|
28
.github/workflows/check_pr.yml
vendored
Normal file
28
.github/workflows/check_pr.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Check PR status for branch
|
||||||
|
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' && github.event.ref_type != 'tag'
|
||||||
|
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"]
|
||||||
|
}
|
44
.github/workflows/ci.yml
vendored
Normal file
44
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
name: Full CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
BEC_CORE_BRANCH:
|
||||||
|
description: 'Branch of BEC Core to install'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
OPHYD_DEVICES_BRANCH:
|
||||||
|
description: 'Branch of Ophyd Devices to install'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_pr_status:
|
||||||
|
uses: ./.github/workflows/check_pr.yml
|
||||||
|
|
||||||
|
formatter:
|
||||||
|
needs: check_pr_status
|
||||||
|
if: needs.check_pr_status.outputs.branch-pr == ''
|
||||||
|
uses: ./.github/workflows/formatter.yml
|
||||||
|
|
||||||
|
unit-test:
|
||||||
|
needs: [check_pr_status, formatter]
|
||||||
|
if: needs.check_pr_status.outputs.branch-pr == ''
|
||||||
|
uses: ./.github/workflows/pytest.yml
|
||||||
|
with:
|
||||||
|
BEC_CORE_BRANCH: ${{ inputs.BEC_CORE_BRANCH || 'main' }}
|
||||||
|
OPHYD_DEVICES_BRANCH: ${{ inputs.OPHYD_DEVICES_BRANCH || github.head_ref || github.sha}}
|
||||||
|
secrets:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
|
unit-test-matrix:
|
||||||
|
needs: [check_pr_status, formatter]
|
||||||
|
if: needs.check_pr_status.outputs.branch-pr == ''
|
||||||
|
uses: ./.github/workflows/pytest-matrix.yml
|
||||||
|
with:
|
||||||
|
BEC_CORE_BRANCH: ${{ inputs.BEC_CORE_BRANCH || 'main' }}
|
||||||
|
OPHYD_DEVICES_BRANCH: ${{ inputs.OPHYD_DEVICES_BRANCH || github.head_ref || github.sha}}
|
61
.github/workflows/formatter.yml
vendored
Normal file
61
.github/workflows/formatter.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
name: Formatter and Pylint jobs
|
||||||
|
on: [workflow_call]
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
Formatter:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
- name: Run black and isort
|
||||||
|
run: |
|
||||||
|
pip install black isort
|
||||||
|
pip install -e .[dev]
|
||||||
|
black --check --diff --color .
|
||||||
|
isort --check --diff ./
|
||||||
|
Pylint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install pylint pylint-exit anybadge
|
||||||
|
|
||||||
|
- name: Run Pylint
|
||||||
|
run: |
|
||||||
|
mkdir -p ./pylint
|
||||||
|
set +e
|
||||||
|
pylint ./${{ github.event.repository.name }} --output-format=text > ./pylint/pylint.log
|
||||||
|
pylint-exit $?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
- name: Extract Pylint Score
|
||||||
|
id: score
|
||||||
|
run: |
|
||||||
|
SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint.log)
|
||||||
|
echo "score=$SCORE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create Badge
|
||||||
|
run: |
|
||||||
|
anybadge --label=Pylint --file=./pylint/pylint.svg --value="${{ steps.score.outputs.score }}" 2=red 4=orange 8=yellow 10=green
|
||||||
|
|
||||||
|
- name: Upload Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: pylint-artifacts
|
||||||
|
path: |
|
||||||
|
# ./pylint/pylint.log # not sure why this isn't working
|
||||||
|
./pylint/pylint.svg
|
42
.github/workflows/pytest-matrix.yml
vendored
Normal file
42
.github/workflows/pytest-matrix.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: Run Pytest with different Python versions
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
BEC_CORE_BRANCH:
|
||||||
|
description: 'Branch of BEC Core to install'
|
||||||
|
required: false
|
||||||
|
default: 'main'
|
||||||
|
type: string
|
||||||
|
OPHYD_DEVICES_BRANCH:
|
||||||
|
description: 'Branch of Ophyd Devices to install'
|
||||||
|
required: false
|
||||||
|
default: 'main'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pytest-matrix:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Checkout BEC Ophyd Devices
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: bec-project/ophyd_devices
|
||||||
|
ref: ${{ inputs.OPHYD_DEVICES_BRANCH }}
|
||||||
|
|
||||||
|
- name: Install Ophyd Devices and dependencies
|
||||||
|
uses: ./.github/actions/ophyd_devices_install
|
||||||
|
with:
|
||||||
|
BEC_CORE_BRANCH: ${{ inputs.BEC_CORE_BRANCH }}
|
||||||
|
OPHYD_DEVICES_BRANCH: ${{ inputs.OPHYD_DEVICES_BRANCH }}
|
||||||
|
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Run Pytest
|
||||||
|
run: |
|
||||||
|
cd ./ophyd_devices
|
||||||
|
pip install pytest pytest-random-order
|
||||||
|
pytest -v --maxfail=2 --junitxml=report.xml --random-order ./tests
|
54
.github/workflows/pytest.yml
vendored
Normal file
54
.github/workflows/pytest.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
name: Run Pytest with Coverage
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
BEC_CORE_BRANCH:
|
||||||
|
description: 'Branch of BEC Core to install'
|
||||||
|
required: false
|
||||||
|
default: 'main'
|
||||||
|
type: string
|
||||||
|
OPHYD_DEVICES_BRANCH:
|
||||||
|
description: 'Branch of Ophyd Devices to install'
|
||||||
|
required: false
|
||||||
|
default: 'main'
|
||||||
|
type: string
|
||||||
|
secrets:
|
||||||
|
CODECOV_TOKEN:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pytest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Ophyd Devices
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: bec-project/ophyd_devices
|
||||||
|
ref: ${{ inputs.OPHYD_DEVICES_BRANCH }}
|
||||||
|
|
||||||
|
- name: Install Ophyd Devices and dependencies
|
||||||
|
uses: ./.github/actions/ophyd_devices_install
|
||||||
|
with:
|
||||||
|
BEC_CORE_BRANCH: ${{ inputs.BEC_CORE_BRANCH }}
|
||||||
|
OPHYD_DEVICES_BRANCH: ${{ inputs.OPHYD_DEVICES_BRANCH }}
|
||||||
|
PYTHON_VERSION: 3.11
|
||||||
|
|
||||||
|
- name: Run Pytest with Coverage
|
||||||
|
id: coverage
|
||||||
|
run: |
|
||||||
|
cd ./ophyd_devices
|
||||||
|
coverage run --source=./ophyd_devices --omit=*/ophyd_devices/tests/* -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests
|
||||||
|
coverage report
|
||||||
|
coverage xml
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
slug: bec-project/ophyd_devices
|
95
.github/workflows/semantic_release.yml
vendored
Normal file
95
.github/workflows/semantic_release.yml
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
name: Continuous Delivery
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
# default: least privileged permissions across all jobs
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-release-${{ github.ref_name }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Note: We checkout the repository at the branch that triggered the workflow
|
||||||
|
# with the entire history to ensure to match PSR's release branch detection
|
||||||
|
# and history evaluation.
|
||||||
|
# However, we forcefully reset the branch to the workflow sha because it is
|
||||||
|
# possible that the branch was updated while the workflow was running. This
|
||||||
|
# prevents accidentally releasing un-evaluated changes.
|
||||||
|
- name: Setup | Checkout Repository on Release Branch
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref_name }}
|
||||||
|
fetch-depth: 0
|
||||||
|
ssh-key: ${{ secrets.CI_DEPLOY_SSH_KEY }}
|
||||||
|
ssh-known-hosts: ${{ secrets.CI_DEPLOY_SSH_KNOWN_HOSTS }}
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Setup | Force release branch to be at workflow sha
|
||||||
|
run: |
|
||||||
|
git reset --hard ${{ github.sha }}
|
||||||
|
- name: Evaluate | Verify upstream has NOT changed
|
||||||
|
# Last chance to abort before causing an error as another PR/push was applied to
|
||||||
|
# the upstream branch while this workflow was running. This is important
|
||||||
|
# because we are committing a version change (--commit). You may omit this step
|
||||||
|
# if you have 'commit: false' in your configuration.
|
||||||
|
#
|
||||||
|
# You may consider moving this to a repo script and call it from this step instead
|
||||||
|
# of writing it in-line.
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set +o pipefail
|
||||||
|
|
||||||
|
UPSTREAM_BRANCH_NAME="$(git status -sb | head -n 1 | cut -d' ' -f2 | grep -E '\.{3}' | cut -d'.' -f4)"
|
||||||
|
printf '%s\n' "Upstream branch name: $UPSTREAM_BRANCH_NAME"
|
||||||
|
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
if [ -z "$UPSTREAM_BRANCH_NAME" ]; then
|
||||||
|
printf >&2 '%s\n' "::error::Unable to determine upstream branch name!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git fetch "${UPSTREAM_BRANCH_NAME%%/*}"
|
||||||
|
|
||||||
|
if ! UPSTREAM_SHA="$(git rev-parse "$UPSTREAM_BRANCH_NAME")"; then
|
||||||
|
printf >&2 '%s\n' "::error::Unable to determine upstream branch sha!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HEAD_SHA="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
|
if [ "$HEAD_SHA" != "$UPSTREAM_SHA" ]; then
|
||||||
|
printf >&2 '%s\n' "[HEAD SHA] $HEAD_SHA != $UPSTREAM_SHA [UPSTREAM SHA]"
|
||||||
|
printf >&2 '%s\n' "::error::Upstream has changed, aborting release..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "Verified upstream branch has not changed, continuing with release..."
|
||||||
|
|
||||||
|
- name: Semantic Version Release
|
||||||
|
id: release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
pip install python-semantic-release==9.* wheel build twine
|
||||||
|
semantic-release -vv version
|
||||||
|
if [ ! -d dist ]; then echo No release will be made; exit 0; fi
|
||||||
|
twine upload dist/* -u __token__ -p ${{ secrets.CI_PYPI_TOKEN }} --skip-existing
|
||||||
|
semantic-release publish
|
40
.github/workflows/sync-issues-pr.yml
vendored
Normal file
40
.github/workflows/sync-issues-pr.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
name: Sync PR to Project
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, ready_for_review, converted_to_draft, reopened, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync-project:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
PROJECT_NUMBER: 3 # BEC Project
|
||||||
|
ORG: 'bec-project'
|
||||||
|
REPO: 'ophyd_devices'
|
||||||
|
TOKEN: ${{ secrets.ADD_ISSUE_TO_PROJECT }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up python environment
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.11
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r ./.github/scripts/pr_issue_sync/requirements.txt
|
||||||
|
- name: Sync PR to Project
|
||||||
|
run: |
|
||||||
|
python ./.github/scripts/pr_issue_sync/pr_issue_sync.py
|
Loading…
x
Reference in New Issue
Block a user