From 933fbb4047585c7971f14748eb3dd1bc19b4ed85 Mon Sep 17 00:00:00 2001 From: Morgan Epp <60796713+epmog@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:19:24 -0500 Subject: [PATCH] feat!: add in-memory session config for gui-submit CLI overrides Add --profile, --farm-id, --queue-id, and --storage-profile-id options to `deadline bundle gui-submit`. Overrides are applied to an in-memory ConfigParser session config that flows through the submission dialog without writing to the on-disk config. The settings dialog now has a "Config File" dropdown menu with "Save to Disk" and "Load from Disk" actions, and an "Apply" button that writes to the in-memory session only. When opened from `deadline config gui` (no session config), Apply is disabled and Ok saves to disk as before. A new `persist_job_id(job_id, profile, farm_id, queue_id)` helper writes the job ID to the correct hierarchical config section on disk. Both CLI and GUI callers use this after submission, fixing cases where CLI overrides caused the job ID to not be persisted. `_apply_cli_options_to_config` now always returns a fresh ConfigParser copy, eliminating a cache-mutation footgun that required callers to defensively pre-copy the config. BREAKING CHANGE: `DeadlineConfigDialog.configure_settings()` returns `ConfigureSettingsResult` dataclass instead of `bool`. Callers using truthiness checks (e.g. `if configure_settings():`) must change to `if configure_settings().changes_applied:` since a dataclass instance is always truthy. Signed-off-by: Morgan Epp <60796713+epmog@users.noreply.github.com> --- docs/design/in-memory-config.md | 227 ++++++++ src/deadline/client/api/_submit_job_bundle.py | 6 +- src/deadline/client/cli/_common.py | 19 +- .../client/cli/_groups/bundle_group.py | 29 +- src/deadline/client/config/__init__.py | 2 + src/deadline/client/config/config_file.py | 24 + src/deadline/client/ui/dialogs/__init__.py | 3 +- .../ui/dialogs/deadline_config_dialog.py | 214 +++++-- .../dialogs/submit_job_to_deadline_dialog.py | 57 +- .../client/ui/job_bundle_submitter.py | 3 + .../client/ui/translations/locales/de_DE.json | 23 +- .../client/ui/translations/locales/en_US.json | 23 +- .../client/ui/translations/locales/es_ES.json | 23 +- .../client/ui/translations/locales/fr_FR.json | 23 +- .../client/ui/translations/locales/id_ID.json | 23 +- .../client/ui/translations/locales/it_IT.json | 23 +- .../client/ui/translations/locales/ja_JP.json | 23 +- .../client/ui/translations/locales/ko_KR.json | 23 +- .../client/ui/translations/locales/pt_BR.json | 23 +- .../client/ui/translations/locales/tr_TR.json | 23 +- .../client/ui/translations/locales/zh_CN.json | 23 +- .../client/ui/translations/locales/zh_TW.json | 23 +- .../ui/widgets/shared_job_settings_tab.py | 83 ++- .../scripts/workstation_config_helpers.py | 9 - .../scripts/workstation_config_locators.py | 8 - .../cli/test_cli_bundle_submit.py | 2 +- .../unit/deadline_client/cli/test_cli_farm.py | 2 +- .../deadline_client/cli/test_cli_queue.py | 2 +- .../config/test_config_file.py | 69 +++ .../deadline_client/ui/test_session_config.py | 527 ++++++++++++++++++ 30 files changed, 1328 insertions(+), 234 deletions(-) create mode 100644 docs/design/in-memory-config.md create mode 100644 test/unit/deadline_client/ui/test_session_config.py diff --git a/docs/design/in-memory-config.md b/docs/design/in-memory-config.md new file mode 100644 index 000000000..746718c3a --- /dev/null +++ b/docs/design/in-memory-config.md @@ -0,0 +1,227 @@ +# In-Memory Configuration for GUI Submit + +## Problem + +The current configuration system in deadline-cloud always reads settings from the on-disk config file (`~/.deadline/config`). When a user runs `deadline bundle gui-submit`, there is no way to pass CLI arguments like `--farm-id` or `--queue-id` to pre-populate the submission dialog. The non-GUI `bundle submit` command supports these options via `_apply_cli_options_to_config`, which creates an in-memory `ConfigParser` overlay, but this pattern is not wired into the GUI submission path. + +Additionally, the settings dialog (`DeadlineConfigDialog`) always writes changes directly to the on-disk config. There is no concept of editing settings that only apply to the current session. + +## Goals + +1. Allow users to pass `--farm-id`, `--queue-id`, `--profile`, and `--storage-profile-id` into `deadline bundle gui-submit`, applying them as in-memory overrides for the submission session without writing to disk. +2. In the settings dialog opened from the submission window, allow users to choose between modifying the in-memory (session) config or the workstation (on-disk) config. + +## Current Architecture + +### Config Read/Write Flow + +- `config_file.read_config()` reads from `~/.deadline/config` (with caching based on mtime). +- `config_file.get_setting(name, config=None)` reads from the on-disk config when `config=None`, or from a provided `ConfigParser` when one is passed. +- `config_file.set_setting(name, value, config=None)` writes to disk when `config=None`, or mutates the provided `ConfigParser` in-memory when one is passed. +- `config_file.write_config(config)` persists a `ConfigParser` to disk. + +### CLI to In-Memory Config (existing pattern) + +`_apply_cli_options_to_config(**args)` in `_common.py`: +- Always returns a fresh `ConfigParser` copy — never `None`, never mutates the caller's config or the `read_config()` cache. +- If any CLI args are non-None, applies overrides via `set_setting(name, value, config=config)` on the copy. +- If no CLI args are provided, returns an unmodified copy of the disk config. +- Used by all CLI commands (`bundle submit`, `bundle gui-submit`, `job get`, etc.). + +### GUI Submission Flow + +`bundle_gui_submit` -> `show_job_bundle_submitter()` -> creates `SubmitJobToDeadlineDialog`. + +The dialog's `SharedJobSettingsWidget` calls `get_setting("defaults.farm_id")` and `get_setting("defaults.queue_id")` **without** passing a config object, so it always reads from disk. + +The `DeadlineUIController` and `DeadlineAuthenticationStatus` singletons accept a `ConfigParser` via `set_config()`, but the submission dialog never passes one derived from CLI args. + +### Settings Dialog + +`DeadlineConfigDialog` / `DeadlineWorkstationConfigWidget`: +- Maintains a `self.config` (copy of on-disk config) and a `self.changes` dict. +- On `apply()`, reads fresh config from disk, applies changes, then calls `write_config()` to persist. +- There is no concept of "session-only" changes. + +## Design + +### 1. CLI Options on `bundle gui-submit` + +Add `--profile`, `--farm-id`, `--queue-id`, and `--storage-profile-id` options to the `bundle_gui_submit` click command, matching what `bundle_submit` already accepts. + +In the command handler, use `_apply_cli_options_to_config()` to produce an in-memory `ConfigParser` with the overrides applied. Since `_apply_cli_options_to_config` always returns a fresh copy (never `None`, never mutates the cache), the result can be passed directly as the session config. Pass this config object through to `show_job_bundle_submitter()` and into `SubmitJobToDeadlineDialog`. + +### 2. Session Config Propagation + +`SubmitJobToDeadlineDialog.__init__` accepts an optional `session_config: Optional[ConfigParser]` parameter. When provided: + +- Stored as `self._session_config`. +- Passed to `SharedJobSettingsWidget` via its new `config` parameter. +- Used in `_set_submit_button_state` to check farm/queue configuration. +- Used in `on_submit` when starting job submission (falls back to `config_file.read_config()` when `None`). + +When `config` is `None` (no CLI overrides), behavior is unchanged — everything reads from disk as before. + +### 3. Config Threading Through Widget Hierarchy + +During implementation we discovered that the config needs to be threaded deeper than initially expected. The full propagation chain is: + +``` +bundle_gui_submit (CLI) + -> _apply_cli_options_to_config(**args) -> ConfigParser (always a fresh copy) + -> show_job_bundle_submitter(session_config=...) + -> SubmitJobToDeadlineDialog(session_config=...) [stores as self._session_config] + -> SharedJobSettingsWidget(config=...) [stores as self._config] + -> DeadlineCloudSettingsWidget(config=...) [stores as self._config] + -> DeadlineFarmDisplay(config=...) [stores as self._config] + -> DeadlineQueueDisplay(config=...) [stores as self._config] + -> All get_setting() calls pass config=self._config + -> _set_submit_button_state() uses config=self._session_config + -> on_submit() uses self._session_config or config_file.read_config() +``` + +The `_DeadlineNamedResourceDisplay` base class and all its subclasses (`DeadlineFarmDisplay`, `DeadlineQueueDisplay`, `DeadlineStorageProfileNameDisplay`) needed to be updated to accept and use the config. These widgets call `get_setting()` both during `__init__` (to get the initial ID) and in `refresh()` / `get_item()` (to fetch current IDs for API calls). All 14 `get_setting()` call sites in `shared_job_settings_tab.py` were updated to pass `config=self._config`. + +### 4. Settings Dialog: Config File Menu + +The settings dialog has a consistent button bar in both `deadline config gui` and `bundle gui-submit`: Ok, Cancel, and "Config File" (dropdown menu). + +- **`deadline config gui`** (no session_config): "Save to Disk" and "Load from Disk" in the Config File menu are enabled when there are pending changes. "Load from Disk" discards pending changes and refreshes from disk. Ok = Save to Disk + close. +- **`bundle gui-submit`** (with session_config): The "Config File" menu contains "Save to Disk" (writes session config to `~/.deadline/config`) and "Load from Disk" (reloads the session config from the on-disk config, discarding in-memory overrides). Ok = Apply pending changes to session + close. + +The submission dialog always provides a session config when opening the settings dialog, even if no CLI overrides were passed. If `_session_config` is `None`, `_ensure_session_config()` creates one from the current on-disk config. + +Implementation: + +- The button bar always contains Ok, Cancel, and "Config File" (dropdown menu with "Save to Disk" and "Load from Disk"). +- "Save to Disk" starts disabled and is enabled when the effective config differs from disk (session mode) or there are pending changes (workstation mode). +- The "Config File" button itself is disabled when all its menu actions are disabled. +- `refresh()` in `DeadlineWorkstationConfigWidget` prunes pending changes that match the base config, so changing a value back to its original disables the buttons. +- `DeadlineConfigDialog.configure_settings()` accepts an optional `session_config: Optional[ConfigParser]` parameter. +- `DeadlineWorkstationConfigWidget` determines its mode from whether `session_config` is provided. `refresh()` uses the session config as the base when one is present. +- In **session mode** (when `session_config` is provided), `apply()` mutates the provided `ConfigParser` in-memory. It does **not** call `write_config()`. +- In **workstation mode** (no `session_config`, e.g. `deadline config gui`), `apply()` behaves as before — reads from disk, applies changes, writes to disk. +- `configure_settings()` returns a `ConfigureSettingsResult(changes_applied, session_config)` so the caller can update its state. + +### 5. Return Flow + +`_apply_settings_result` in `SubmitJobToDeadlineDialog` handles the result from the settings dialog: + +```python +def _apply_settings_result(self, result): + """Apply the result from DeadlineConfigDialog.configure_settings().""" + if result.session_config is not None: + self._session_config = result.session_config + self.deadline_authentication_status.set_config(self._session_config) + self.shared_job_settings.set_session_config(self._session_config) + self.refresh_deadline_settings() +``` + +The submitter always refreshes when a session config is returned, regardless of whether `changes_applied` is True. This ensures the submitter picks up any changes made via Ok in the settings dialog. + +Both `on_settings_button_clicked` and `on_switch_profile_clicked` call `_ensure_session_config()` to guarantee a session config exists (creating one from disk if needed), pass it to `configure_settings()`, and delegate to `_apply_settings_result()`. + +### 6. Job ID Persistence with `persist_job_id` + +After a successful submission, the job ID should be written to the on-disk config so that subsequent CLI commands like `deadline job get` can find it. The config's hierarchical section structure (`profile → farm → queue → job_id`) means the job ID must be written to the section matching the farm/queue the job was submitted to — not necessarily the on-disk defaults. + +Previously, `create_job_from_job_bundle` in the API layer auto-persisted the job ID when `config=None`, and `bundle_submit` skipped persistence entirely when CLI overrides were present. The GUI path wrote the job ID using the on-disk defaults, which was incorrect when `--farm-id`/`--queue-id` overrides were used. + +The new approach: +- **The API layer (`create_job_from_job_bundle`) still auto-persists the job ID when `config=None`**, preserving backward compatibility for public API callers (e.g. Unreal) that use on-disk defaults. When a `config` is provided, the API layer does not persist — the caller owns that responsibility. +- **A new `config_file.persist_job_id(job_id, profile, farm_id, queue_id)` helper** handles persistence correctly. The caller extracts the profile, farm ID, and queue ID from its config (e.g. a session config with CLI overrides) and passes them explicitly. The helper builds the correct hierarchical section name and writes the job ID to the on-disk config. The on-disk farm/queue defaults are never changed. +- **Both CLI and GUI callers** call `persist_job_id(job_id, profile=..., farm_id=..., queue_id=...)` after a successful submission, extracting the values from their session config so the section resolves correctly regardless of whether overrides were used. + +## Implementation Progress + +### Done + +- [x] `bundle_group.py`: Added `--profile`, `--farm-id`, `--queue-id`, `--storage-profile-id` click options to `bundle_gui_submit`. Added `_apply_cli_options_to_config(**args)` call. Passing `session_config=config` to `show_job_bundle_submitter()`. +- [x] `_common.py`: `_apply_cli_options_to_config` always copies the input config and always returns a `ConfigParser` (never `None`, never mutates the caller's object or the `read_config()` cache). +- [x] `config_file.py`: Added `persist_job_id(job_id, profile, farm_id, queue_id)` helper that writes the job ID to the correct hierarchical section on disk without changing on-disk farm/queue defaults. +- [x] `bundle_group.py` `bundle_submit`: Uses `persist_job_id(job_id, profile=..., farm_id=..., queue_id=...)` to always persist the job ID to the correct section, even when CLI overrides are present. +- [x] `bundle_group.py` `bundle_gui_submit`: Simplified — no longer needs manual pre-copy workaround since `_apply_cli_options_to_config` always copies. +- [x] `_submit_job_bundle.py`: Scoped `set_setting("defaults.job_id", job_id)` side effect to `config is None` only (preserving backward compat for public API callers). When a `config` is provided, callers own their persistence policy. +- [x] `job_bundle_submitter.py`: `show_job_bundle_submitter()` accepts `session_config` param and passes it to `SubmitJobToDeadlineDialog`. +- [x] `submit_job_to_deadline_dialog.py`: `SubmitJobToDeadlineDialog.__init__` accepts `session_config: Optional[ConfigParser]`, stores as `self._session_config`. Passes to `SharedJobSettingsWidget`. `_set_submit_button_state` and `on_submit` use session config. Added `_ensure_session_config()` to lazily create a session config from disk when none was provided via CLI args. Added `_apply_settings_result()` helper to propagate updated session config to singletons and child widgets after settings dialog closes. `_submission_succeeded_signal_receiver` uses `persist_job_id(job_id, profile=..., farm_id=..., queue_id=...)` to write the job ID to the correct farm/queue section on disk. +- [x] `shared_job_settings_tab.py`: `SharedJobSettingsWidget` accepts `config` param. All 14 `get_setting()` calls updated to pass `config=self._config`. `DeadlineCloudSettingsWidget`, `_DeadlineNamedResourceDisplay`, `DeadlineFarmDisplay`, `DeadlineQueueDisplay`, `DeadlineStorageProfileNameDisplay` all accept and thread config. Added `set_session_config()` method for updating the config after settings dialog closes. +- [x] `deadline_config_dialog.py`: Accept `session_config` in `configure_settings()`. "Config File" dropdown menu offers "Save to Disk" and "Load from Disk". Ok applies to session (session mode) or saves to disk (workstation mode). Returns `ConfigureSettingsResult` dataclass instead of `bool`. +- [x] Propagate session config to `DeadlineUIController` and `DeadlineAuthenticationStatus` singletons via their existing `set_config()` methods. +- [x] Tests for the new behavior (`test/unit/deadline_client/ui/test_session_config.py`). + +## Affected Components Summary + +| Component | Change | Status | +|---|---|---| +| `_common.py` `_apply_cli_options_to_config` | Always copy input config. Always return `ConfigParser` (never `None`). Never mutate caller's object or `read_config()` cache. | Done | +| `config_file.py` `persist_job_id` | New helper. Accepts explicit profile, farm_id, queue_id to build the correct hierarchical section. Writes job ID to on-disk config without changing farm/queue defaults. | Done | +| `config/__init__.py` | Export `persist_job_id`. | Done | +| `bundle_group.py` `bundle_submit` | Use `persist_job_id(job_id, profile=..., farm_id=..., queue_id=...)` instead of conditional `set_setting`. Job ID now always persisted to correct section even with CLI overrides. | Done | +| `bundle_group.py` `bundle_gui_submit` | Add `--profile`, `--farm-id`, `--queue-id`, `--storage-profile-id` options. Call `_apply_cli_options_to_config()` directly (no manual pre-copy needed). Pass `session_config` to `show_job_bundle_submitter()`. | Done | +| `_submit_job_bundle.py` `create_job_from_job_bundle` | Scoped `set_setting("defaults.job_id", job_id)` to `config is None` only. When a `config` is provided, callers own persistence via `persist_job_id`. | Done | +| `job_bundle_submitter.py` `show_job_bundle_submitter()` | Accept optional `session_config` param, pass to `SubmitJobToDeadlineDialog`. | Done | +| `submit_job_to_deadline_dialog.py` `SubmitJobToDeadlineDialog` | Accept optional `session_config` param. Store as `_session_config`. Propagate to child widgets. Use in `_set_submit_button_state` and `on_submit`. Use `persist_job_id` in `_submission_succeeded_signal_receiver`. | Done | +| `shared_job_settings_tab.py` `SharedJobSettingsWidget` | Accept optional `config` param. Use `get_setting(..., config=self._config)` instead of bare `get_setting(...)`. | Done | +| `shared_job_settings_tab.py` display widgets | `DeadlineCloudSettingsWidget`, `_DeadlineNamedResourceDisplay`, `DeadlineFarmDisplay`, `DeadlineQueueDisplay`, `DeadlineStorageProfileNameDisplay` all accept and use config. | Done | +| `deadline_config_dialog.py` `DeadlineConfigDialog` | Accept optional `session_config`. Button bar: Ok, Cancel, "Config File" dropdown ("Save to Disk" / "Load from Disk"). Ok applies to session or saves to disk depending on mode. Return `ConfigureSettingsResult`. | Done | +| `submit_job_to_deadline_dialog.py` settings button | Update `on_settings_button_clicked` to pass/receive session config via `_ensure_session_config()`. Propagate to singletons and child widgets via `_apply_settings_result()`. | Done | +| `_deadline_controller.py` `DeadlineUIController` | Already supports `set_config()` — called with session config at init and after settings dialog. | Done | +| `deadline_authentication_status.py` | Already supports `set_config()` — called with session config at init and after settings dialog. | Done | + +## Breaking Changes + +### For library consumers (`deadline.client.api`) + +**`create_job_from_job_bundle` auto-persist behavior is now scoped to `config=None` only.** + +When calling `create_job_from_job_bundle()` without passing `config=`, the function still auto-persists the job ID to `~/.deadline/config` as before — no change needed. This preserves backward compatibility for callers like Unreal that use the public API with on-disk defaults. + +When `config=` is provided (a `ConfigParser` object), the function no longer auto-persists the job ID. This was already the behavior before — the `config is None` check meant it only ever auto-persisted in the no-config case. Callers that pass a config and want persistence should use `persist_job_id`: +```python +from deadline.client.config import persist_job_id, get_setting +job_id = create_job_from_job_bundle(..., config=my_config) +if job_id: + persist_job_id( + job_id, + profile=get_setting("defaults.aws_profile_name", config=my_config), + farm_id=get_setting("defaults.farm_id", config=my_config), + queue_id=get_setting("defaults.queue_id", config=my_config), + ) +``` + +### For UI plugin authors (`deadline.client.ui`) + +**`DeadlineConfigDialog.configure_settings()` return type changed from `bool` to `ConfigureSettingsResult`.** + +- **Who is affected**: Code that captures and inspects the return value of `configure_settings()`, including truthiness checks like `if configure_settings():`. +- **Migration**: The `ConfigureSettingsResult` dataclass instance is always truthy (even when `changes_applied=False`), so `if configure_settings():` will no longer work as before. Replace with `if configure_settings().changes_applied:`. Code that discarded the return value is unaffected. + +### Behavioral changes (non-breaking but notable) + +**Job ID is now persisted even when CLI overrides are used.** + +Previously, `deadline bundle submit --farm-id X --queue-id Y` would submit the job but *not* save the job ID to disk. Now it always persists the job ID to the correct hierarchical section (`profile/farm-X/queue-Y`). This means `deadline job get` will find the job ID when the same farm/queue is configured, even if they were originally specified via CLI flags. + +**`deadline bundle gui-submit` always passes a session config to the submit dialog.** + +Previously, when no `--farm-id`/`--queue-id`/`--profile`/`--storage-profile-id` flags were provided, `bundle_gui_submit` passed `session_config=None` to the dialog. Now it always passes a `ConfigParser` (a copy of the disk config). The dialog's `Optional[ConfigParser]` parameter still accepts `None` for third-party submitters that don't use the CLI entry point. + +## Key Design Decisions + +1. **Reuse `_apply_cli_options_to_config`**: Rather than inventing a new mechanism, we reuse the existing pattern from `bundle submit` to create the in-memory config overlay. The function was hardened to always copy and always return a `ConfigParser`, eliminating a class of cache-mutation bugs. +2. **ConfigParser as the session state carrier**: The `ConfigParser` object is already the abstraction used throughout the codebase. Passing it explicitly avoids introducing a new config abstraction. +3. **No global/singleton session config**: The session config is scoped to the dialog instance and passed explicitly. This avoids side effects on other parts of the system and keeps the on-disk config as the single source of truth for non-GUI paths. +4. **Consistent settings dialog button bar**: The button bar is identical across both entry points: Ok, Cancel, and "Config File" (dropdown menu with "Save to Disk" and "Load from Disk"). Without a session config (`deadline config gui`), "Save to Disk" and "Load from Disk" enable when there are pending changes. With a session config (`bundle gui-submit`), "Save to Disk" and "Load from Disk" enable when the effective config differs from disk. Ok saves to disk in workstation mode and applies to the in-memory session in session mode. +5. **Session config does not persist across submissions**: If the user closes the GUI and re-runs `bundle gui-submit` without CLI args, the session config is gone. This is intentional — CLI args are ephemeral by nature. +6. **Workstation changes in settings dialog write to disk on Ok**: This preserves the existing behavior and user expectations for workstation config. +7. **Deep config threading required**: The config must be passed through the entire widget hierarchy, not just to `SharedJobSettingsWidget`. The display widgets (`DeadlineFarmDisplay`, `DeadlineQueueDisplay`, `DeadlineStorageProfileNameDisplay`) also call `get_setting()` and need the config for correct behavior. All 14 `get_setting()` call sites in `shared_job_settings_tab.py` were updated. +8. **`_apply_cli_options_to_config` always returns a `ConfigParser`**: The function never returns `None`. When no CLI overrides are provided, it returns an unmodified copy of the disk config. This eliminates `config or read_config()` fallback patterns in callers and ensures the session config is always available for threading through the widget hierarchy. +9. **`configure_settings()` returns a dataclass instead of `bool`**: The return type changed from `bool` to `ConfigureSettingsResult(changes_applied, session_config)`. This is a breaking change for callers that used the return value in a truthiness check (e.g. `if configure_settings():`), since a dataclass instance is always truthy. Existing callers in this repo (`dev_application.py`, `config_group.py`) discard the return value and are unaffected. +10. **Session config must be re-propagated after settings dialog closes**: When the settings dialog returns an updated session config, it must be pushed to `DeadlineAuthenticationStatus`, `DeadlineUIController`, and `SharedJobSettingsWidget._config` — not just stored on the submit dialog. The `_apply_settings_result()` helper method handles this and always refreshes the submitter when a session config is returned. +11. **Config sections are hierarchical (farm-scoped queues)**: The `ConfigParser` sections are structured like `profile-(default) farm-XXXX defaults`, meaning `queue_id` is scoped under the farm section. Overriding `--farm-id` alone causes `queue_id` to resolve to empty (the new farm section doesn't exist yet). This is correct behavior — a queue belongs to a specific farm — but means `--farm-id` and `--queue-id` should typically be passed together. +12. **Lazy session config creation from the submitter**: The submit dialog's `_ensure_session_config()` creates a session config from the on-disk config on first use if none was provided via CLI args. This guarantees the settings dialog always operates in session mode when opened from the submitter, while `deadline config gui` (which never passes a session config) remains workstation-only. +13. **Load from Disk is a menu action with context-dependent enablement**: In session mode, enabled when the effective config (session + pending changes) differs from disk. In workstation mode, enabled when there are pending changes (acts as a discard-changes operation). "Save to Disk" follows the same logic per mode. The "Config File" button itself is disabled when all its menu actions are disabled. +14. **Change pruning keeps button state accurate**: `refresh()` removes pending changes whose values match the base config. This means changing a value and changing it back correctly disables Save to Disk, rather than leaving stale entries in the changes dict. +15. **Job ID persistence moved out of the API layer for config-aware callers**: `create_job_from_job_bundle` still auto-persists the job ID when `config=None` (preserving backward compatibility for public API callers like Unreal). When a `config` is provided, the caller owns persistence via `persist_job_id`. This keeps the simple public API path working while giving CLI and GUI callers control over which section the job ID is written to. +16. **`persist_job_id` takes explicit profile, farm_id, and queue_id**: Rather than accepting a `ConfigParser` and internally resolving the section via the dependency chain, `persist_job_id(job_id, profile, farm_id, queue_id)` makes its dependencies explicit. The caller extracts the three values from its config (via `get_setting`) and passes them directly. This makes the function easier to test, removes coupling to `_get_section_prefixes` internals, and makes the section construction transparent. For example, `deadline bundle submit --farm-id X --queue-id Y` extracts the profile, farm, and queue from the session config and passes them to `persist_job_id`, which writes the job ID under the `profile-{profile} X Y defaults` section. +17. **`_apply_cli_options_to_config` never mutates its inputs**: The function always creates a fresh `ConfigParser` copy before applying overrides. This eliminates the footgun where the `read_config()` cache could be permanently mutated by CLI overrides, which previously required callers (like `bundle_gui_submit`) to defensively pre-copy the config. diff --git a/src/deadline/client/api/_submit_job_bundle.py b/src/deadline/client/api/_submit_job_bundle.py index 9e89fe14e..9d1826571 100644 --- a/src/deadline/client/api/_submit_job_bundle.py +++ b/src/deadline/client/api/_submit_job_bundle.py @@ -844,8 +844,10 @@ def create_job_from_job_bundle( job_id = create_job_response["jobId"] print_function_callback("Waiting for Job to be created...") - # If using the default config, set the default job id so it holds the - # most-recently submitted job. + # When no config was provided, the caller is using the public API with + # on-disk defaults — auto-persist the job ID so `deadline job get` works. + # When a config IS provided, the caller (CLI or GUI) owns persistence + # and can resolve the correct hierarchical section via persist_job_id. if config is None: set_setting("defaults.job_id", job_id) diff --git a/src/deadline/client/cli/_common.py b/src/deadline/client/cli/_common.py index b69091141..54a97e9e9 100644 --- a/src/deadline/client/cli/_common.py +++ b/src/deadline/client/cli/_common.py @@ -89,20 +89,21 @@ def wraps(ctx: click.Context, *args, **kwargs): def _apply_cli_options_to_config( *, config: Optional[ConfigParser] = None, required_options: Set[str] = set(), **args -) -> Optional[ConfigParser]: +) -> ConfigParser: """ - Modifies an AWS Deadline Cloud config object to apply standard option names to it, such as - the AWS profile, AWS Deadline Cloud Farm, or AWS Deadline Cloud Queue to use. + Returns an in-memory AWS Deadline Cloud config with standard CLI option overrides applied. + Always returns a fresh copy — never mutates the caller's config or the read_config() cache. Args: - config (ConfigParser, optional): an AWS Deadline Cloud config, read by config_file.read_config(). - If not provided, loads the config from disk. + config (ConfigParser, optional): a base config to copy from. If not provided, loads from disk. """ - # Only work with a custom config if there are standard options provided - if any(value is not None for value in args.values()): - if config is None: - config = config_file.read_config() + # Always start from a copy so we never mutate the caller's object or the cache + base = config if config is not None else config_file.read_config() + config = ConfigParser() + config.read_dict(base) + # Apply any provided standard options + if any(value is not None for value in args.values()): aws_profile_name = args.pop("profile", None) if aws_profile_name: config_file.set_setting("defaults.aws_profile_name", aws_profile_name, config=config) diff --git a/src/deadline/client/cli/_groups/bundle_group.py b/src/deadline/client/cli/_groups/bundle_group.py index 21080a0b4..6c259a277 100644 --- a/src/deadline/client/cli/_groups/bundle_group.py +++ b/src/deadline/client/cli/_groups/bundle_group.py @@ -20,7 +20,7 @@ from botocore.exceptions import ClientError from ... import api -from ...config import config_file +from ...config import config_file, get_setting, persist_job_id from ...dataclasses import SubmitterInfo from ....job_attachments.exceptions import ( AssetSyncError, @@ -310,17 +310,16 @@ def _check_create_job_wait_canceled() -> bool: click.echo("Saved job debug snapshot:") click.echo(f" {save_debug_snapshot}") - # Check Whether the CLI options are modifying any of the default settings that affect - # the job id. If not, we'll save the job id submitted as the default job id. + # Persist the job ID to the on-disk config under the correct + # farm/queue section so `deadline job get` picks it up. # If a job snapshot directory was provided, the job_id will be None. - if ( - args.get("profile") is None - and args.get("farm_id") is None - and args.get("queue_id") is None - and args.get("storage_profile_id") is None - and job_id - ): - config_file.set_setting("defaults.job_id", job_id) + if job_id: + persist_job_id( + job_id, + profile=get_setting("defaults.aws_profile_name", config=config), + farm_id=get_setting("defaults.farm_id", config=config), + queue_id=get_setting("defaults.queue_id", config=config), + ) except AssetSyncCancelledError as exc: if sigint_handler.continue_operation: @@ -418,6 +417,10 @@ def _check_create_job_wait_canceled() -> bool: 'OR --submitter-info \'{"submitter_name": "MyApp", "additional_info": {"render_engine": "Cycles"}}\' ' "OR --submitter-info file://path/to/submitter.json", ) +@click.option("--profile", help="The AWS profile to use.") +@click.option("--farm-id", help="The farm to use.") +@click.option("--queue-id", help="The queue to use.") +@click.option("--storage-profile-id", help="The storage profile to use.") @_handle_error def bundle_gui_submit( parameter, @@ -439,6 +442,9 @@ def bundle_gui_submit( Learn more about [job bundles](https://docs.aws.amazon.com/deadline-cloud/latest/developerguide/build-job-bundle.html) """ + # Apply CLI options (--profile, --farm-id, --queue-id, --storage-profile-id) to an in-memory config. + # _apply_cli_options_to_config always returns a fresh copy, so the on-disk cache is never mutated. + config = _apply_cli_options_to_config(**args) if submitter_name: click.echo( click.style( @@ -473,6 +479,7 @@ def bundle_gui_submit( submitter_info=submitter_info, known_asset_paths=known_asset_path, job_parameters=parameter, + session_config=config, ) if not submitter: diff --git a/src/deadline/client/config/__init__.py b/src/deadline/client/config/__init__.py index d2bf89c45..84e20cc36 100644 --- a/src/deadline/client/config/__init__.py +++ b/src/deadline/client/config/__init__.py @@ -15,6 +15,7 @@ "get_setting", "set_setting", "clear_setting", + "persist_job_id", "get_best_profile_for_farm", "str2bool", "DEFAULT_DEADLINE_ENDPOINT_URL", @@ -27,5 +28,6 @@ get_setting_default, set_setting, clear_setting, + persist_job_id, str2bool, ) diff --git a/src/deadline/client/config/config_file.py b/src/deadline/client/config/config_file.py index 66c1406bd..8fc439a2b 100644 --- a/src/deadline/client/config/config_file.py +++ b/src/deadline/client/config/config_file.py @@ -429,6 +429,30 @@ def set_setting(setting_name: str, value: str, config: Optional[ConfigParser] = write_config(config) +def persist_job_id(job_id: str, profile: str, farm_id: str, queue_id: str) -> None: + """ + Persists a job ID to the on-disk config file under the correct + hierarchical section (profile / farm / queue). + + The caller provides the explicit profile, farm, and queue that the job + was submitted to. The job ID is written into the matching section of + the on-disk config without changing the on-disk farm/queue defaults. + + Args: + job_id: The job ID to persist. + profile: The AWS profile name used for submission. + farm_id: The farm ID the job was submitted to. + queue_id: The queue ID the job was submitted to. + """ + section = f"profile-{profile} {farm_id} {queue_id} defaults" + + disk_config = read_config() + if section not in disk_config: + disk_config[section] = {} + disk_config.set(section, "job_id", job_id) + write_config(disk_config) + + def clear_setting(setting_name: str, config: Optional[ConfigParser] = None): """ Sets the value of the specified setting back to the default value. diff --git a/src/deadline/client/ui/dialogs/__init__.py b/src/deadline/client/ui/dialogs/__init__.py index 60545700e..7ddf63a9e 100644 --- a/src/deadline/client/ui/dialogs/__init__.py +++ b/src/deadline/client/ui/dialogs/__init__.py @@ -1,6 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. __all__ = [ + "ConfigureSettingsResult", "DeadlineConfigDialog", "DeadlineLoginDialog", "SubmitJobProgressDialog", @@ -10,7 +11,7 @@ ] from ._types import JobBundlePurpose -from .deadline_config_dialog import DeadlineConfigDialog +from .deadline_config_dialog import ConfigureSettingsResult, DeadlineConfigDialog from .deadline_login_dialog import DeadlineLoginDialog from .submit_job_progress_dialog import SubmitJobProgressDialog from .submit_job_to_deadline_dialog import SubmitJobToDeadlineDialog diff --git a/src/deadline/client/ui/dialogs/deadline_config_dialog.py b/src/deadline/client/ui/dialogs/deadline_config_dialog.py index 52bb3aa43..74437111d 100644 --- a/src/deadline/client/ui/dialogs/deadline_config_dialog.py +++ b/src/deadline/client/ui/dialogs/deadline_config_dialog.py @@ -11,6 +11,7 @@ __all__ = ["DeadlineConfigDialog"] from configparser import ConfigParser +from dataclasses import dataclass from logging import getLogger, root from typing import Callable, Dict, List, Optional @@ -30,6 +31,7 @@ QHBoxLayout, QLabel, QListWidget, + QMenu, QMessageBox, QPushButton, QSizePolicy, @@ -60,41 +62,66 @@ NOT_VALID_MARKER = "[NOT VALID]" +@dataclass +class ConfigureSettingsResult: + """Result from DeadlineConfigDialog.configure_settings().""" + + changes_applied: bool + session_config: Optional[ConfigParser] = None + + class DeadlineConfigDialog(QDialog): """ A modal dialog box for modifying the AWS Deadline Cloud local workstation configuration. + When a session_config is provided, "Ok" applies changes to the in-memory + session and closes. "Save to Disk" writes to the on-disk config, and + "Load from Disk" reverts the session to match the on-disk config. + Without a session_config, "Ok" saves to disk and closes. + Example code: DeadlineConfigDialog.configure_settings(parent=self) """ @staticmethod def configure_settings( - parent: Optional[QWidget] = None, set_profile_focus: bool = False - ) -> bool: + parent: Optional[QWidget] = None, + set_profile_focus: bool = False, + session_config: Optional[ConfigParser] = None, + ) -> ConfigureSettingsResult: """ Static method that runs the Deadline Config Dialog. Args: parent: Parent widget set_profile_focus: Optional boolean to set the initial focus to the profile selector + session_config: Optional in-memory config for session mode - Returns True if any changes were applied, False otherwise. + Returns a ConfigureSettingsResult with changes_applied and the + (possibly modified) session_config. """ - deadline_config = DeadlineConfigDialog(parent=parent) + deadline_config = DeadlineConfigDialog(parent=parent, session_config=session_config) if set_profile_focus: deadline_config.config_box.aws_profiles_box.setFocus() deadline_config.exec_() - return deadline_config.changes_were_applied + return ConfigureSettingsResult( + changes_applied=deadline_config.changes_were_applied, + session_config=deadline_config._session_config, + ) - def __init__(self, parent: Optional[QWidget] = None) -> None: + def __init__( + self, + parent: Optional[QWidget] = None, + session_config: Optional[ConfigParser] = None, + ) -> None: super().__init__( parent=parent, f=Qt.WindowSystemMenuHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint ) + self._session_config = session_config self.setWindowTitle(tr("AWS Deadline Cloud workstation configuration")) self.deadline_authentication_status = DeadlineAuthenticationStatus.getInstance() self._build_ui() @@ -116,7 +143,10 @@ def sizeHint(self): def _build_ui(self): self.layout = QVBoxLayout(self) - self.config_box = DeadlineWorkstationConfigWidget(parent=self) + self.config_box = DeadlineWorkstationConfigWidget( + parent=self, + session_config=self._session_config, + ) self.scrollArea = DeadlineScrollArea(self) self.scrollArea.setWidget(self.config_box) @@ -139,16 +169,38 @@ def _build_ui(self): self.on_auth_status_update ) - # We only use a Close button, not OK/Cancel, because we live update the settings. + # Build the button bar: Ok, Cancel, and "Config File" dropdown. self.button_box = QDialogButtonBox( - QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Apply, Qt.Horizontal + QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal ) self.button_box.button(QDialogButtonBox.Ok).setText(tr("Ok")) self.button_box.button(QDialogButtonBox.Cancel).setText(tr("Cancel")) - self.button_box.button(QDialogButtonBox.Apply).setText(tr("Apply")) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) - self.button_box.clicked.connect(self.on_button_box_clicked) + + # Config File dropdown menu with Save to Disk / Load from Disk + self._config_file_button = QPushButton(tr("Config File")) + config_file_menu = QMenu(self._config_file_button) + self._save_to_disk_action = config_file_menu.addAction( + tr("Save to Disk"), self._on_save_to_disk + ) + self._save_to_disk_action.setEnabled(False) + self._load_from_disk_action = config_file_menu.addAction( + tr("Load from Disk"), self._on_load_from_disk + ) + self._load_from_disk_action.setEnabled(False) + self._config_file_button.setMenu(config_file_menu) + self.button_box.addButton(self._config_file_button, QDialogButtonBox.ActionRole) + + # Set initial enabled state: in session mode, enable if session differs from disk + if self._session_config is not None: + differs = not self._effective_config_matches_disk() + self._config_file_button.setEnabled(differs) + self._save_to_disk_action.setEnabled(differs) + self._load_from_disk_action.setEnabled(differs) + else: + self._config_file_button.setEnabled(False) + self.auth_status_box.logout_clicked.connect(self.on_logout) self.auth_status_box.login_clicked.connect(self.on_login) self.layout.addWidget(self.button_box) @@ -160,9 +212,55 @@ def _build_ui(self): def changes_were_applied(self) -> bool: return self.config_box.changes_were_applied + def _effective_config_matches_disk(self) -> bool: + """Return True if the effective config (session + pending changes) matches disk.""" + disk = config_file.read_config() + effective = self.config_box.config if self.config_box.config else self._session_config + if effective is None: + return True + return {s: dict(disk[s]) for s in disk} == {s: dict(effective[s]) for s in effective} + + def _on_load_from_disk(self) -> None: + """Reload config from disk, discarding pending changes (and session overrides if any).""" + if self._session_config is not None: + self._session_config = ConfigParser() + self._session_config.read_dict(config_file.read_config()) + self.config_box.set_session_config(self._session_config) + else: + self.config_box.changes.clear() + self.config_box.refresh() + + def _on_save_to_disk(self) -> bool: + """Save the current changes to the on-disk config. Returns True on success.""" + if not self.config_box._validate_changes("Save to Disk"): + return False + if self._session_config is not None: + # Session path: apply pending changes to session config, then write it all to disk + for setting_name, value in self.config_box.changes.items(): + config_file.set_setting(setting_name, value, self._session_config) + config_file.write_config(self._session_config) + else: + # Workstation path: read from disk, apply changes, write back + config = config_file.read_config() + for setting_name, value in self.config_box.changes.items(): + config_file.set_setting(setting_name, value, config) + config_file.write_config(config) + self.config_box.changes.clear() + self.config_box.changes_were_applied = True + self.config_box.refresh() + return True + def accept(self): - if self.config_box.apply(): - super().accept() + if self._session_config is not None: + # Session mode: Ok = Apply to session + close + if not self.config_box.apply(): + return + self._session_config = self.config_box.config + else: + # Workstation mode: Ok = Save to Disk + close + if not self._on_save_to_disk(): + return + super().accept() def reject(self): self.deadline_authentication_status.set_config(config_file.read_config()) @@ -178,13 +276,17 @@ def on_logout(self): self.deadline_authentication_status.refresh_status() self.config_box.refresh() - def on_button_box_clicked(self, button): - if self.button_box.standardButton(button) == QDialogButtonBox.Apply: - self.config_box.apply() - def on_refresh(self): - # Enable the "Apply" button only if there are changes - self.button_box.button(QDialogButtonBox.Apply).setEnabled(bool(self.config_box.changes)) + has_changes = bool(self.config_box.changes) + differs_from_disk = not self._effective_config_matches_disk() + if self._session_config is not None: + self._save_to_disk_action.setEnabled(differs_from_disk) + self._load_from_disk_action.setEnabled(differs_from_disk) + self._config_file_button.setEnabled(differs_from_disk) + else: + self._save_to_disk_action.setEnabled(has_changes) + self._load_from_disk_action.setEnabled(has_changes) + self._config_file_button.setEnabled(has_changes) # Update the auth status with the refreshed config self.deadline_authentication_status.set_config(self.config_box.config) @@ -223,12 +325,15 @@ class DeadlineWorkstationConfigWidget(QWidget): # provides (operation_name, BaseException) _background_exception = Signal(str, BaseException) - def __init__(self, parent: Optional[QWidget] = None): + def __init__( + self, parent: Optional[QWidget] = None, session_config: Optional[ConfigParser] = None + ): super().__init__(parent) self.changes: dict = {} self.config: Optional[ConfigParser] = None self.changes_were_applied = False + self._session_config = session_config # Flags to track when we're waiting for cascading list refreshes # These prevent the list_updated handlers from interfering with manual user selections @@ -242,6 +347,13 @@ def __init__(self, parent: Optional[QWidget] = None): def minimumSizeHint(self): return QSize(500, 700) + def set_session_config(self, session_config: ConfigParser) -> None: + """Replace the session config and refresh the UI.""" + self._session_config = session_config + self.changes.clear() + self.refresh() + self.refresh_lists() + def _build_ui(self): # Ensure the widget expands horizontally self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) @@ -755,9 +867,21 @@ def refresh(self): """ Refreshes all the configuration UI elements from the current config. """ - # Make self.config be a deep copy of the config, with changes applied + # Make self.config be a deep copy of the config, with changes applied. + # In session mode, start from the session config; otherwise from disk. self.config = ConfigParser() - self.config.read_dict(config_file.read_config()) + base_config = ( + self._session_config if self._session_config is not None else config_file.read_config() + ) + self.config.read_dict(base_config) + + # Remove changes that match the base config (e.g. user changed a value then changed it back) + for setting_name in list(self.changes): + if self.changes[setting_name] == config_file.get_setting( + setting_name, config=self.config + ): + del self.changes[setting_name] + for setting_name, value in self.changes.items(): config_file.set_setting(setting_name, value, self.config) self.default_farm_box.set_config(self.config) @@ -803,9 +927,24 @@ def refresh(self): self.refreshed.emit() + def _validate_changes(self, action_name: str) -> bool: + """Check that no pending changes contain NOT_VALID_MARKER. Shows a warning and returns False if invalid.""" + for setting_name, value in self.changes.items(): + if value.startswith(NOT_VALID_MARKER): + QMessageBox.warning( + self, + action_name, + f"Cannot apply changes, {value} is not valid for setting {setting_name}", + ) + return False + return True + def apply(self) -> bool: """ - Apply all the settings that the user has changed into the config file. + Apply all the settings that the user has changed. + + In workstation mode, writes changes to the on-disk config file. + In session mode, mutates the in-memory session config without writing to disk. Returns True if the settings were applied, False otherwise. """ @@ -815,19 +954,22 @@ def apply(self) -> bool: self.default_storage_profile_box.box.currentData() ) - for setting_name, value in self.changes.items(): - if value.startswith(NOT_VALID_MARKER): - QMessageBox.warning( # type: ignore[call-arg] - self, - "Apply changes", - f"Cannot apply changes, {value} is not valid for setting {setting_name}", - ) - return False - - self.config = config_file.read_config() + if not self._validate_changes("Apply changes"): + return False + + if self._session_config is not None: + # Session mode: mutate the in-memory config without writing to disk + for setting_name, value in self.changes.items(): + config_file.set_setting(setting_name, value, self._session_config) + self.config = ConfigParser() + self.config.read_dict(self._session_config) + else: + # Workstation mode: read from disk, apply changes, write back + self.config = config_file.read_config() + for setting_name, value in self.changes.items(): + config_file.set_setting(setting_name, value, self.config) + config_file.write_config(self.config) - for setting_name, value in self.changes.items(): - config_file.set_setting(setting_name, value, self.config) root.setLevel(config_file.get_setting("settings.log_level")) api.get_deadline_cloud_library_telemetry_client().set_opt_out(config=self.config) @@ -838,8 +980,6 @@ def apply(self) -> bool: self.changes.clear() - config_file.write_config(self.config) - # Refresh the GUI (writing the config file should cause this, but do this redundantly to make sure) self.refresh() diff --git a/src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py b/src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py index 2bd2bba9a..7f15143e6 100644 --- a/src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py +++ b/src/deadline/client/ui/dialogs/submit_job_to_deadline_dialog.py @@ -9,6 +9,7 @@ import os import sys import json +from configparser import ConfigParser from typing import Any, Dict, Optional, Protocol import yaml @@ -35,7 +36,7 @@ from ...api._session import session_context as _session_context from ..deadline_authentication_status import DeadlineAuthenticationStatus from .._utils import block_signals, tr -from ...config import get_setting, set_setting, config_file +from ...config import get_setting, config_file, persist_job_id from ...exceptions import UserInitiatedCancel, NonValidInputError from ...job_bundle import create_job_history_bundle_dir from ...job_bundle.parameters import JobParameter @@ -44,7 +45,7 @@ from ..widgets.job_attachments_tab import JobAttachmentsWidget from ..widgets.shared_job_settings_tab import SharedJobSettingsWidget from ..widgets.host_requirements_tab import HostRequirementsWidget -from . import DeadlineConfigDialog, DeadlineLoginDialog +from . import ConfigureSettingsResult, DeadlineConfigDialog, DeadlineLoginDialog from ._types import JobBundlePurpose from ._help_dialog import _HelpDialog @@ -114,6 +115,7 @@ def __init__( attachments: AssetReferences, on_create_job_bundle_callback: OnCreateJobBundleCallback, parent: Optional[QWidget] = None, + session_config: Optional[ConfigParser] = None, f: Any = Qt.WindowFlags(), show_host_requirements_tab: bool = False, host_requirements: Optional[HostRequirements] = None, @@ -123,6 +125,7 @@ def __init__( # The Qt.Tool flag makes sure our widget stays in front of the main application window super().__init__(parent=parent, f=f) + self._session_config = session_config # Set window title with submitter package info if available window_title = tr("Submit to AWS Deadline Cloud") if submitter_info: @@ -147,6 +150,8 @@ def __init__( self.job_id = None self.job_history_bundle_dir: Optional[str] = None self.deadline_authentication_status = DeadlineAuthenticationStatus.getInstance() + if self._session_config is not None: + self.deadline_authentication_status.set_config(self._session_config) self.show_host_requirements_tab = show_host_requirements_tab self.known_asset_paths = known_asset_paths or [] self.should_close = False @@ -166,7 +171,16 @@ def __init__( def _submission_succeeded_signal_receiver(self, job_id: str): self.job_id = job_id - set_setting("defaults.job_id", job_id) + # Persist the job ID to the on-disk config under the correct + # farm/queue section so that CLI overrides (--farm-id / --queue-id) + # target the right place without changing the on-disk defaults. + config = self._session_config + persist_job_id( + job_id, + profile=get_setting("defaults.aws_profile_name", config=config), + farm_id=get_setting("defaults.farm_id", config=config), + queue_id=get_setting("defaults.queue_id", config=config), + ) def _close_event_receiver(self): if self.submitter_info.submitter_name != "JobBundle" and self.job_id: @@ -257,8 +271,8 @@ def _set_submit_button_state(self): # Enable/disable the Submit button based on whether the # AWS Deadline Cloud API is accessible and the farm+queue are configured. api_available = self.deadline_authentication_status.api_availability is True - farm_configured = get_setting("defaults.farm_id") != "" - queue_configured = get_setting("defaults.queue_id") != "" + farm_configured = get_setting("defaults.farm_id", config=self._session_config) != "" + queue_configured = get_setting("defaults.queue_id", config=self._session_config) != "" queue_valid = self.shared_job_settings.is_queue_valid() enable = api_available and farm_configured and queue_configured and queue_valid @@ -319,6 +333,7 @@ def _build_shared_job_settings_tab(self, initial_job_settings, initial_shared_pa initial_settings=initial_job_settings, initial_shared_parameter_values=initial_shared_parameter_values, parent=self, + config=self._session_config, ) self.shared_job_settings.parameter_changed.connect(self.on_shared_job_parameter_changed) self.shared_job_settings_tab.setWidget(self.shared_job_settings) @@ -403,13 +418,35 @@ def on_logout(self): # not always catch a change so force a refresh here. self.deadline_authentication_status.refresh_status() - def on_switch_profile_clicked(self): - if DeadlineConfigDialog.configure_settings(parent=self, set_profile_focus=True): + def _apply_settings_result(self, result: ConfigureSettingsResult) -> None: + """Apply the result from DeadlineConfigDialog.configure_settings().""" + if result.session_config is not None: + self._session_config = result.session_config + self.deadline_authentication_status.set_config(self._session_config) + self.shared_job_settings.set_session_config(self._session_config) self.refresh_deadline_settings() + def _ensure_session_config(self) -> ConfigParser: + """Return the current session config, creating one from disk if needed.""" + if self._session_config is None: + self._session_config = ConfigParser() + self._session_config.read_dict(config_file.read_config()) + return self._session_config + + def on_switch_profile_clicked(self): + result = DeadlineConfigDialog.configure_settings( + parent=self, + set_profile_focus=True, + session_config=self._ensure_session_config(), + ) + self._apply_settings_result(result) + def on_settings_button_clicked(self): - if DeadlineConfigDialog.configure_settings(parent=self): - self.refresh_deadline_settings() + result = DeadlineConfigDialog.configure_settings( + parent=self, + session_config=self._ensure_session_config(), + ) + self._apply_settings_result(result) def _on_help_button_clicked(self): """Show the Help dialog with submitter information.""" @@ -599,7 +636,7 @@ def on_submit(self): job_progress_dialog.start_job_submission( job_bundle_dir=self.job_history_bundle_dir, submitter_name=self.submitter_info.submitter_name, - config=config_file.read_config(), + config=self._session_config or config_file.read_config(), require_paths_exist=self.job_attachments.get_require_paths_exist(), job_parameters=job_parameters, known_asset_paths=self.known_asset_paths diff --git a/src/deadline/client/ui/job_bundle_submitter.py b/src/deadline/client/ui/job_bundle_submitter.py index 84a0d2ee0..4731c8edd 100644 --- a/src/deadline/client/ui/job_bundle_submitter.py +++ b/src/deadline/client/ui/job_bundle_submitter.py @@ -3,6 +3,7 @@ from __future__ import annotations import copy import os +from configparser import ConfigParser from logging import getLogger from typing import Any, Optional, Dict @@ -123,6 +124,7 @@ def show_job_bundle_submitter( submitter_info: Optional[SubmitterInfo] = None, known_asset_paths: Optional[list[str]] = None, job_parameters: Optional[list[dict[str, Any]]] = None, + session_config: Optional[ConfigParser] = None, ) -> Optional[SubmitJobToDeadlineDialog]: """ Opens an AWS Deadline Cloud job submission dialog for the provided job bundle. @@ -305,6 +307,7 @@ def on_create_job_bundle_callback( f=f, submitter_info=submitter_info, known_asset_paths=known_asset_paths, + session_config=session_config, ) if job_parameters: diff --git a/src/deadline/client/ui/translations/locales/de_DE.json b/src/deadline/client/ui/translations/locales/de_DE.json index a5cf52407..7c1cc5a1f 100644 --- a/src/deadline/client/ui/translations/locales/de_DE.json +++ b/src/deadline/client/ui/translations/locales/de_DE.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "Beim Zugriff auf die Anmeldeinformationen für das Profil '{profile}' ist ein Konfigurationsfehler aufgetreten.", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "Die AWS Deadline Cloud-API ist nicht erreichbar. Überprüfen Sie Ihren Authentifizierungsstatus.", - "AWS Deadline Cloud submission": "AWS Deadline Cloud-Übermittlung", - "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud-Workstation-Konfiguration", - "AWS profile": "AWS-Profil", "About": "Über", "Application Restart Required": "Neustart der Anwendung erforderlich", "Add": "Hinzufügen", @@ -22,14 +18,19 @@ "Attach input files": "Eingabedateien anhängen", "Attribute name": "Attributname", "Auto accept prompt defaults": "Standardwerte automatisch akzeptieren", - "CPU architecture": "CPU-Architektur", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "Die AWS Deadline Cloud-API ist nicht erreichbar. Überprüfen Sie Ihren Authentifizierungsstatus.", + "AWS Deadline Cloud submission": "AWS Deadline Cloud-Übermittlung", + "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud-Workstation-Konfiguration", + "AWS profile": "AWS-Profil", "Cancel": "Abbrechen", "Canceling submission...": "Übermittlung wird abgebrochen...", "Cannot submit job:\n\n\u2022 {issues}": "Job kann nicht übermittelt werden:\n\n\\u2022 {issues}", "Choose job bundle directory": "Jobpaket-Verzeichnis auswählen", "Close": "Schließen", + "Config File": "Konfigurationsdatei", "Conflict resolution option": "Option zur Konfliktlösung", "Copy": "Kopieren", + "CPU architecture": "CPU-Architektur", "Current: {current_version} -> New: {latest_version}": "Aktuell: {current_version} -> Neu: {latest_version}", "Current logging level": "Aktuelle Protokollierungsebene", "Custom host requirements": "Benutzerdefinierte Host-Anforderungen", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "Anmeldung bei AWS Deadline Cloud fehlgeschlagen:

