Skip to content

Commit 6272b42

Browse files
Merge branch 'main' into Improve-BlueapiClient-plan-repr-to-add-parameter-types
2 parents 951eb7f + 107d54e commit 6272b42

24 files changed

Lines changed: 1423 additions & 1140 deletions

.github/workflows/_system_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
uses: astral-sh/setup-uv@v7
2323

2424
- name: Compose services
25-
uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0
25+
uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0
2626
with:
2727
compose-file: "tests/system_tests/compose.yaml"
2828

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262

6363
# Initializes the CodeQL tools for scanning.
6464
- name: Initialize CodeQL
65-
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4
65+
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
6666
with:
6767
languages: ${{ matrix.language }}
6868
build-mode: ${{ matrix.build-mode }}
@@ -89,6 +89,6 @@ jobs:
8989
echo ' make release'
9090
exit 1
9191
- name: Perform CodeQL Analysis
92-
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4
92+
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
9393
with:
9494
category: "/language:${{matrix.language}}"

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y --no-ins
4747
&& apt-get dist-clean
4848

4949
# Install uv to allow setup-scratch to run
50-
COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /bin/
50+
COPY --from=ghcr.io/astral-sh/uv:0.11 /uv /uvx /bin/
5151

5252
# For this pod to understand finding user information from LDAP
5353
RUN sed -i 's/files/ldap files/g' /etc/nsswitch.conf
@@ -64,7 +64,7 @@ COPY --from=build /python /python
6464

6565
# Copy the environment, but not the source code
6666
COPY --chown=1000:1000 --from=build /app/.venv /app/.venv
67-
RUN chmod o+wrX /app/.venv
67+
RUN chmod -R 777 /app
6868
ENV PATH=/app/.venv/bin:$PATH
6969

7070
# Add copy of blueapi source to container for debugging

codecov.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,3 @@ coverage:
44
default:
55
target: 85% # the required coverage value
66
threshold: 1% # the leniency in hitting the target
7-
ignore:
8-
- "src/blueapi/startup"

helm/blueapi/config_schema.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -430,9 +430,9 @@
430430
"title": "Remote Url",
431431
"type": "string"
432432
},
433-
"branch": {
434-
"description": "Branch of repo to check out - defaults to remote's default when cloning and the existing branch when the repo already exists",
435-
"title": "Branch",
433+
"target_revision": {
434+
"description": "Revision (branch or tag) to check out when cloning - defaults to remote's HEAD. If a tag is used, the repo will be left in a 'detached head' state.",
435+
"title": "Target Revision",
436436
"type": "string"
437437
}
438438
},

helm/blueapi/values.schema.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -852,11 +852,6 @@
852852
"title": "ScratchRepository",
853853
"type": "object",
854854
"properties": {
855-
"branch": {
856-
"title": "Branch",
857-
"description": "Branch of repo to check out - defaults to remote's default when cloning and the existing branch when the repo already exists",
858-
"type": "string"
859-
},
860855
"name": {
861856
"title": "Name",
862857
"description": "Unique name for this repository in the scratch directory",
@@ -868,6 +863,11 @@
868863
"description": "URL to clone from",
869864
"default": "https://github.com/example/example.git",
870865
"type": "string"
866+
},
867+
"target_revision": {
868+
"title": "Target Revision",
869+
"description": "Revision (branch or tag) to check out when cloning - defaults to remote's HEAD. If a tag is used, the repo will be left in a 'detached head' state.",
870+
"type": "string"
871871
}
872872
},
873873
"additionalProperties": false