{error}", "Farm": "Farm", "Farm settings": "Farm-Einstellungen", - "GPU memory (GiB)": "GPU-Speicher (GiB)", - "GPUs": "GPUs", "General settings": "Allgemeine Einstellungen", "General submission settings": "Allgemeine Übermittlungseinstellungen", "Global settings": "Globale Einstellungen", + "GPU memory (GiB)": "GPU-Speicher (GiB)", + "GPUs": "GPUs", "Hardware requirements": "Hardwareanforderungen", "Hashing progress": "Hashing-Fortschritt", "Help": "Hilfe", "Host requirements": "Host-Anforderungen", "Initial state": "Anfangszustand", "Issue With Profile Configuration": "Problem mit Profilkonfiguration", - "Job Properties": "Auftragseigenschaften", - "Job Submission Confirmation": "Bestätigung der Jobübermittlung", "Job attachments": "Arbeitsanhänge", "Job attachments filesystem options": "Dateisystemoptionen für Jobanhänge", "Job history directory": "Jobverlaufsverzeichnis", + "Job Properties": "Auftragseigenschaften", + "Job Submission Confirmation": "Bestätigung der Jobübermittlung", "Job submission confirmation": "Bestätigung der Jobübermittlung", "Job-specific settings": "Jobspezifische Einstellungen", "Known asset paths": "Bekannte Asset-Pfade", "Language": "Sprache", "Language will change next time the submitter is opened": "Die Sprache wird beim nächsten Öffnen des Submitters geändert", "Load Bundle": "Paket laden", + "Load from Disk": "Von Festplatte laden", "Log in": "Anmelden", "Log in to AWS Deadline Cloud": "Bei AWS Deadline Cloud anmelden", "Logging you in...": "Sie werden angemeldet...", @@ -106,6 +108,7 @@ "Require all input paths exist": "Alle Eingabepfade müssen vorhanden sein", "Run on all available worker hosts": "Auf allen verfügbaren Worker-Hosts ausführen", "Run on worker hosts that meet the following requirements": "Auf Worker-Hosts ausführen, die die folgenden Anforderungen erfüllen", + "Save to Disk": "Auf Festplatte speichern", "Saved the submission as a job bundle:\n{path}": "Die Übermittlung wurde als Jobpaket gespeichert:\n{path}", "Scratch space": "Temporärer Speicherplatz", "Set max worker count": "Maximale Worker-Anzahl festlegen", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - Sie sind abgemeldet.", "{profile} doesn't have access permissions to submit a job.": "{profile} hat keine Zugriffsberechtigungen zum Übermitteln eines Jobs.", "{submitter} job submission": "{submitter}-Jobübermittlung" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/en_US.json b/src/deadline/client/ui/translations/locales/en_US.json index 2a1435de7..6de41a616 100644 --- a/src/deadline/client/ui/translations/locales/en_US.json +++ b/src/deadline/client/ui/translations/locales/en_US.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "A configuration error was received while accessing credentials for the profile '{profile}'.", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "AWS Deadline Cloud API is not accessible. Check your authentication status.", - "AWS Deadline Cloud submission": "AWS Deadline Cloud submission", - "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud workstation configuration", - "AWS profile": "AWS profile", "About": "About", "Application Restart Required": "Application Restart Required", "Add": "Add", @@ -22,14 +18,19 @@ "Attach input files": "Attach input files", "Attribute name": "Attribute name", "Auto accept prompt defaults": "Auto accept prompt defaults", - "CPU architecture": "CPU architecture", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "AWS Deadline Cloud API is not accessible. Check your authentication status.", + "AWS Deadline Cloud submission": "AWS Deadline Cloud submission", + "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud workstation configuration", + "AWS profile": "AWS profile", "Cancel": "Cancel", "Canceling submission...": "Canceling submission...", "Cannot submit job:\n\n\u2022 {issues}": "Cannot submit job:\n\n\\u2022 {issues}", "Choose job bundle directory": "Choose job bundle directory", "Close": "Close", + "Config File": "Config File", "Conflict resolution option": "Conflict resolution option", "Copy": "Copy", + "CPU architecture": "CPU architecture", "Current: {current_version} -> New: {latest_version}": "Current: {current_version} -> New: {latest_version}", "Current logging level": "Current logging level", "Custom host requirements": "Custom host requirements", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "Failed to log in to AWS Deadline Cloud:

{error}", "Farm": "Farm", "Farm settings": "Farm settings", - "GPU memory (GiB)": "GPU memory (GiB)", - "GPUs": "GPUs", "General settings": "General settings", "General submission settings": "General submission settings", "Global settings": "Global settings", + "GPU memory (GiB)": "GPU memory (GiB)", + "GPUs": "GPUs", "Hardware requirements": "Hardware requirements", "Hashing progress": "Hashing progress", "Help": "Help", "Host requirements": "Host requirements", "Initial state": "Initial state", "Issue With Profile Configuration": "Issue With Profile Configuration", - "Job Properties": "Job Properties", - "Job Submission Confirmation": "Job Submission Confirmation", "Job attachments": "Job attachments", "Job attachments filesystem options": "Job attachments filesystem options", "Job history directory": "Job history directory", + "Job Properties": "Job Properties", + "Job Submission Confirmation": "Job Submission Confirmation", "Job submission confirmation": "Job submission confirmation", "Job-specific settings": "Job-specific settings", "Known asset paths": "Known asset paths", "Language": "Language", "Language will change next time the submitter is opened": "Language will change next time the submitter is opened", "Load Bundle": "Load Bundle", + "Load from Disk": "Load from Disk", "Log in": "Log in", "Log in to AWS Deadline Cloud": "Log in to AWS Deadline Cloud", "Logging you in...": "Logging you in...", @@ -106,6 +108,7 @@ "Require all input paths exist": "Require all input paths exist", "Run on all available worker hosts": "Run on all available worker hosts", "Run on worker hosts that meet the following requirements": "Run on worker hosts that meet the following requirements", + "Save to Disk": "Save to Disk", "Saved the submission as a job bundle:\n{path}": "Saved the submission as a job bundle:\n{path}", "Scratch space": "Scratch space", "Set max worker count": "Set max worker count", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - You are logged out.", "{profile} doesn't have access permissions to submit a job.": "{profile} doesn't have access permissions to submit a job.", "{submitter} job submission": "{submitter} job submission" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/es_ES.json b/src/deadline/client/ui/translations/locales/es_ES.json index 50587f1c4..255b9afbf 100644 --- a/src/deadline/client/ui/translations/locales/es_ES.json +++ b/src/deadline/client/ui/translations/locales/es_ES.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "Se recibió un error de configuración al acceder a las credenciales del perfil '{profile}'.", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "No se puede acceder a la API de AWS Deadline Cloud. Compruebe su estado de autenticación.", - "AWS Deadline Cloud submission": "Envío de AWS Deadline Cloud", - "AWS Deadline Cloud workstation configuration": "Configuración de estación de trabajo de AWS Deadline Cloud", - "AWS profile": "Perfil de AWS", "About": "Acerca de", "Application Restart Required": "Se requiere reiniciar la aplicación", "Add": "Agregar", @@ -22,14 +18,19 @@ "Attach input files": "Adjuntar archivos de entrada", "Attribute name": "Nombre de atributo", "Auto accept prompt defaults": "Aceptar automáticamente valores predeterminados", - "CPU architecture": "Arquitectura de CPU", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "No se puede acceder a la API de AWS Deadline Cloud. Compruebe su estado de autenticación.", + "AWS Deadline Cloud submission": "Envío de AWS Deadline Cloud", + "AWS Deadline Cloud workstation configuration": "Configuración de estación de trabajo de AWS Deadline Cloud", + "AWS profile": "Perfil de AWS", "Cancel": "Cancelar", "Canceling submission...": "Cancelando envío...", "Cannot submit job:\n\n\u2022 {issues}": "No se puede enviar el trabajo:\n\n\\u2022 {issues}", "Choose job bundle directory": "Elegir directorio de paquete de trabajos", "Close": "Cerrar", + "Config File": "Archivo de configuración", "Conflict resolution option": "Opción de resolución de conflictos", "Copy": "Copiar", + "CPU architecture": "Arquitectura de CPU", "Current: {current_version} -> New: {latest_version}": "Actual: {current_version} -> Nuevo: {latest_version}", "Current logging level": "Nivel de registro actual", "Custom host requirements": "Requisitos de host personalizados", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "Error al iniciar sesión en AWS Deadline Cloud:

{error}", "Farm": "Granja", "Farm settings": "Configuración de granja", - "GPU memory (GiB)": "Memoria de GPU (GiB)", - "GPUs": "GPU", "General settings": "Configuración general", "General submission settings": "Configuración general de envío", "Global settings": "Configuración global", + "GPU memory (GiB)": "Memoria de GPU (GiB)", + "GPUs": "GPU", "Hardware requirements": "Requisitos de hardware", "Hashing progress": "Progreso de hash", "Help": "Ayuda", "Host requirements": "Requisitos de host", "Initial state": "Estado inicial", "Issue With Profile Configuration": "Problema con la configuración del perfil", - "Job Properties": "Propiedades del trabajo", - "Job Submission Confirmation": "Confirmación de envío de trabajo", "Job attachments": "Adjuntos de trabajo", "Job attachments filesystem options": "Opciones del sistema de archivos de adjuntos de trabajo", "Job history directory": "Directorio de historial de trabajos", + "Job Properties": "Propiedades del trabajo", + "Job Submission Confirmation": "Confirmación de envío de trabajo", "Job submission confirmation": "Confirmación de envío de trabajo", "Job-specific settings": "Configuración específica del trabajo", "Known asset paths": "Rutas de recursos conocidas", "Language": "Idioma", "Language will change next time the submitter is opened": "El idioma cambiará la próxima vez que se abra el submitter", "Load Bundle": "Cargar paquete", + "Load from Disk": "Cargar desde disco", "Log in": "Iniciar sesión", "Log in to AWS Deadline Cloud": "Iniciar sesión en AWS Deadline Cloud", "Logging you in...": "Iniciando sesión...", @@ -106,6 +108,7 @@ "Require all input paths exist": "Requerir que existan todas las rutas de entrada", "Run on all available worker hosts": "Ejecutar en todos los hosts de trabajadores disponibles", "Run on worker hosts that meet the following requirements": "Ejecutar en hosts de trabajadores que cumplan los siguientes requisitos", + "Save to Disk": "Guardar en disco", "Saved the submission as a job bundle:\n{path}": "Se guardó el envío como paquete de trabajos:\n{path}", "Scratch space": "Espacio temporal", "Set max worker count": "Establecer recuento máximo de trabajadores", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - Ha cerrado sesión.", "{profile} doesn't have access permissions to submit a job.": "{profile} no tiene permisos de acceso para enviar un trabajo.", "{submitter} job submission": "Envío de trabajo de {submitter}" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/fr_FR.json b/src/deadline/client/ui/translations/locales/fr_FR.json index 0370c48e0..2918a5233 100644 --- a/src/deadline/client/ui/translations/locales/fr_FR.json +++ b/src/deadline/client/ui/translations/locales/fr_FR.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "Une erreur de configuration s'est produite lors de l'accès aux informations d'identification du profil '{profile}'.", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "L'API AWS Deadline Cloud n'est pas accessible. Vérifiez votre statut d'authentification.", - "AWS Deadline Cloud submission": "Soumission AWS Deadline Cloud", - "AWS Deadline Cloud workstation configuration": "Configuration de poste de travail AWS Deadline Cloud", - "AWS profile": "Profil AWS", "About": "À propos", "Application Restart Required": "Redémarrage de l'application requis", "Add": "Ajouter", @@ -22,14 +18,19 @@ "Attach input files": "Joindre des fichiers d'entrée", "Attribute name": "Nom d'attribut", "Auto accept prompt defaults": "Accepter automatiquement les valeurs par défaut", - "CPU architecture": "Architecture CPU", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "L'API AWS Deadline Cloud n'est pas accessible. Vérifiez votre statut d'authentification.", + "AWS Deadline Cloud submission": "Soumission AWS Deadline Cloud", + "AWS Deadline Cloud workstation configuration": "Configuration de poste de travail AWS Deadline Cloud", + "AWS profile": "Profil AWS", "Cancel": "Annuler", "Canceling submission...": "Annulation de la soumission...", "Cannot submit job:\n\n\u2022 {issues}": "Impossible de soumettre la tâche :\n\n\\u2022 {issues}", "Choose job bundle directory": "Choisir le répertoire du lot de tâches", "Close": "Fermer", + "Config File": "Fichier de configuration", "Conflict resolution option": "Option de résolution de conflits", "Copy": "Copier", + "CPU architecture": "Architecture CPU", "Current: {current_version} -> New: {latest_version}": "Actuel : {current_version} -> Nouveau : {latest_version}", "Current logging level": "Niveau de journalisation actuel", "Custom host requirements": "Exigences d'hôte personnalisées", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "Échec de la connexion à AWS Deadline Cloud :