pyproject.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ dev = [
5555
"pytest-cov",
5656
"pytest-asyncio",
5757
"pytest-httpx>=0.35.0",
58+
"pytest-timeout",
5859
"responses",
5960
"ruff",
6061
"semver",
@@ -72,7 +73,7 @@ dev = [
7273
"jwcrypto",
7374
"deepdiff",
7475
"tiled[minimal-server]>=0.2.4", # For system-test of dls.py
75-
"respx"
76+
"respx",
7677
]
7778

7879
[project.scripts]
@@ -94,20 +95,17 @@ reportMissingImports = false # Ignore missing stubs in imported modules
9495

9596
[tool.pytest.ini_options]
9697
# Run pytest with all our checkers, and don't spam us with massive tracebacks on error
97-
addopts = """
98-
--tb=native -vv --doctest-modules --doctest-glob="*.rst"
99-
--ignore=src/blueapi/startup
100-
"""
98+
addopts = '--tb=native -vv --doctest-modules --doctest-glob="*.rst"'
10199
# https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings
102100
filterwarnings = ["error", "ignore::DeprecationWarning"]
103101
# Doctest python code in docs, python code in src docstrings, test functions in tests
104102
testpaths = "docs src tests"
105103
asyncio_mode = "auto"
104+
timeout = 3
106105

107106
[tool.coverage.run]
108107
patch = ["subprocess"]
109108
data_file = "/tmp/blueapi.coverage"
110-
omit = ["src/blueapi/startup/**/*"]
111109

112110
[tool.coverage.paths]
113111
# Tests are run from installed location, map back to the src directory
@@ -178,6 +176,7 @@ commands = [
178176
"term",
179177
"--cov-report",
180178
"xml:cov.xml",
179+
"--timeout=60",
181180
{ replace = "posargs", default = [
182181
], extend = true },
183182
],

src/blueapi/cli/cli.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from blueapi.client.rest import (
2727
BlueskyRemoteControlError,
2828
InvalidParametersError,
29+
NonJsonResponseError,
2930
ServiceUnavailableError,
3031
UnauthorisedAccessError,
3132
UnknownPlanError,
@@ -83,8 +84,24 @@ def is_str_dict(val: Any) -> TypeGuard[TaskParameters]:
8384
@click.option(
8485
"-c", "--config", type=Path, help="Path to configuration YAML file", multiple=True
8586
)
87+
@click.option(
88+
"-v",
89+
"--verbose",
90+
"log_level",
91+
flag_value="DEBUG",
92+
help="Include DEBUG level logging output",
93+
)
94+
@click.option(
95+
"-q",
96+
"--quiet",
97+
"log_level",
98+
flag_value="ERROR",
99+
help="Reduce logging noise to only show errors",
100+
)
86101
@click.pass_context
87-
def main(ctx: click.Context, config: tuple[Path, ...]) -> None:
102+
def main(
103+
ctx: click.Context, config: tuple[Path, ...], log_level: str | None = None
104+
) -> None:
88105
# if no command is supplied, run with the options passed
89106

90107
# Set umask to DLS standard
@@ -96,6 +113,9 @@ def main(ctx: click.Context, config: tuple[Path, ...]) -> None:
96113
except FileNotFoundError as fnfe:
97114
raise ClickException(f"Config file not found: {fnfe.filename}") from fnfe
98115

116+
if log_level:
117+
config_loader.use_values({"logging": {"level": log_level}})
118+
99119
loaded_config: ApplicationConfig = config_loader.load()
100120

101121
set_up_logging(loaded_config.logging)
@@ -494,13 +514,16 @@ def login(obj: dict) -> None:
494514
print("Logged in")
495515
except Exception:
496516
client = BlueapiClient.from_config(config)
497-
if oidc := client.oidc_config:
498-
auth = SessionManager(
499-
oidc, cache_manager=SessionCacheManager(config.auth_token_path)
500-
)
501-
auth.start_device_flow()
502-
else:
503-
print("Server is not configured to use authentication!")
517+
try:
518+
if oidc := client.oidc_config:
519+
auth = SessionManager(
520+
oidc, cache_manager=SessionCacheManager(config.auth_token_path)
521+
)
522+
auth.start_device_flow()
523+
else:
524+
print("Server is not configured to use authentication!")
525+
except NonJsonResponseError as e:
526+
print(str(e))
504527

505528

506529
@main.command(name="logout")

src/blueapi/cli/scratch.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ def setup_scratch(
4848
)
4949
for repo in config.repositories:
5050
local_directory = config.root / repo.name
51-
ensure_repo(repo.remote_url, local_directory, repo.branch)
51+
ensure_repo(repo.remote_url, local_directory, repo.target_revision)
5252
scratch_install(local_directory, timeout=install_timeout)
5353

5454

5555
def ensure_repo(
56-
remote_url: str, local_directory: Path, branch: str | None = None
56+
remote_url: str, local_directory: Path, target_revision: str | None = None
5757
) -> None:
5858
"""
5959
Ensure that a repository is checked out for use in the scratch area.
@@ -69,29 +69,29 @@ def ensure_repo(
6969

7070
if not local_directory.exists():
7171
LOGGER.info(f"Cloning {remote_url}")
72-
repo = Repo.clone_from(remote_url, local_directory)
72+
Repo.clone_from(remote_url, local_directory, branch=target_revision)
7373
LOGGER.info(f"Cloned {remote_url} -> {local_directory}")
7474
elif local_directory.is_dir():
7575
repo = Repo(local_directory)
76-
LOGGER.info(f"Found {local_directory}")
76+
head = repo.head.commit
77+
LOGGER.info("Found %s @ %s", local_directory, head.name_rev)
78+
if target_revision:
79+
if target_revision in repo.refs:
80+
if repo.refs[target_revision].commit != head:
81+
LOGGER.warning(
82+
"Repository %s not at target revision: %r instead of %r",
83+
local_directory.name,
84+
head.name_rev,
85+
target_revision,
86+
)
87+
else:
88+
LOGGER.warning("Target revision %r not found", target_revision)
7789
else:
7890
raise KeyError(
79-
f"Unable to open {local_directory} as a git repository because it is a file"
91+
f"Unable to open {local_directory} as a git repository because it is not a "
92+
"directory"
8093
)
8194

82-
if branch:
83-
if not (local := getattr(repo.heads, branch, None)):
84-
origin = repo.remotes[0]
85-
origin.fetch()
86-
LOGGER.info(
87-
"Creating branch '%s' to track remote '%s'", branch, origin.refs[branch]
88-
)
89-
local = repo.create_head(branch, origin.refs[branch])
90-
local.set_tracking_branch(origin.refs[branch])
91-
92-
LOGGER.info("Checking out branch '%s'", branch)
93-
local.checkout()
94-
9595

9696
def scratch_install(path: Path, timeout: float = _DEFAULT_INSTALL_TIMEOUT) -> None:
9797
"""

src/blueapi/client/client.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
)
3838
from blueapi.utils import deprecated
3939
from blueapi.worker import WorkerEvent, WorkerState
40-
from blueapi.worker.event import ProgressEvent, TaskStatus
40+
from blueapi.worker.event import ProgressEvent, TaskError, TaskResult, TaskStatus
4141
from blueapi.worker.task_worker import TrackableTask
4242

4343
from .event_bus import AnyEvent, EventBusClient, OnAnyEvent
@@ -166,13 +166,17 @@ def __init__(self, name, model: PlanModel, client: "BlueapiClient"):
166166
self._client = client
167167
self.__doc__ = model.description
168168

169-
def __call__(self, *args, **kwargs):
169+
def __call__(self, *args, **kwargs) -> Any:
170170
req = TaskRequest(
171171
name=self.name,
172172
params=self._build_args(*args, **kwargs),
173173
instrument_session=self._client.instrument_session,
174174
)
175-
self._client.run_task(req)
175+
match self._client.run_task(req):
176+
case TaskStatus(result=TaskResult(result=res)):
177+
return res
178+
case TaskStatus(result=TaskError(type=typ, message=msg)):
179+
raise PlanFailedError(typ, msg)
176180

177181
@property
178182
def help_text(self) -> str:
@@ -795,3 +799,9 @@ def login(self, token_path: Path | None = None):
795799
auth.start_device_flow()
796800
else:
797801
print("Server is not configured to use authentication!")
802+
803+
804+
class PlanFailedError(Exception):
805+
def __init__(self, typ: str, message: str):
806+
super().__init__(message)
807+
self._type = typ

0 commit comments

Comments
 (0)