{error}", "Farm": "Ferme", "Farm settings": "Paramètres de ferme", - "GPU memory (GiB)": "Mémoire GPU (Gio)", - "GPUs": "GPU", "General settings": "Paramètres généraux", "General submission settings": "Paramètres généraux de soumission", "Global settings": "Paramètres globaux", + "GPU memory (GiB)": "Mémoire GPU (Gio)", + "GPUs": "GPU", "Hardware requirements": "Exigences matérielles", "Hashing progress": "Progression du hachage", "Help": "Aide", "Host requirements": "Exigences d'hôte", "Initial state": "État initial", "Issue With Profile Configuration": "Problème avec la configuration du profil", - "Job Properties": "Propriétés de la tâche", - "Job Submission Confirmation": "Confirmation de soumission de tâche", "Job attachments": "Fichiers joints de tâche", "Job attachments filesystem options": "Options du système de fichiers des pièces jointes aux tâches", "Job history directory": "Répertoire d'historique des tâches", + "Job Properties": "Propriétés de la tâche", + "Job Submission Confirmation": "Confirmation de soumission de tâche", "Job submission confirmation": "Confirmation de soumission de tâche", "Job-specific settings": "Paramètres spécifiques à la tâche", "Known asset paths": "Chemins d'actifs connus", "Language": "Langue", "Language will change next time the submitter is opened": "La langue changera lors de la prochaine ouverture du submitter", "Load Bundle": "Charger le lot", + "Load from Disk": "Charger depuis le disque", "Log in": "Se connecter", "Log in to AWS Deadline Cloud": "Se connecter à AWS Deadline Cloud", "Logging you in...": "Connexion en cours...", @@ -106,6 +108,7 @@ "Require all input paths exist": "Exiger que tous les chemins d'entrée existent", "Run on all available worker hosts": "Exécuter sur tous les hôtes de travail disponibles", "Run on worker hosts that meet the following requirements": "Exécuter sur les hôtes de travail qui répondent aux exigences suivantes", + "Save to Disk": "Enregistrer sur le disque", "Saved the submission as a job bundle:\n{path}": "La soumission a été enregistrée en tant que lot de tâches :\n{path}", "Scratch space": "Espace temporaire", "Set max worker count": "Définir le nombre maximal de travailleurs", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - Vous êtes déconnecté.", "{profile} doesn't have access permissions to submit a job.": "{profile} n'a pas les autorisations d'accès pour soumettre une tâche.", "{submitter} job submission": "Soumission de tâche {submitter}" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/id_ID.json b/src/deadline/client/ui/translations/locales/id_ID.json index 2a066fc28..467682784 100644 --- a/src/deadline/client/ui/translations/locales/id_ID.json +++ b/src/deadline/client/ui/translations/locales/id_ID.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "Kesalahan konfigurasi diterima saat mengakses kredensial untuk profil '{profile}'.", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "API AWS Deadline Cloud tidak dapat diakses. Periksa status autentikasi Anda.", - "AWS Deadline Cloud submission": "Pengiriman AWS Deadline Cloud", - "AWS Deadline Cloud workstation configuration": "Konfigurasi workstation AWS Deadline Cloud", - "AWS profile": "Profil AWS", "About": "Tentang", "Application Restart Required": "Diperlukan restart aplikasi", "Add": "Tambah", @@ -22,14 +18,19 @@ "Attach input files": "Lampirkan file input", "Attribute name": "Nama atribut", "Auto accept prompt defaults": "Terima default secara otomatis", - "CPU architecture": "Arsitektur CPU", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "API AWS Deadline Cloud tidak dapat diakses. Periksa status autentikasi Anda.", + "AWS Deadline Cloud submission": "Pengiriman AWS Deadline Cloud", + "AWS Deadline Cloud workstation configuration": "Konfigurasi workstation AWS Deadline Cloud", + "AWS profile": "Profil AWS", "Cancel": "Batal", "Canceling submission...": "Membatalkan pengiriman...", "Cannot submit job:\n\n\u2022 {issues}": "Tidak dapat mengirim pekerjaan:\n\n\\u2022 {issues}", "Choose job bundle directory": "Pilih direktori bundel pekerjaan", "Close": "Tutup", + "Config File": "File Konfigurasi", "Conflict resolution option": "Opsi resolusi konflik", "Copy": "Salin", + "CPU architecture": "Arsitektur CPU", "Current: {current_version} -> New: {latest_version}": "Saat ini: {current_version} -> Baru: {latest_version}", "Current logging level": "Tingkat logging saat ini", "Custom host requirements": "Persyaratan host kustom", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "Gagal masuk ke AWS Deadline Cloud:

{error}", "Farm": "Peternakan", "Farm settings": "Pengaturan peternakan", - "GPU memory (GiB)": "Memori GPU (GiB)", - "GPUs": "GPU", "General settings": "Pengaturan umum", "General submission settings": "Pengaturan pengiriman umum", "Global settings": "Pengaturan global", + "GPU memory (GiB)": "Memori GPU (GiB)", + "GPUs": "GPU", "Hardware requirements": "Persyaratan perangkat keras", "Hashing progress": "Kemajuan hashing", "Help": "Bantuan", "Host requirements": "Persyaratan host", "Initial state": "Status awal", "Issue With Profile Configuration": "Masalah dengan konfigurasi profil", - "Job Properties": "Properti Job", - "Job Submission Confirmation": "Konfirmasi pengiriman pekerjaan", "Job attachments": "Lampiran Job", "Job attachments filesystem options": "Opsi sistem file lampiran pekerjaan", "Job history directory": "Direktori riwayat pekerjaan", + "Job Properties": "Properti Job", + "Job Submission Confirmation": "Konfirmasi pengiriman pekerjaan", "Job submission confirmation": "Konfirmasi pengiriman pekerjaan", "Job-specific settings": "Pengaturan khusus pekerjaan", "Known asset paths": "Jalur aset yang diketahui", "Language": "Bahasa", "Language will change next time the submitter is opened": "Bahasa akan berubah saat submitter dibuka kembali", "Load Bundle": "Muat bundel", + "Load from Disk": "Muat dari Disk", "Log in": "Masuk", "Log in to AWS Deadline Cloud": "Masuk ke AWS Deadline Cloud", "Logging you in...": "Memasukkan Anda...", @@ -106,6 +108,7 @@ "Require all input paths exist": "Memerlukan semua jalur input ada", "Run on all available worker hosts": "Jalankan di semua host pekerja yang tersedia", "Run on worker hosts that meet the following requirements": "Jalankan di host pekerja yang memenuhi persyaratan berikut", + "Save to Disk": "Simpan ke Disk", "Saved the submission as a job bundle:\n{path}": "Menyimpan pengiriman sebagai bundel pekerjaan:\n{path}", "Scratch space": "Ruang sementara", "Set max worker count": "Atur jumlah pekerja maksimum", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - Anda telah keluar.", "{profile} doesn't have access permissions to submit a job.": "{profile} tidak memiliki izin akses untuk mengirim pekerjaan.", "{submitter} job submission": "Pengiriman pekerjaan {submitter}" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/it_IT.json b/src/deadline/client/ui/translations/locales/it_IT.json index 3562adeb1..cf77e0a29 100644 --- a/src/deadline/client/ui/translations/locales/it_IT.json +++ b/src/deadline/client/ui/translations/locales/it_IT.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "Si è verificato un errore di configurazione durante l'accesso alle credenziali per il profilo '{profile}'.", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "L'API AWS Deadline Cloud non è accessibile. Verifica lo stato di autenticazione.", - "AWS Deadline Cloud submission": "Invio AWS Deadline Cloud", - "AWS Deadline Cloud workstation configuration": "Configurazione workstation AWS Deadline Cloud", - "AWS profile": "Profilo AWS", "About": "Informazioni", "Application Restart Required": "Riavvio dell'applicazione richiesto", "Add": "Aggiungi", @@ -22,14 +18,19 @@ "Attach input files": "Allega file di input", "Attribute name": "Nome attributo", "Auto accept prompt defaults": "Accetta automaticamente i valori predefiniti", - "CPU architecture": "Architettura CPU", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "L'API AWS Deadline Cloud non è accessibile. Verifica lo stato di autenticazione.", + "AWS Deadline Cloud submission": "Invio AWS Deadline Cloud", + "AWS Deadline Cloud workstation configuration": "Configurazione workstation AWS Deadline Cloud", + "AWS profile": "Profilo AWS", "Cancel": "Annulla", "Canceling submission...": "Annullamento invio...", "Cannot submit job:\n\n\u2022 {issues}": "Impossibile inviare il lavoro:\n\n\\u2022 {issues}", "Choose job bundle directory": "Scegli directory pacchetto lavoro", "Close": "Chiudi", + "Config File": "File di configurazione", "Conflict resolution option": "Opzione di risoluzione conflitti", "Copy": "Copia", + "CPU architecture": "Architettura CPU", "Current: {current_version} -> New: {latest_version}": "Corrente: {current_version} -> Nuovo: {latest_version}", "Current logging level": "Livello di registrazione corrente", "Custom host requirements": "Requisiti host personalizzati", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "Accesso ad AWS Deadline Cloud non riuscito:

{error}", "Farm": "Farm", "Farm settings": "Impostazioni farm", - "GPU memory (GiB)": "Memoria GPU (GiB)", - "GPUs": "GPU", "General settings": "Impostazioni generali", "General submission settings": "Impostazioni generali di invio", "Global settings": "Impostazioni globali", + "GPU memory (GiB)": "Memoria GPU (GiB)", + "GPUs": "GPU", "Hardware requirements": "Requisiti hardware", "Hashing progress": "Avanzamento hashing", "Help": "Aiuto", "Host requirements": "Requisiti host", "Initial state": "Stato iniziale", "Issue With Profile Configuration": "Problema con la configurazione del profilo", - "Job Properties": "Proprietà processo", - "Job Submission Confirmation": "Conferma invio lavoro", "Job attachments": "Allegati Job", "Job attachments filesystem options": "Opzioni filesystem allegati lavoro", "Job history directory": "Directory cronologia lavori", + "Job Properties": "Proprietà processo", + "Job Submission Confirmation": "Conferma invio lavoro", "Job submission confirmation": "Conferma invio lavoro", "Job-specific settings": "Impostazioni specifiche del lavoro", "Known asset paths": "Percorsi asset noti", "Language": "Lingua", "Language will change next time the submitter is opened": "La lingua cambierà alla prossima apertura del submitter", "Load Bundle": "Carica pacchetto", + "Load from Disk": "Carica da disco", "Log in": "Accedi", "Log in to AWS Deadline Cloud": "Accedi ad AWS Deadline Cloud", "Logging you in...": "Accesso in corso...", @@ -106,6 +108,7 @@ "Require all input paths exist": "Richiedi che tutti i percorsi di input esistano", "Run on all available worker hosts": "Esegui su tutti gli host worker disponibili", "Run on worker hosts that meet the following requirements": "Esegui su host worker che soddisfano i seguenti requisiti", + "Save to Disk": "Salva su disco", "Saved the submission as a job bundle:\n{path}": "Invio salvato come pacchetto lavoro:\n{path}", "Scratch space": "Spazio temporaneo", "Set max worker count": "Imposta numero massimo di worker", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - Hai effettuato il logout.", "{profile} doesn't have access permissions to submit a job.": "{profile} non dispone delle autorizzazioni di accesso per inviare un lavoro.", "{submitter} job submission": "Invio lavoro {submitter}" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/ja_JP.json b/src/deadline/client/ui/translations/locales/ja_JP.json index 8459dc03b..c2c1d9a33 100644 --- a/src/deadline/client/ui/translations/locales/ja_JP.json +++ b/src/deadline/client/ui/translations/locales/ja_JP.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "プロファイル '{profile}' の認証情報にアクセス中に設定エラーが発生しました。", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "AWS Deadline Cloud API にアクセスできません。認証ステータスを確認してください。", - "AWS Deadline Cloud submission": "AWS Deadline Cloud 送信", - "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud ワークステーション設定", - "AWS profile": "AWS プロファイル", "About": "バージョン情報", "Application Restart Required": "アプリケーションの再起動が必要です", "Add": "追加", @@ -22,14 +18,19 @@ "Attach input files": "入力ファイルを添付", "Attribute name": "属性名", "Auto accept prompt defaults": "デフォルト値を自動的に受け入れる", - "CPU architecture": "CPU アーキテクチャ", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "AWS Deadline Cloud API にアクセスできません。認証ステータスを確認してください。", + "AWS Deadline Cloud submission": "AWS Deadline Cloud 送信", + "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud ワークステーション設定", + "AWS profile": "AWS プロファイル", "Cancel": "キャンセル", "Canceling submission...": "送信をキャンセルしています...", "Cannot submit job:\n\n\u2022 {issues}": "ジョブを送信できません:\n\n\\u2022 {issues}", "Choose job bundle directory": "ジョブバンドルディレクトリを選択", "Close": "閉じる", + "Config File": "設定ファイル", "Conflict resolution option": "競合解決オプション", "Copy": "コピー", + "CPU architecture": "CPU アーキテクチャ", "Current: {current_version} -> New: {latest_version}": "現在: {current_version} -> 新規: {latest_version}", "Current logging level": "現在のログレベル", "Custom host requirements": "カスタムホスト要件", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "AWS Deadline Cloud へのログインに失敗しました:

{error}", "Farm": "ファーム", "Farm settings": "ファーム設定", - "GPU memory (GiB)": "GPU メモリ (GiB)", - "GPUs": "GPU", "General settings": "一般設定", "General submission settings": "一般送信設定", "Global settings": "グローバル設定", + "GPU memory (GiB)": "GPU メモリ (GiB)", + "GPUs": "GPU", "Hardware requirements": "ハードウェア要件", "Hashing progress": "ハッシュ進行状況", "Help": "ヘルプ", "Host requirements": "ホスト要件", "Initial state": "初期状態", "Issue With Profile Configuration": "プロファイル設定の問題", - "Job Properties": "ジョブプロパティ", - "Job Submission Confirmation": "ジョブ送信の確認", "Job attachments": "ジョブアタッチメント", "Job attachments filesystem options": "ジョブアタッチメントファイルシステムオプション", "Job history directory": "ジョブ履歴ディレクトリ", + "Job Properties": "ジョブプロパティ", + "Job Submission Confirmation": "ジョブ送信の確認", "Job submission confirmation": "ジョブ送信の確認", "Job-specific settings": "ジョブ固有の設定", "Known asset paths": "既知のアセットパス", "Language": "言語", "Language will change next time the submitter is opened": "言語は次回サブミッターを開いたときに変更されます", "Load Bundle": "バンドルを読み込む", + "Load from Disk": "ディスクから読み込み", "Log in": "ログイン", "Log in to AWS Deadline Cloud": "AWS Deadline Cloud にログイン", "Logging you in...": "ログイン中...", @@ -106,6 +108,7 @@ "Require all input paths exist": "すべての入力パスが存在することを要求", "Run on all available worker hosts": "使用可能なすべてのワーカーホストで実行", "Run on worker hosts that meet the following requirements": "次の要件を満たすワーカーホストで実行", + "Save to Disk": "ディスクに保存", "Saved the submission as a job bundle:\n{path}": "送信をジョブバンドルとして保存しました:\n{path}", "Scratch space": "一時領域", "Set max worker count": "最大ワーカー数を設定", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - ログアウトしています。", "{profile} doesn't have access permissions to submit a job.": "{profile} にはジョブを送信するアクセス許可がありません。", "{submitter} job submission": "{submitter} ジョブ送信" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/ko_KR.json b/src/deadline/client/ui/translations/locales/ko_KR.json index db3e7eb85..f256c12a2 100644 --- a/src/deadline/client/ui/translations/locales/ko_KR.json +++ b/src/deadline/client/ui/translations/locales/ko_KR.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "프로필 '{profile}'의 자격 증명에 액세스하는 동안 구성 오류가 발생했습니다.", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "AWS Deadline Cloud API에 액세스할 수 없습니다. 인증 상태를 확인하세요.", - "AWS Deadline Cloud submission": "AWS Deadline Cloud 제출", - "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud 워크스테이션 구성", - "AWS profile": "AWS 프로필", "About": "정보", "Application Restart Required": "애플리케이션 재시작 필요", "Add": "추가", @@ -22,14 +18,19 @@ "Attach input files": "입력 파일 연결", "Attribute name": "속성 이름", "Auto accept prompt defaults": "기본값 자동 수락", - "CPU architecture": "CPU 아키텍처", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "AWS Deadline Cloud API에 액세스할 수 없습니다. 인증 상태를 확인하세요.", + "AWS Deadline Cloud submission": "AWS Deadline Cloud 제출", + "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud 워크스테이션 구성", + "AWS profile": "AWS 프로필", "Cancel": "취소", "Canceling submission...": "제출 취소 중...", "Cannot submit job:\n\n\u2022 {issues}": "작업을 제출할 수 없습니다:\n\n\\u2022 {issues}", "Choose job bundle directory": "작업 번들 디렉터리 선택", "Close": "닫기", + "Config File": "구성 파일", "Conflict resolution option": "충돌 해결 옵션", "Copy": "복사", + "CPU architecture": "CPU 아키텍처", "Current: {current_version} -> New: {latest_version}": "현재: {current_version} -> 새 버전: {latest_version}", "Current logging level": "현재 로깅 수준", "Custom host requirements": "사용자 지정 호스트 요구 사항", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "AWS Deadline Cloud에 로그인하지 못했습니다:

{error}", "Farm": "팜", "Farm settings": "팜 설정", - "GPU memory (GiB)": "GPU 메모리(GiB)", - "GPUs": "GPU", "General settings": "일반 설정", "General submission settings": "일반 제출 설정", "Global settings": "전역 설정", + "GPU memory (GiB)": "GPU 메모리(GiB)", + "GPUs": "GPU", "Hardware requirements": "하드웨어 요구 사항", "Hashing progress": "해싱 진행률", "Help": "도움말", "Host requirements": "호스트 요구 사항", "Initial state": "초기 상태", "Issue With Profile Configuration": "프로필 구성 문제", - "Job Properties": "작업 속성", - "Job Submission Confirmation": "작업 제출 확인", "Job attachments": "작업 첨부 파일", "Job attachments filesystem options": "작업 첨부 파일 파일 시스템 옵션", "Job history directory": "작업 기록 디렉터리", + "Job Properties": "작업 속성", + "Job Submission Confirmation": "작업 제출 확인", "Job submission confirmation": "작업 제출 확인", "Job-specific settings": "작업별 설정", "Known asset paths": "알려진 자산 경로", "Language": "언어", "Language will change next time the submitter is opened": "언어는 다음에 제출자를 열 때 변경됩니다", "Load Bundle": "번들 로드", + "Load from Disk": "디스크에서 불러오기", "Log in": "로그인", "Log in to AWS Deadline Cloud": "AWS Deadline Cloud에 로그인", "Logging you in...": "로그인 중...", @@ -106,6 +108,7 @@ "Require all input paths exist": "모든 입력 경로가 존재해야 함", "Run on all available worker hosts": "사용 가능한 모든 작업자 호스트에서 실행", "Run on worker hosts that meet the following requirements": "다음 요구 사항을 충족하는 작업자 호스트에서 실행", + "Save to Disk": "디스크에 저장", "Saved the submission as a job bundle:\n{path}": "제출을 작업 번들로 저장했습니다:\n{path}", "Scratch space": "임시 공간", "Set max worker count": "최대 작업자 수 설정", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - 로그아웃되었습니다.", "{profile} doesn't have access permissions to submit a job.": "{profile}에 작업을 제출할 액세스 권한이 없습니다.", "{submitter} job submission": "{submitter} 작업 제출" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/pt_BR.json b/src/deadline/client/ui/translations/locales/pt_BR.json index 0a1cc251b..6ff7b7b94 100644 --- a/src/deadline/client/ui/translations/locales/pt_BR.json +++ b/src/deadline/client/ui/translations/locales/pt_BR.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "Um erro de configuração foi recebido ao acessar as credenciais do perfil '{profile}'.", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "A API do AWS Deadline Cloud não está acessível. Verifique seu status de autenticação.", - "AWS Deadline Cloud submission": "Envio do AWS Deadline Cloud", - "AWS Deadline Cloud workstation configuration": "Configuração de estação de trabalho do AWS Deadline Cloud", - "AWS profile": "Perfil da AWS", "About": "Sobre", "Application Restart Required": "Reinicialização do aplicativo necessária", "Add": "Adicionar", @@ -22,14 +18,19 @@ "Attach input files": "Anexar arquivos de entrada", "Attribute name": "Nome do atributo", "Auto accept prompt defaults": "Aceitar automaticamente padrões", - "CPU architecture": "Arquitetura da CPU", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "A API do AWS Deadline Cloud não está acessível. Verifique seu status de autenticação.", + "AWS Deadline Cloud submission": "Envio do AWS Deadline Cloud", + "AWS Deadline Cloud workstation configuration": "Configuração de estação de trabalho do AWS Deadline Cloud", + "AWS profile": "Perfil da AWS", "Cancel": "Cancelar", "Canceling submission...": "Cancelando envio...", "Cannot submit job:\n\n\u2022 {issues}": "Não é possível enviar o trabalho:\n\n\\u2022 {issues}", "Choose job bundle directory": "Escolher diretório do pacote de tarefas", "Close": "Fechar", + "Config File": "Arquivo de configuração", "Conflict resolution option": "Opção de resolução de conflitos", "Copy": "Copiar", + "CPU architecture": "Arquitetura da CPU", "Current: {current_version} -> New: {latest_version}": "Atual: {current_version} -> Novo: {latest_version}", "Current logging level": "Nível de registro atual", "Custom host requirements": "Requisitos de host personalizados", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "Falha ao fazer login no AWS Deadline Cloud:

{error}", "Farm": "Fazenda", "Farm settings": "Configurações da fazenda", - "GPU memory (GiB)": "Memória da GPU (GiB)", - "GPUs": "GPUs", "General settings": "Configurações gerais", "General submission settings": "Configurações gerais de envio", "Global settings": "Configurações globais", + "GPU memory (GiB)": "Memória da GPU (GiB)", + "GPUs": "GPUs", "Hardware requirements": "Requisitos de hardware", "Hashing progress": "Progresso de hash", "Help": "Ajuda", "Host requirements": "Requisitos de host", "Initial state": "Estado inicial", "Issue With Profile Configuration": "Problema com a configuração do perfil", - "Job Properties": "Propriedades do trabalho", - "Job Submission Confirmation": "Confirmação de envio de trabalho", "Job attachments": "Anexos de trabalho", "Job attachments filesystem options": "Opções do sistema de arquivos de anexos de tarefas", "Job history directory": "Diretório de histórico de trabalhos", + "Job Properties": "Propriedades do trabalho", + "Job Submission Confirmation": "Confirmação de envio de trabalho", "Job submission confirmation": "Confirmação de envio de trabalho", "Job-specific settings": "Configurações específicas do trabalho", "Known asset paths": "Caminhos de ativos conhecidos", "Language": "Idioma", "Language will change next time the submitter is opened": "O idioma será alterado na próxima vez que o submitter for aberto", "Load Bundle": "Carregar pacote", + "Load from Disk": "Carregar do disco", "Log in": "Fazer login", "Log in to AWS Deadline Cloud": "Fazer login no AWS Deadline Cloud", "Logging you in...": "Fazendo login...", @@ -106,6 +108,7 @@ "Require all input paths exist": "Exigir que todos os caminhos de entrada existam", "Run on all available worker hosts": "Executar em todos os hosts de trabalho disponíveis", "Run on worker hosts that meet the following requirements": "Executar em hosts de trabalho que atendam aos seguintes requisitos", + "Save to Disk": "Salvar em disco", "Saved the submission as a job bundle:\n{path}": "O envio foi salvo como um pacote de tarefas:\n{path}", "Scratch space": "Espaço temporário", "Set max worker count": "Definir contagem máxima de trabalhadores", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - Você está desconectado.", "{profile} doesn't have access permissions to submit a job.": "{profile} não tem permissões de acesso para enviar um trabalho.", "{submitter} job submission": "Envio de trabalho {submitter}" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/tr_TR.json b/src/deadline/client/ui/translations/locales/tr_TR.json index 618ede40c..e4a8f8aa6 100644 --- a/src/deadline/client/ui/translations/locales/tr_TR.json +++ b/src/deadline/client/ui/translations/locales/tr_TR.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "'{profile}' profili için kimlik bilgilerine erişilirken bir yapılandırma hatası alındı.", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "AWS Deadline Cloud API'sine erişilemiyor. Kimlik doğrulama durumunuzu kontrol edin.", - "AWS Deadline Cloud submission": "AWS Deadline Cloud gönderimi", - "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud iş istasyonu yapılandırması", - "AWS profile": "AWS profili", "About": "Hakkında", "Application Restart Required": "Uygulama yeniden başlatma gerekli", "Add": "Ekle", @@ -22,14 +18,19 @@ "Attach input files": "Giriş dosyalarını ekle", "Attribute name": "Öznitelik adı", "Auto accept prompt defaults": "Varsayılanları otomatik olarak kabul et", - "CPU architecture": "CPU mimarisi", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "AWS Deadline Cloud API'sine erişilemiyor. Kimlik doğrulama durumunuzu kontrol edin.", + "AWS Deadline Cloud submission": "AWS Deadline Cloud gönderimi", + "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud iş istasyonu yapılandırması", + "AWS profile": "AWS profili", "Cancel": "İptal", "Canceling submission...": "Gönderim iptal ediliyor...", "Cannot submit job:\n\n\u2022 {issues}": "İş gönderilemedi:\n\n\\u2022 {issues}", "Choose job bundle directory": "İş paketi dizini seçin", "Close": "Kapat", + "Config File": "Yapılandırma Dosyası", "Conflict resolution option": "Çakışma çözümleme seçeneği", "Copy": "Kopyala", + "CPU architecture": "CPU mimarisi", "Current: {current_version} -> New: {latest_version}": "Mevcut: {current_version} -> Yeni: {latest_version}", "Current logging level": "Geçerli günlük kaydı düzeyi", "Custom host requirements": "Özel ana bilgisayar gereksinimleri", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "AWS Deadline Cloud'da oturum açılamadı:

{error}", "Farm": "Farm", "Farm settings": "Farm ayarları", - "GPU memory (GiB)": "GPU belleği (GiB)", - "GPUs": "GPU'lar", "General settings": "Genel ayarlar", "General submission settings": "Genel gönderim ayarları", "Global settings": "Genel ayarlar", + "GPU memory (GiB)": "GPU belleği (GiB)", + "GPUs": "GPU'lar", "Hardware requirements": "Donanım gereksinimleri", "Hashing progress": "Karma oluşturma ilerlemesi", "Help": "Yardım", "Host requirements": "Ana bilgisayar gereksinimleri", "Initial state": "Başlangıç durumu", "Issue With Profile Configuration": "Profil yapılandırmasıyla ilgili sorun", - "Job Properties": "İş özellikleri", - "Job Submission Confirmation": "İş gönderimi onayı", "Job attachments": "İş ekleri", "Job attachments filesystem options": "İş ekleri dosya sistemi seçenekleri", "Job history directory": "İş geçmişi dizini", + "Job Properties": "İş özellikleri", + "Job Submission Confirmation": "İş gönderimi onayı", "Job submission confirmation": "İş gönderimi onayı", "Job-specific settings": "İşe özel ayarlar", "Known asset paths": "Bilinen varlık yolları", "Language": "Dil", "Language will change next time the submitter is opened": "Dil, submitter bir sonraki açılışında değişecektir", "Load Bundle": "Paket yükle", + "Load from Disk": "Diskten yükle", "Log in": "Oturum aç", "Log in to AWS Deadline Cloud": "AWS Deadline Cloud'da oturum aç", "Logging you in...": "Oturum açılıyor...", @@ -106,6 +108,7 @@ "Require all input paths exist": "Tüm giriş yollarının var olmasını gerektir", "Run on all available worker hosts": "Tüm kullanılabilir çalışan ana bilgisayarlarda çalıştır", "Run on worker hosts that meet the following requirements": "Aşağıdaki gereksinimleri karşılayan çalışan ana bilgisayarlarda çalıştır", + "Save to Disk": "Diske kaydet", "Saved the submission as a job bundle:\n{path}": "Gönderim iş paketi olarak kaydedildi:\n{path}", "Scratch space": "Geçici alan", "Set max worker count": "Maksimum çalışan sayısını ayarla", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - Oturumunuz kapatıldı.", "{profile} doesn't have access permissions to submit a job.": "{profile} bir iş göndermek için erişim izinlerine sahip değil.", "{submitter} job submission": "{submitter} iş gönderimi" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/zh_CN.json b/src/deadline/client/ui/translations/locales/zh_CN.json index 0b5a7e8e5..eaa9f69d1 100644 --- a/src/deadline/client/ui/translations/locales/zh_CN.json +++ b/src/deadline/client/ui/translations/locales/zh_CN.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "访问配置文件 '{profile}' 的凭证时收到配置错误。", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "无法访问 AWS Deadline Cloud API。请检查您的身份验证状态。", - "AWS Deadline Cloud submission": "AWS Deadline Cloud 提交", - "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud 工作站配置", - "AWS profile": "AWS 配置文件", "About": "关于", "Application Restart Required": "需要重新启动应用程序", "Add": "添加", @@ -22,14 +18,19 @@ "Attach input files": "附加输入文件", "Attribute name": "属性名称", "Auto accept prompt defaults": "自动接受默认值", - "CPU architecture": "CPU 架构", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "无法访问 AWS Deadline Cloud API。请检查您的身份验证状态。", + "AWS Deadline Cloud submission": "AWS Deadline Cloud 提交", + "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud 工作站配置", + "AWS profile": "AWS 配置文件", "Cancel": "取消", "Canceling submission...": "正在取消提交...", "Cannot submit job:\n\n\u2022 {issues}": "无法提交作业:\n\n\\u2022 {issues}", "Choose job bundle directory": "选择作业捆绑包目录", "Close": "关闭", + "Config File": "配置文件", "Conflict resolution option": "冲突解决选项", "Copy": "复制", + "CPU architecture": "CPU 架构", "Current: {current_version} -> New: {latest_version}": "当前: {current_version} -> 新版本: {latest_version}", "Current logging level": "当前日志记录级别", "Custom host requirements": "自定义主机要求", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "登录 AWS Deadline Cloud 失败:

{error}", "Farm": "服务器农场", "Farm settings": "服务器农场设置", - "GPU memory (GiB)": "GPU 内存 (GiB)", - "GPUs": "GPU", "General settings": "常规设置", "General submission settings": "常规提交设置", "Global settings": "全局设置", + "GPU memory (GiB)": "GPU 内存 (GiB)", + "GPUs": "GPU", "Hardware requirements": "硬件要求", "Hashing progress": "哈希进度", "Help": "帮助", "Host requirements": "主机要求", "Initial state": "初始状态", "Issue With Profile Configuration": "配置文件配置问题", - "Job Properties": "作业属性", - "Job Submission Confirmation": "作业提交确认", "Job attachments": "作业附件", "Job attachments filesystem options": "作业附件文件系统选项", "Job history directory": "作业历史记录目录", + "Job Properties": "作业属性", + "Job Submission Confirmation": "作业提交确认", "Job submission confirmation": "作业提交确认", "Job-specific settings": "作业特定设置", "Known asset paths": "已知资产路径", "Language": "语言", "Language will change next time the submitter is opened": "语言将在下次打开提交器时更改", "Load Bundle": "加载捆绑包", + "Load from Disk": "从磁盘加载", "Log in": "登录", "Log in to AWS Deadline Cloud": "登录 AWS Deadline Cloud", "Logging you in...": "正在登录...", @@ -106,6 +108,7 @@ "Require all input paths exist": "要求所有输入路径存在", "Run on all available worker hosts": "在所有可用的工作主机上运行", "Run on worker hosts that meet the following requirements": "在满足以下要求的工作主机上运行", + "Save to Disk": "保存到磁盘", "Saved the submission as a job bundle:\n{path}": "已将提交保存为作业捆绑包:\n{path}", "Scratch space": "临时空间", "Set max worker count": "设置最大工作线程数", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - 您已登出。", "{profile} doesn't have access permissions to submit a job.": "{profile} 没有提交作业的访问权限。", "{submitter} job submission": "{submitter} 作业提交" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/translations/locales/zh_TW.json b/src/deadline/client/ui/translations/locales/zh_TW.json index a44253123..cd717e03b 100644 --- a/src/deadline/client/ui/translations/locales/zh_TW.json +++ b/src/deadline/client/ui/translations/locales/zh_TW.json @@ -1,10 +1,6 @@ { "...": "...", "A configuration error was received while accessing credentials for the profile '{profile}'.": "存取設定檔 '{profile}' 的憑證時收到組態錯誤。", - "AWS Deadline Cloud API is not accessible. Check your authentication status.": "無法存取 AWS Deadline Cloud API。請檢查您的身分驗證狀態。", - "AWS Deadline Cloud submission": "AWS Deadline Cloud 提交", - "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud 工作站組態", - "AWS profile": "AWS 設定檔", "About": "關於", "Application Restart Required": "需要重新啟動應用程式", "Add": "新增", @@ -22,14 +18,19 @@ "Attach input files": "附加輸入檔案", "Attribute name": "屬性名稱", "Auto accept prompt defaults": "自動接受預設值", - "CPU architecture": "CPU 架構", + "AWS Deadline Cloud API is not accessible. Check your authentication status.": "無法存取 AWS Deadline Cloud API。請檢查您的身分驗證狀態。", + "AWS Deadline Cloud submission": "AWS Deadline Cloud 提交", + "AWS Deadline Cloud workstation configuration": "AWS Deadline Cloud 工作站組態", + "AWS profile": "AWS 設定檔", "Cancel": "取消", "Canceling submission...": "正在取消提交...", "Cannot submit job:\n\n\u2022 {issues}": "無法提交任務:\n\n\\u2022 {issues}", "Choose job bundle directory": "選擇任務套件目錄", "Close": "關閉", + "Config File": "設定檔", "Conflict resolution option": "衝突解決選項", "Copy": "複製", + "CPU architecture": "CPU 架構", "Current: {current_version} -> New: {latest_version}": "目前: {current_version} -> 新版本: {latest_version}", "Current logging level": "目前的日誌記錄層級", "Custom host requirements": "自訂主機需求", @@ -50,28 +51,29 @@ "Failed to log in to AWS Deadline Cloud:

{error}": "登入 AWS Deadline Cloud 失敗:

{error}", "Farm": "伺服器陣列", "Farm settings": "伺服器陣列設定", - "GPU memory (GiB)": "GPU 記憶體 (GiB)", - "GPUs": "GPU", "General settings": "一般設定", "General submission settings": "一般提交設定", "Global settings": "全域設定", + "GPU memory (GiB)": "GPU 記憶體 (GiB)", + "GPUs": "GPU", "Hardware requirements": "硬體需求", "Hashing progress": "雜湊進度", "Help": "說明", "Host requirements": "主機需求", "Initial state": "初始狀態", "Issue With Profile Configuration": "設定檔組態問題", - "Job Properties": "任務屬性", - "Job Submission Confirmation": "任務提交確認", "Job attachments": "任務附件", "Job attachments filesystem options": "任務附件檔案系統選項", "Job history directory": "任務歷史記錄目錄", + "Job Properties": "任務屬性", + "Job Submission Confirmation": "任務提交確認", "Job submission confirmation": "任務提交確認", "Job-specific settings": "任務特定設定", "Known asset paths": "已知資產路徑", "Language": "語言", "Language will change next time the submitter is opened": "語言將在下次開啟提交器時變更", "Load Bundle": "載入套件", + "Load from Disk": "從磁碟載入", "Log in": "登入", "Log in to AWS Deadline Cloud": "登入 AWS Deadline Cloud", "Logging you in...": "正在登入...", @@ -106,6 +108,7 @@ "Require all input paths exist": "要求所有輸入路徑存在", "Run on all available worker hosts": "在所有可用的工作者主機上執行", "Run on worker hosts that meet the following requirements": "在符合下列需求的工作者主機上執行", + "Save to Disk": "儲存到磁碟", "Saved the submission as a job bundle:\n{path}": "已將提交儲存為任務套件:\n{path}", "Scratch space": "暫存空間", "Set max worker count": "設定工作程序數上限", @@ -142,4 +145,4 @@ "{profile} - You are logged out.": "{profile} - 您已登出。", "{profile} doesn't have access permissions to submit a job.": "{profile} 沒有提交任務的存取許可。", "{submitter} job submission": "{submitter} 任務提交" -} \ No newline at end of file +} diff --git a/src/deadline/client/ui/widgets/shared_job_settings_tab.py b/src/deadline/client/ui/widgets/shared_job_settings_tab.py index fb873fb47..5ff04151c 100644 --- a/src/deadline/client/ui/widgets/shared_job_settings_tab.py +++ b/src/deadline/client/ui/widgets/shared_job_settings_tab.py @@ -7,6 +7,7 @@ from __future__ import annotations import sys +from configparser import ConfigParser from typing import Any, Dict, List, Optional from qtpy.QtCore import Qt, Signal # type: ignore @@ -58,11 +59,14 @@ def __init__( *, initial_settings: Any, initial_shared_parameter_values: dict[str, Any], + config: Optional[ConfigParser] = None, parent: Optional[QWidget] = None, ): super().__init__(parent=parent) layout = QVBoxLayout(self) + self._config = config + # This is a dictionary {: } containing values to # override the queue parameter defaults. self.initial_shared_parameter_values = initial_shared_parameter_values @@ -72,7 +76,9 @@ def __init__( ) layout.addWidget(self.shared_job_properties_box) - self.deadline_cloud_settings_box = DeadlineCloudSettingsWidget(parent=self) + self.deadline_cloud_settings_box = DeadlineCloudSettingsWidget( + config=self._config, parent=self + ) layout.addWidget(self.deadline_cloud_settings_box) self.queue_parameters_box = OpenJDParametersWidget( @@ -84,12 +90,14 @@ def __init__( ) # Track current farm/queue IDs for change detection - self.farm_id = get_setting("defaults.farm_id") - self.queue_id = get_setting("defaults.queue_id") + self.farm_id = get_setting("defaults.farm_id", config=self._config) + self.queue_id = get_setting("defaults.queue_id", config=self._config) self.__valid_queue = False # Connect to the controller for queue parameters self._controller = DeadlineUIController.getInstance() + if self._config is not None: + self._controller.set_config(self._config) self._controller.queue_parameters_updated.connect( self._handle_queue_parameters_update, Qt.QueuedConnection ) @@ -109,6 +117,14 @@ def __init__( if name.startswith("deadline:"): self.set_parameter_value({"name": name, "value": value}) + def set_session_config(self, session_config: ConfigParser) -> None: + """Update the session config used by this widget and its children.""" + self._config = session_config + self._controller.set_config(session_config) + self.deadline_cloud_settings_box._config = session_config + self.deadline_cloud_settings_box.farm_box._config = session_config + self.deadline_cloud_settings_box.queue_box._config = session_config + def refresh_ui(self, job_settings: Any, load_new_bundle: bool = False): # Refresh the job settings in the UI self.shared_job_properties_box.refresh_ui(job_settings) @@ -127,8 +143,8 @@ def refresh_queue_parameters(self, load_new_bundle: bool = False): """ If the default queue id or job bundle has changed, refresh the queue parameters. """ - farm_id = get_setting("defaults.farm_id") - queue_id = get_setting("defaults.queue_id") + farm_id = get_setting("defaults.farm_id", config=self._config) + queue_id = get_setting("defaults.queue_id", config=self._config) if not farm_id or not queue_id: self.queue_parameters_box.rebuild_ui(async_loading_state="") return # If the user has not selected a farm or queue ID, don't try to load @@ -164,8 +180,8 @@ def _start_load_queue_parameters(self): """ Triggers the controller to load queue parameters. """ - self.farm_id = farm_id = get_setting("defaults.farm_id") - self.queue_id = queue_id = get_setting("defaults.queue_id") + self.farm_id = farm_id = get_setting("defaults.farm_id", config=self._config) + self.queue_id = queue_id = get_setting("defaults.queue_id", config=self._config) if not self.farm_id or not self.queue_id: # If the user has not selected a farm or queue ID, don't bother loading return @@ -442,8 +458,14 @@ class DeadlineCloudSettingsWidget(QGroupBox): UI component for the Deadline Cloud settings. """ - def __init__(self, *, parent: Optional[QWidget] = None): + def __init__( + self, + *, + config: Optional[ConfigParser] = None, + parent: Optional[QWidget] = None, + ): super().__init__(tr("Deadline Cloud settings"), parent=parent) + self._config = config self.deadline_settings: Dict[str, Any] = {"counter": -1} self.layout = QFormLayout(self) self.layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) @@ -460,11 +482,11 @@ def _build_ui(self): Build the UI for the Deadline settings """ self.farm_box_label = QLabel(tr("Farm")) - self.farm_box = DeadlineFarmDisplay() + self.farm_box = DeadlineFarmDisplay(config=self._config) self.layout.addRow(self.farm_box_label, self.farm_box) self.queue_box_label = QLabel(tr("Queue")) - self.queue_box = DeadlineQueueDisplay() + self.queue_box = DeadlineQueueDisplay(config=self._config) self.layout.addRow(self.queue_box_label, self.queue_box) def refresh_setting_controls(self, deadline_authorized): @@ -499,12 +521,20 @@ class _DeadlineNamedResourceDisplay(QWidget): # provides (operation_name, BaseException) background_exception = Signal(str, BaseException) - def __init__(self, *, resource_name, setting_name, parent: Optional[QWidget] = None): + def __init__( + self, + *, + resource_name, + setting_name, + config: Optional[ConfigParser] = None, + parent: Optional[QWidget] = None, + ): super().__init__(parent=parent) self.resource_name = resource_name self.setting_name = setting_name - self.item_id = get_setting(self.setting_name) + self._config = config + self.item_id = get_setting(self.setting_name, config=self._config) self.item_name = "" self.item_description = "" @@ -544,7 +574,7 @@ def refresh(self, deadline_authorized): api.check_deadline_available, for example from an AWS Deadline Cloud Status Widget. """ - resource_id = get_setting(self.setting_name) + resource_id = get_setting(self.setting_name, config=self._config) if resource_id != self.item_id or not self.item_name: self.item_id = resource_id self.item_name = "" @@ -577,11 +607,13 @@ def _handle_item_update(self, item: tuple) -> None: class DeadlineFarmDisplay(_DeadlineNamedResourceDisplay): - def __init__(self, *, parent: Optional[QWidget] = None): - super().__init__(resource_name="Farm", setting_name="defaults.farm_id", parent=parent) + def __init__(self, *, config: Optional[ConfigParser] = None, parent: Optional[QWidget] = None): + super().__init__( + resource_name="Farm", setting_name="defaults.farm_id", config=config, parent=parent + ) def get_item(self): - farm_id = get_setting(self.setting_name) + farm_id = get_setting(self.setting_name, config=self._config) if farm_id: deadline = api.get_boto3_client("deadline") response = deadline.get_farm(farmId=farm_id) @@ -591,12 +623,14 @@ def get_item(self): class DeadlineQueueDisplay(_DeadlineNamedResourceDisplay): - def __init__(self, *, parent: Optional[QWidget] = None): - super().__init__(resource_name="Queue", setting_name="defaults.queue_id", parent=parent) + def __init__(self, *, config: Optional[ConfigParser] = None, parent: Optional[QWidget] = None): + super().__init__( + resource_name="Queue", setting_name="defaults.queue_id", config=config, parent=parent + ) def get_item(self): - farm_id = get_setting("defaults.farm_id") - queue_id = get_setting(self.setting_name) + farm_id = get_setting("defaults.farm_id", config=self._config) + queue_id = get_setting(self.setting_name, config=self._config) if farm_id and queue_id: deadline = api.get_boto3_client("deadline") response = deadline.get_queue(farmId=farm_id, queueId=queue_id) @@ -610,17 +644,18 @@ class DeadlineStorageProfileNameDisplay(_DeadlineNamedResourceDisplay): MAC_OS = "Macos" LINUX_OS = "Linux" - def __init__(self, *, parent: Optional[QWidget] = None): + def __init__(self, *, config: Optional[ConfigParser] = None, parent: Optional[QWidget] = None): super().__init__( resource_name="Storage profile name", setting_name="settings.storage_profile_id", + config=config, parent=parent, ) def get_item(self): - farm_id = get_setting("defaults.farm_id") - queue_id = get_setting("defaults.queue_id") - storage_profile_id = get_setting(self.setting_name) + farm_id = get_setting("defaults.farm_id", config=self._config) + queue_id = get_setting("defaults.queue_id", config=self._config) + storage_profile_id = get_setting(self.setting_name, config=self._config) if farm_id and queue_id and storage_profile_id: deadline = api.get_boto3_client("deadline") diff --git a/test/squish/suite_deadline_gui/shared/scripts/workstation_config_helpers.py b/test/squish/suite_deadline_gui/shared/scripts/workstation_config_helpers.py index 324d039e0..a09a60bf5 100644 --- a/test/squish/suite_deadline_gui/shared/scripts/workstation_config_helpers.py +++ b/test/squish/suite_deadline_gui/shared/scripts/workstation_config_helpers.py @@ -78,15 +78,6 @@ def open_settings_dialogue(): ) -def hit_apply_button(): - test.log("Hitting `Apply` button to apply selected settings.") - # hit 'Apply' button - squish.clickButton( - squish.waitForObject(workstation_config_locators.deadlinedialog_apply_button) - ) - test.log("Settings have been applied.") - - def set_farm_name(farm_name: str): # open Default farm drop down menu squish.mouseClick( diff --git a/test/squish/suite_deadline_gui/shared/scripts/workstation_config_locators.py b/test/squish/suite_deadline_gui/shared/scripts/workstation_config_locators.py index fedea6846..eaf72159d 100644 --- a/test/squish/suite_deadline_gui/shared/scripts/workstation_config_locators.py +++ b/test/squish/suite_deadline_gui/shared/scripts/workstation_config_locators.py @@ -15,14 +15,6 @@ "visible": 1, "window": deadline_config_dialog, } -# Apply button -deadlinedialog_apply_button = { - "text": "Apply", - "type": "QPushButton", - "unnamed": 1, - "visible": 1, - "window": deadline_config_dialog, -} # global settings box deadlinedialog_globalsettings_box = { diff --git a/test/unit/deadline_client/cli/test_cli_bundle_submit.py b/test/unit/deadline_client/cli/test_cli_bundle_submit.py index fd3fb0b03..abb0e00a7 100644 --- a/test/unit/deadline_client/cli/test_cli_bundle_submit.py +++ b/test/unit/deadline_client/cli/test_cli_bundle_submit.py @@ -119,7 +119,7 @@ def test_cli_bundle_explicit_parameters(fresh_deadline_config, temp_job_bundle_d ], ) - session_mock.assert_called_with(profile_name="NonDefaultProfileName", botocore_session=ANY) + session_mock.assert_any_call(profile_name="NonDefaultProfileName", botocore_session=ANY) session_mock().client().create_job.assert_called_once_with( farmId=MOCK_FARM_ID, queueId=MOCK_QUEUE_ID, diff --git a/test/unit/deadline_client/cli/test_cli_farm.py b/test/unit/deadline_client/cli/test_cli_farm.py index 682a3d18d..9d783af47 100644 --- a/test/unit/deadline_client/cli/test_cli_farm.py +++ b/test/unit/deadline_client/cli/test_cli_farm.py @@ -71,7 +71,7 @@ def test_cli_farm_list_override_profile(fresh_deadline_config): result = runner.invoke(main, ["farm", "list", "--profile", "NonDefaultProfileName"]) assert result.exit_code == 0 - session_mock.assert_called_with(profile_name="NonDefaultProfileName", botocore_session=ANY) + session_mock.assert_any_call(profile_name="NonDefaultProfileName", botocore_session=ANY) session_mock().client().list_farms.assert_called_once_with() diff --git a/test/unit/deadline_client/cli/test_cli_queue.py b/test/unit/deadline_client/cli/test_cli_queue.py index 31f10bcfb..75d10cf54 100644 --- a/test/unit/deadline_client/cli/test_cli_queue.py +++ b/test/unit/deadline_client/cli/test_cli_queue.py @@ -81,7 +81,7 @@ def test_cli_queue_list_override_profile(fresh_deadline_config): result = runner.invoke(main, ["queue", "list", "--profile", "NonDefaultProfileName"]) assert result.exit_code == 0 - session_mock.assert_called_with(profile_name="NonDefaultProfileName", botocore_session=ANY) + session_mock.assert_any_call(profile_name="NonDefaultProfileName", botocore_session=ANY) session_mock().client().list_queues.assert_called_once_with(farmId="farm-overriddenid") diff --git a/test/unit/deadline_client/config/test_config_file.py b/test/unit/deadline_client/config/test_config_file.py index 5f3f7f14d..591fa53de 100644 --- a/test/unit/deadline_client/config/test_config_file.py +++ b/test/unit/deadline_client/config/test_config_file.py @@ -371,3 +371,72 @@ def test_posix_config_file_permissions(fresh_deadline_config) -> None: config_file.set_setting("defaults.aws_profile_name", "goodguyprofile") assert config_file_path.stat().st_mode & 0o777 == 0o600 + + +def test_persist_job_id_with_default_config(fresh_deadline_config) -> None: + """persist_job_id writes the job ID under the specified farm/queue section.""" + config.set_setting("defaults.farm_id", "farm-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + config.set_setting("defaults.queue_id", "queue-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") + + config_file.persist_job_id( + "job-ccccccccccccccccccccccccccccccc", + profile="(default)", + farm_id="farm-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + queue_id="queue-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + ) + + assert config.get_setting("defaults.job_id") == "job-ccccccccccccccccccccccccccccccc" + + +def test_persist_job_id_with_overridden_farm_queue(fresh_deadline_config) -> None: + """persist_job_id writes the job ID under the overridden farm/queue section + without changing the on-disk farm/queue defaults.""" + # Set up on-disk defaults + config.set_setting("defaults.farm_id", "farm-disk1111111111111111111111111") + config.set_setting("defaults.queue_id", "queue-disk111111111111111111111111") + + # Persist job ID under a different farm/queue + config_file.persist_job_id( + "job-submitted111111111111111111111", + profile="(default)", + farm_id="farm-cli11111111111111111111111111", + queue_id="queue-cli1111111111111111111111111", + ) + + # On-disk farm/queue defaults are unchanged + assert config.get_setting("defaults.farm_id") == "farm-disk1111111111111111111111111" + assert config.get_setting("defaults.queue_id") == "queue-disk111111111111111111111111" + + # Job ID under the default farm/queue section is NOT set + assert config.get_setting("defaults.job_id") == "" + + # Job ID IS set when we read using the overridden farm/queue + from configparser import ConfigParser + + session = ConfigParser() + session.read_dict(config_file.read_config()) + config_file.set_setting( + "defaults.farm_id", "farm-cli11111111111111111111111111", config=session + ) + config_file.set_setting( + "defaults.queue_id", "queue-cli1111111111111111111111111", config=session + ) + assert ( + config_file.get_setting("defaults.job_id", config=session) + == "job-submitted111111111111111111111" + ) + + +def test_persist_job_id_creates_section_if_missing(fresh_deadline_config) -> None: + """persist_job_id creates the section if it doesn't exist yet.""" + config_file.persist_job_id( + "job-newwwwwwwwwwwwwwwwwwwwwwwwwwwwww", + profile="(default)", + farm_id="farm-newwwwwwwwwwwwwwwwwwwwwwwwwwww", + queue_id="queue-newwwwwwwwwwwwwwwwwwwwwwwwwww", + ) + + # Re-read from disk to confirm it was persisted + fresh = config_file.read_config() + section = "profile-(default) farm-newwwwwwwwwwwwwwwwwwwwwwwwwwww queue-newwwwwwwwwwwwwwwwwwwwwwwwwww defaults" + assert fresh.get(section, "job_id") == "job-newwwwwwwwwwwwwwwwwwwwwwwwwwwwww" diff --git a/test/unit/deadline_client/ui/test_session_config.py b/test/unit/deadline_client/ui/test_session_config.py new file mode 100644 index 000000000..c34b032ab --- /dev/null +++ b/test/unit/deadline_client/ui/test_session_config.py @@ -0,0 +1,527 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +""" +Tests for in-memory session config behavior. + +Validates that CLI options (--farm-id, --queue-id, etc.) create a session config +that is used by the submission dialog without affecting the on-disk config, and +that the settings dialog correctly separates session vs workstation config. +""" + +from configparser import ConfigParser +from typing import Callable, Generator, Optional +from unittest.mock import MagicMock, PropertyMock, patch + +import pytest + +try: + from qtpy.QtWidgets import QWidget + + from deadline.client.config import config_file + from deadline.client.cli._common import _apply_cli_options_to_config + from deadline.client.ui.dataclasses import JobBundleSettings + from deadline.client.job_bundle.submission import AssetReferences + from deadline.client.ui.dialogs.submit_job_to_deadline_dialog import ( + SubmitJobToDeadlineDialog, + ) + from deadline.client.ui.dialogs.deadline_config_dialog import ( + DeadlineConfigDialog, + DeadlineWorkstationConfigWidget, + ) +except ImportError: + pytest.importorskip("deadline.client.ui.dialogs.submit_job_to_deadline_dialog") + +DISK_FARM_ID = "farm-disk11111111111111111111111111" +DISK_QUEUE_ID = "queue-disk1111111111111111111111111" +CLI_FARM_ID = "farm-cli111111111111111111111111111" +CLI_QUEUE_ID = "queue-cli11111111111111111111111111" + + +class MockJobSettingsWidget(QWidget): + def __init__( + self, initial_settings: Optional[JobBundleSettings] = None, parent: Optional[QWidget] = None + ): + super().__init__(parent) + self.initial_settings = initial_settings + self.parameter_changed = MagicMock() + self.parameter_changed.connect = MagicMock() + + def update_settings(self, settings: JobBundleSettings) -> None: + pass + + +def _mock_auth_instance() -> MagicMock: + """Create a mock DeadlineAuthenticationStatus that won't trigger real API calls.""" + inst = MagicMock() + inst.api_availability = False + inst.api_availability_changed = MagicMock() + inst.api_availability_changed.connect = MagicMock() + inst.deadline_config_changed = MagicMock() + inst.deadline_config_changed.connect = MagicMock() + return inst + + +@pytest.fixture +def mock_auth_status() -> MagicMock: + mock_instance = MagicMock() + type(mock_instance).api_availability = PropertyMock(return_value=None) + type(mock_instance).creds_source = PropertyMock(return_value=None) + type(mock_instance).auth_status = PropertyMock(return_value=None) + mock_instance.config = ConfigParser() + mock_instance.api_availability_changed = MagicMock() + mock_instance.api_availability_changed.connect = MagicMock() + mock_instance.creds_source_changed = MagicMock() + mock_instance.creds_source_changed.connect = MagicMock() + mock_instance.auth_status_changed = MagicMock() + mock_instance.auth_status_changed.connect = MagicMock() + return mock_instance + + +@pytest.fixture +def disk_config() -> ConfigParser: + """A ConfigParser representing the on-disk config with known farm/queue.""" + config = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=config) + config_file.set_setting("defaults.queue_id", DISK_QUEUE_ID, config=config) + return config + + +@pytest.fixture +def submit_dialog_factory(qtbot, mock_auth_status) -> Callable: + """Factory that creates a SubmitJobToDeadlineDialog with mocked auth.""" + + def _create(session_config: Optional[ConfigParser] = None) -> "SubmitJobToDeadlineDialog": + import deadline.client.ui.deadline_authentication_status as auth_module + + auth_module._deadline_authentication_status = mock_auth_status + + with patch( + "deadline.client.ui.widgets.deadline_authentication_status_widget.DeadlineAuthenticationStatus.getInstance", + return_value=mock_auth_status, + ), patch( + "deadline.client.ui.dialogs.submit_job_to_deadline_dialog.DeadlineAuthenticationStatus.getInstance", + return_value=mock_auth_status, + ): + dialog = SubmitJobToDeadlineDialog( + job_setup_widget_type=MockJobSettingsWidget, + initial_job_settings=JobBundleSettings(), + initial_shared_parameter_values={}, + auto_detected_attachments=AssetReferences(), + attachments=AssetReferences(), + on_create_job_bundle_callback=MagicMock(), + session_config=session_config, + ) + qtbot.addWidget(dialog) + return dialog + + return _create + + +@pytest.fixture +def config_dialog_mocks() -> Generator[MagicMock, None, None]: + """Patches config_file, boto3.Session, and auth for DeadlineConfigDialog tests. + + Yields mock_config_file after setting up sane defaults. + Callers can override mock_config_file.read_config.return_value etc. as needed. + """ + with patch( + "deadline.client.ui.dialogs.deadline_config_dialog.DeadlineAuthenticationStatus.getInstance" + ) as mock_auth, patch( + "deadline.client.ui.dialogs.deadline_config_dialog.boto3.Session" + ) as mock_boto3_session, patch( + "deadline.client.ui.dialogs.deadline_config_dialog.config_file" + ) as mock_config_file: + mock_auth.return_value = _mock_auth_instance() + mock_boto3_session.return_value._session.full_config = {"profiles": {}} + mock_config_file.read_config.return_value = ConfigParser() + mock_config_file.get_setting = config_file.get_setting + mock_config_file.set_setting = config_file.set_setting + mock_config_file.write_config = MagicMock() + yield mock_config_file + + +class TestApplyCliOptionsToConfig: + """Tests for _apply_cli_options_to_config override and copy behavior.""" + + def test_returns_config_when_no_options(self) -> None: + """When no CLI options are provided, returns a copy of the disk config.""" + result = _apply_cli_options_to_config() + assert isinstance(result, ConfigParser) + + def test_overrides_farm_id(self, disk_config) -> None: + session = ConfigParser() + session.read_dict(disk_config) + result = _apply_cli_options_to_config(config=session, farm_id=CLI_FARM_ID) + assert config_file.get_setting("defaults.farm_id", config=result) == CLI_FARM_ID + # queue_id is scoped to the farm, so changing farm clears the queue + assert config_file.get_setting("defaults.queue_id", config=result) == "" + + def test_overrides_queue_id(self, disk_config) -> None: + session = ConfigParser() + session.read_dict(disk_config) + result = _apply_cli_options_to_config(config=session, queue_id=CLI_QUEUE_ID) + assert config_file.get_setting("defaults.queue_id", config=result) == CLI_QUEUE_ID + assert config_file.get_setting("defaults.farm_id", config=result) == DISK_FARM_ID + + def test_overrides_both(self, disk_config) -> None: + session = ConfigParser() + session.read_dict(disk_config) + result = _apply_cli_options_to_config( + config=session, farm_id=CLI_FARM_ID, queue_id=CLI_QUEUE_ID + ) + assert config_file.get_setting("defaults.farm_id", config=result) == CLI_FARM_ID + assert config_file.get_setting("defaults.queue_id", config=result) == CLI_QUEUE_ID + + def test_does_not_mutate_disk_cache(self) -> None: + """When no config is passed, reads from disk and returns a fresh copy.""" + with patch.object(config_file, "read_config") as mock_read: + base = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=base) + mock_read.return_value = base + + result = _apply_cli_options_to_config(farm_id=CLI_FARM_ID) + + assert config_file.get_setting("defaults.farm_id", config=result) == CLI_FARM_ID + assert result is not base + assert config_file.get_setting("defaults.farm_id", config=base) == DISK_FARM_ID + + def test_does_not_mutate_provided_config(self) -> None: + """When a config is explicitly passed, it is copied — the original is not mutated.""" + original = ConfigParser() + config_file.set_setting("defaults.queue_id", DISK_QUEUE_ID, config=original) + + result = _apply_cli_options_to_config(config=original, queue_id=CLI_QUEUE_ID) + + assert result is not original + assert config_file.get_setting("defaults.queue_id", config=result) == CLI_QUEUE_ID + assert config_file.get_setting("defaults.queue_id", config=original) == DISK_QUEUE_ID + + +class TestSubmitDialogSessionConfig: + """Tests for session config propagation through SubmitJobToDeadlineDialog.""" + + def test_no_session_config_when_no_cli_args(self, submit_dialog_factory) -> None: + dialog = submit_dialog_factory(session_config=None) + assert dialog._session_config is None + + def test_session_config_stored_when_provided(self, submit_dialog_factory, disk_config) -> None: + session = _apply_cli_options_to_config(config=disk_config, queue_id=CLI_QUEUE_ID) + dialog = submit_dialog_factory(session_config=session) + + assert dialog._session_config is not None + assert ( + config_file.get_setting("defaults.queue_id", config=dialog._session_config) + == CLI_QUEUE_ID + ) + + def test_session_config_passed_to_shared_job_settings( + self, submit_dialog_factory, disk_config + ) -> None: + session = _apply_cli_options_to_config(config=disk_config, farm_id=CLI_FARM_ID) + dialog = submit_dialog_factory(session_config=session) + + assert dialog.shared_job_settings._config is session + + def test_session_config_passed_to_display_widgets( + self, submit_dialog_factory, disk_config + ) -> None: + """Session config should reach farm_box and queue_box through the widget hierarchy.""" + session = _apply_cli_options_to_config(config=disk_config, farm_id=CLI_FARM_ID) + dialog = submit_dialog_factory(session_config=session) + + settings_box = dialog.shared_job_settings.deadline_cloud_settings_box + assert settings_box._config is session + assert settings_box.farm_box._config is session + assert settings_box.queue_box._config is session + + +@pytest.mark.usefixtures("fresh_deadline_config") +class TestWorkstationConfigWidget: + """Tests for DeadlineWorkstationConfigWidget session vs disk config behavior.""" + + def test_no_session_config_by_default(self, config_dialog_mocks, qtbot) -> None: + widget = DeadlineWorkstationConfigWidget() + qtbot.addWidget(widget) + assert widget._session_config is None + + def test_has_session_config_when_provided(self, config_dialog_mocks, qtbot) -> None: + session = ConfigParser() + config_file.set_setting("defaults.queue_id", CLI_QUEUE_ID, config=session) + + widget = DeadlineWorkstationConfigWidget(session_config=session) + qtbot.addWidget(widget) + assert widget._session_config is not None + + def test_refresh_uses_session_config(self, config_dialog_mocks, qtbot) -> None: + """In session mode, refresh() bases the config on the session config, not disk.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + mock_cf.read_config.return_value = disk + + session = ConfigParser() + config_file.set_setting("defaults.farm_id", CLI_FARM_ID, config=session) + + widget = DeadlineWorkstationConfigWidget(session_config=session) + qtbot.addWidget(widget) + assert config_file.get_setting("defaults.farm_id", config=widget.config) == CLI_FARM_ID + + def test_refresh_uses_disk_config_without_session(self, config_dialog_mocks, qtbot) -> None: + """Without session config, refresh() bases the config on disk.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + mock_cf.read_config.return_value = disk + + widget = DeadlineWorkstationConfigWidget() + qtbot.addWidget(widget) + assert config_file.get_setting("defaults.farm_id", config=widget.config) == DISK_FARM_ID + + def test_apply_with_session_does_not_write_to_disk(self, config_dialog_mocks, qtbot) -> None: + """With session config, apply() mutates in-memory without writing to disk.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + mock_cf.read_config.return_value = disk + + session = ConfigParser() + config_file.set_setting("defaults.farm_id", CLI_FARM_ID, config=session) + + widget = DeadlineWorkstationConfigWidget(session_config=session) + qtbot.addWidget(widget) + + widget.changes["defaults.farm_id"] = "farm-newvalue1111111111111111111111" + assert widget.apply() is True + mock_cf.write_config.assert_not_called() + assert ( + config_file.get_setting("defaults.farm_id", config=session) + == "farm-newvalue1111111111111111111111" + ) + + def test_apply_without_session_writes_to_disk(self, config_dialog_mocks, qtbot) -> None: + """Without session config, apply() writes to disk.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + mock_cf.read_config.return_value = disk + + widget = DeadlineWorkstationConfigWidget() + qtbot.addWidget(widget) + + widget.changes["defaults.farm_id"] = "farm-newvalue1111111111111111111111" + assert widget.apply() is True + mock_cf.write_config.assert_called_once() + + +@pytest.mark.usefixtures("fresh_deadline_config") +class TestConfigDialog: + """Tests for DeadlineConfigDialog button bar and disk I/O actions.""" + + def test_disk_actions_disabled_without_session(self, config_dialog_mocks, qtbot) -> None: + """Without session_config, disk actions start disabled.""" + dialog = DeadlineConfigDialog() + qtbot.addWidget(dialog) + assert not dialog._load_from_disk_action.isEnabled() + + def test_config_file_button_exists_with_session(self, config_dialog_mocks, qtbot) -> None: + """With session_config, Config File button is present.""" + session = ConfigParser() + dialog = DeadlineConfigDialog(session_config=session) + qtbot.addWidget(dialog) + assert dialog._config_file_button is not None + + def test_session_mode_does_not_write_to_disk(self, config_dialog_mocks, qtbot) -> None: + """With session_config, the widget is in session mode and doesn't write to disk.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + mock_cf.read_config.return_value = disk + + session = ConfigParser() + session.read_dict(disk) + + dialog = DeadlineConfigDialog(session_config=session) + qtbot.addWidget(dialog) + + assert dialog.config_box._session_config is not None + mock_cf.write_config.assert_not_called() + + def test_session_config_shows_cli_override(self, config_dialog_mocks, qtbot) -> None: + """CLI --queue-id override is visible in the dialog's config.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + config_file.set_setting("defaults.queue_id", DISK_QUEUE_ID, config=disk) + mock_cf.read_config.return_value = disk + + session = ConfigParser() + session.read_dict(disk) + config_file.set_setting("defaults.queue_id", CLI_QUEUE_ID, config=session) + + dialog = DeadlineConfigDialog(session_config=session) + qtbot.addWidget(dialog) + + assert ( + config_file.get_setting("defaults.queue_id", config=dialog.config_box.config) + == CLI_QUEUE_ID + ) + + def test_save_to_disk_with_session_writes_config(self, config_dialog_mocks, qtbot) -> None: + """Save to Disk applies pending changes to session config and writes to disk.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + mock_cf.read_config.return_value = disk + + session = ConfigParser() + session.read_dict(disk) + dialog = DeadlineConfigDialog(session_config=session) + qtbot.addWidget(dialog) + + dialog.config_box.changes["defaults.farm_id"] = CLI_FARM_ID + dialog._on_save_to_disk() + + mock_cf.write_config.assert_called_once() + assert config_file.get_setting("defaults.farm_id", config=session) == CLI_FARM_ID + assert not dialog.config_box.changes + + def test_save_to_disk_without_session_writes_config(self, config_dialog_mocks, qtbot) -> None: + """Save to Disk reads from disk, applies changes, and writes back.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + mock_cf.read_config.return_value = disk + + dialog = DeadlineConfigDialog() + qtbot.addWidget(dialog) + + dialog.config_box.changes["defaults.farm_id"] = CLI_FARM_ID + dialog._on_save_to_disk() + + mock_cf.write_config.assert_called_once() + assert not dialog.config_box.changes + + def test_save_to_disk_blocks_on_not_valid(self, config_dialog_mocks, qtbot) -> None: + """Save to Disk should not write when changes contain NOT_VALID_MARKER.""" + mock_cf = config_dialog_mocks + + dialog = DeadlineConfigDialog() + qtbot.addWidget(dialog) + + dialog.config_box.changes["defaults.aws_profile_name"] = "[NOT VALID] bad" + with patch("deadline.client.ui.dialogs.deadline_config_dialog.QMessageBox.warning"): + dialog._on_save_to_disk() + + mock_cf.write_config.assert_not_called() + + def test_load_from_disk_with_session_replaces_session(self, config_dialog_mocks, qtbot) -> None: + """Load from Disk replaces the session config with disk contents.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + mock_cf.read_config.return_value = disk + + session = ConfigParser() + session.read_dict(disk) + config_file.set_setting("defaults.farm_id", CLI_FARM_ID, config=session) + + dialog = DeadlineConfigDialog(session_config=session) + qtbot.addWidget(dialog) + + dialog._on_load_from_disk() + + assert ( + config_file.get_setting("defaults.farm_id", config=dialog._session_config) + == DISK_FARM_ID + ) + + def test_load_from_disk_without_session_clears_changes( + self, config_dialog_mocks, qtbot + ) -> None: + """Load from Disk discards pending changes.""" + mock_cf = config_dialog_mocks + disk = ConfigParser() + config_file.set_setting("defaults.farm_id", DISK_FARM_ID, config=disk) + mock_cf.read_config.return_value = disk + + dialog = DeadlineConfigDialog() + qtbot.addWidget(dialog) + + dialog.config_box.changes["defaults.farm_id"] = CLI_FARM_ID + dialog._on_load_from_disk() + + assert not dialog.config_box.changes + + def test_accept_blocks_on_not_valid_with_session(self, config_dialog_mocks, qtbot) -> None: + """Ok should not close the dialog when session-mode changes are not valid.""" + session = ConfigParser() + dialog = DeadlineConfigDialog(session_config=session) + qtbot.addWidget(dialog) + + dialog.config_box.changes["defaults.aws_profile_name"] = "[NOT VALID] bad" + with patch("deadline.client.ui.dialogs.deadline_config_dialog.QMessageBox.warning"): + with patch.object(type(dialog).mro()[1], "accept") as mock_super_accept: + dialog.accept() + mock_super_accept.assert_not_called() + + def test_accept_blocks_on_not_valid_without_session(self, config_dialog_mocks, qtbot) -> None: + """Ok should not close the dialog when workstation-mode changes are not valid.""" + mock_cf = config_dialog_mocks + + dialog = DeadlineConfigDialog() + qtbot.addWidget(dialog) + + dialog.config_box.changes["defaults.aws_profile_name"] = "[NOT VALID] bad" + with patch("deadline.client.ui.dialogs.deadline_config_dialog.QMessageBox.warning"): + with patch.object(type(dialog).mro()[1], "accept") as mock_super_accept: + dialog.accept() + mock_super_accept.assert_not_called() + mock_cf.write_config.assert_not_called() + + +class TestSubmitDialogPersistJobId: + """Tests for job ID persistence after successful GUI submission.""" + + def test_persist_job_id_uses_session_config_values( + self, submit_dialog_factory, disk_config + ) -> None: + """After submission, persist_job_id is called with profile/farm/queue from session config.""" + session = _apply_cli_options_to_config( + config=disk_config, farm_id=CLI_FARM_ID, queue_id=CLI_QUEUE_ID + ) + dialog = submit_dialog_factory(session_config=session) + + with patch( + "deadline.client.ui.dialogs.submit_job_to_deadline_dialog.persist_job_id" + ) as mock_persist: + dialog._submission_succeeded_signal_receiver("job-test1111111111111111111111111") + + mock_persist.assert_called_once_with( + "job-test1111111111111111111111111", + profile=config_file.get_setting("defaults.aws_profile_name", config=session), + farm_id=CLI_FARM_ID, + queue_id=CLI_QUEUE_ID, + ) + + +class TestSetSessionConfigPropagation: + """Tests that set_session_config propagates to child display widgets.""" + + def test_set_session_config_propagates_to_child_widgets( + self, submit_dialog_factory, disk_config + ) -> None: + """set_session_config should update config on farm_box and queue_box.""" + session = _apply_cli_options_to_config(config=disk_config, farm_id=CLI_FARM_ID) + dialog = submit_dialog_factory(session_config=session) + + new_session = ConfigParser() + new_session.read_dict(disk_config) + config_file.set_setting( + "defaults.farm_id", "farm-new111111111111111111111111111", config=new_session + ) + + dialog.shared_job_settings.set_session_config(new_session) + + settings_box = dialog.shared_job_settings.deadline_cloud_settings_box + assert settings_box._config is new_session + assert settings_box.farm_box._config is new_session + assert settings_box.queue_box._config is new_session