Skip to content

Conversation

@nedvedba
Copy link
Collaborator

@nedvedba nedvedba commented Oct 6, 2025

Ticket

#1578

Description

Generic OAuth2/OIDC support for device auth flow, starting Rust API.

How Has This Been Tested?

N/A

Artifacts (if appropriate):

N/A

Tasks

  • - A description of the PR has been provided, and a diagram included if it is a new feature.
  • - Formatter has been run
  • - CHANGELOG comment has been added
  • - Labels have been assigned to the pr
  • - A reviwer has been added
  • - A user has been assigned to work on the pr
  • - If new feature a unit test has been added

Summary by Sourcery

Implement generic OAuth2/OIDC device authorization flow support in the Python client and integrate a new Rust-based core REST API with OIDC discovery, device auth endpoints, OpenAPI/Swagger UI, metrics, and graceful shutdown

New Features:

  • Add startDeviceAuthorization, pollDeviceAuthorization, refreshDeviceAuthorization, loginByToken, and validateAccessToken methods to the Python client library
  • Integrate device authorization flow into the CLI via a --device-auth option
  • Introduce datafed-core-api Rust crate providing OIDC discovery, token endpoints, OpenAPI spec, Swagger UI, and separate metrics server

Enhancements:

  • Extend client configuration with core_api_url, core_api_port, device_auth_scope, and client_refresh_token options
  • Automatically validate and refresh stored access tokens at client startup

Build:

  • Add datafed-core Rust binary to launch the API server with tracing, optional Loki integration, and graceful shutdown support

Deployment:

  • Add PingFederate OIDC provider service to docker-compose for device authorization testing

JoshuaSBrown and others added 30 commits January 13, 2025 15:20
…scanning

1216 daps feature add improved ci scanning
* Add additional params to DatabaseAPI::userSetAccessToken to pass message with addtional info on to database API.

* Add convenience method with old type signature of DatabaseAPI::userSetAccessToken for compatibility

* Conditionally add other token options param when calling dbGetRaw.

* Pass token type along to Database API.

* Pass token type from message.

* Extract logic for building URL from dbPost and dbGetRaw into separate function for easier testing and debugging

* Extract url building logic from dbGet

* Add some comments for moved code, address a TODO, add new TODO for AccessToken Message type field

* Address default on token type, formatting

* Add new AccessTokenType entries for a sentinel value and Globus default in SDMS.proto, use as desired in DatabaseAPI.cpp

* Change query param to match backend changes

* Restore formatting of SDMS.proto

* Make built URL const where appropriate in DatabaseAPI.cpp and DatabaseAPI.hpp

* Change default AccessTokenType for UserSetAccessTokenRequest in SDMS_Auth.proto.

* Remove logging of potentially senitive data

* Adjust AccessTokenType ACCESS_SENTINEL to have value of uint8 max.

---------

Co-authored-by: Anthony Ramirez <[email protected]>
[DLT-1110] Add controller unit tests
[DLT-1110] Remove debug
[DLT-1110] Correct import statements, endpoint list
[DLT-1110] Refactor out into MVC
[DLT-1110] Add debug
[DLT-1110] Functioning modal, needs refactoring
[DLT-1110] Refactor dlg start transfer using OOO programming. Should be MVC cased on what I'm seeing but we'll see
…ollection-endpoint-browse

Revert "[DLT-1110] Mapped Collection Endpoint Browse (1/4)"
JoshuaSBrown and others added 23 commits October 13, 2025 13:13
…ional fields when creating repo to support metadat… (#1714)
…uilding the container image from incorrect sha (#1732)
Co-authored-by: Joshua S Brown <[email protected]>
Co-authored-by: JoshuaSBrown <[email protected]>
…bus credentials for web service (#1731)

Co-authored-by: Joshua S Brown <[email protected]>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@JoshuaSBrown JoshuaSBrown marked this pull request as ready for review November 5, 2025 21:26
@JoshuaSBrown JoshuaSBrown self-requested a review November 5, 2025 21:26
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 5, 2025

Reviewer's Guide

This PR adds end-to-end OAuth2/OIDC device authorization support to the Python DataFed client and CLI, extends Docker Compose for an identity provider, and scaffolds a new Rust-based core API service with OIDC discovery, routing, error handling, and optional metrics.

Sequence diagram for device authorization flow between CLI and core API

sequenceDiagram
    actor User
    participant CLI
    participant "Core API"
    User->>CLI: Start login with --device-auth
    CLI->>"Core API": POST /auth/device (request device code)
    "Core API"-->>CLI: verification_uri, code
    CLI->>User: Display verification URI and code
    User->>VerificationURI: Visit URI and enter code
    loop Polling
        CLI->>"Core API": POST /auth/device/poll (with code)
        alt Not yet authorized
            "Core API"-->>CLI: 400/401/429 (wait and retry)
        else Authorized
            "Core API"-->>CLI: access_token, refresh_token
        end
    end
    CLI->>CLI: Store tokens, log in
    CLI->>User: Show success
Loading

Entity relationship diagram for new and updated config options

erDiagram
    CONFIG {
        string client_token
        string client_refresh_token
        string device_auth_scope
        string core_api_url
        int core_api_port
        bool allow_self_signed_certs
    }
    CONFIG ||--o| CLIENT : has
    CONFIG ||--o| SERVER : has
    CLIENT {
        string client_token
        string client_refresh_token
        string device_auth_scope
        bool allow_self_signed_certs
    }
    SERVER {
        string core_api_url
        int core_api_port
    }
Loading

Class diagram for new Rust OIDC and API state types

classDiagram
    class OIDCConfig {
        +discovery_url: String
        +client_id: String
    }
    class OIDC {
        +device_authorization_endpoint: String
        +token_endpoint: String
        +client_id: String
        +issuer: String
        +jwks_uri: String
        +jwks_cache: Arc<RwLock<Option<JwkSet>>>
        +new(oidc_config: OIDCConfig): OIDC
        +cached_jwks(): Option<JwkSet>
        +store_jwks(jwks: JwkSet)
        +clear_jwks()
    }
    class ApiState {
        +oidc: OIDC
    }
    OIDCConfig <|-- OIDC
    ApiState o-- OIDC
Loading

Class diagram for new Rust AppConfig and related config types

classDiagram
    class AppConfig {
        +rust_log: String
        +api: ApiConfig
        +loki: Option<LokiConfig>
        +metrics: MetricsConfig
        +oidc: OIDCConfig
    }
    class ApiConfig {
        +url: String
        +port: u16
    }
    class LokiConfig {
        +url: String
        +service_name: String
    }
    class MetricsConfig {
        +url: String
        +port: u16
    }
    class OIDCConfig {
        +discovery_url: String
        +client_id: String
    }
    AppConfig o-- ApiConfig
    AppConfig o-- LokiConfig
    AppConfig o-- MetricsConfig
    AppConfig o-- OIDCConfig
Loading

File-Level Changes

Change Details Files
Extend Python DataFed client with OAuth2/OIDC device authorization flow
  • Introduce pending device code state and manual token login
  • Add HTTP helpers (_core_api_request, TLS verification, JSON parsing)
  • Implement startDeviceAuthorization, pollDeviceAuthorization, refreshDeviceAuthorization, validateAccessToken
  • Persist tokens in configuration and auto-login logic
python/datafed_pkg/datafed/CommandLib.py
Add CLI support for device authorization flow
  • Add --device-auth option to global arguments
  • Branch manual-auth into device flow when flagged
  • Implement interactive helper _perform_device_authorization with prompts
python/datafed_pkg/datafed/CLI.py
Configure new device auth and core API parameters
  • Define client_refresh_token, core_api_url, core_api_port, device_auth_scope config keys
python/datafed_pkg/datafed/Config.py
Include PingFederate identity provider in Docker Compose
  • Add pingfederate service with environment variables, ports, and volumes
compose/all/compose.yml
Scaffold Rust core API service with OIDC, routing, and metrics
  • Create crates/core-api module with app_config, errors, middleware, state, router, and metrics server
  • Implement Rust binary entrypoint in src/core.rs
  • Add workspace members and dependencies in Cargo.toml
crates/core-api/src/*
Cargo.toml
src/core.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#1599 Add a Rust backend to the project that does not interfere with other components.
#1599 Implement device authorization flow support in the Rust API.
#1599 Provide a token validation endpoint in the Rust API.
#1653 Create a Docker image for the Rust backend using Cargo-chef to optimize dependency caching and build performance. The PR does not include any Dockerfile, Docker image configuration, or Cargo-chef setup for the Rust backend. The changes are focused on device authentication and Rust API code, not on Dockerization or build optimization.
#1653 Ensure the Docker build process pulls in only necessary files (Cargo.toml & Cargo.lock), caches dependencies, and then builds the binary using the cached dependencies. There is no evidence in the PR of a Docker build process, nor any configuration or scripting that implements the described multi-stage build or dependency caching. The diff does not touch any Dockerfile or related build scripts for the Rust backend.

Possibly linked issues

  • Epic - Generic OAuth2/OIDC Support #1578: The PR adds the Python client-side device authorization flow, including token refresh and validation, and establishes the Rust backend for these services, directly implementing the issue.
  • Epic - Generic OAuth2/OIDC Support #1578: The PR implements the new device authorization flow, including client-side logic, new CLI options, and configuration for OAuth/OIDC, directly fulfilling the issue's request.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • This PR mixes substantial Python client changes with a new Rust core-API service—consider splitting it into separate PRs to keep reviews focused and maintain clear boundaries between components.
  • The new device flow logic in CommandLib has grown large—extracting it into its own module or class would improve readability and keep the core command library more maintainable.
  • There’s repeated HTTP request and error-parsing code (_core_api_request, _format_api_error, _parse_json_response) across the client methods—consolidate these into a shared helper to reduce duplication.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- This PR mixes substantial Python client changes with a new Rust core-API service—consider splitting it into separate PRs to keep reviews focused and maintain clear boundaries between components.
- The new device flow logic in CommandLib has grown large—extracting it into its own module or class would improve readability and keep the core command library more maintainable.
- There’s repeated HTTP request and error-parsing code (_core_api_request, _format_api_error, _parse_json_response) across the client methods—consolidate these into a shared helper to reduce duplication.

## Individual Comments

### Comment 1
<location> `crates/core-api/src/state.rs:20-29` </location>
<code_context>
+    pub async fn new(oidc_config: OIDCConfig) -> Result<Self, reqwest::Error> {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Consider more robust error handling for missing OIDC config fields.

Defaulting to empty strings for missing required fields may cause issues downstream. Instead, return an error or log a warning when required fields are absent.

Suggested implementation:

```rust
        let device_authorization_endpoint = config
            .get("device_authorization_endpoint")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: device_authorization_endpoint",
                )
            })?
            .to_string();

```

```rust
        let token_endpoint = config
            .get("token_endpoint")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: token_endpoint",
                )
            })?
            .to_string();

        let client_id = config
            .get("client_id")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: client_id",
                )
            })?
            .to_string();

        let issuer = config
            .get("issuer")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: issuer",
                )
            })?
            .to_string();

        let jwks_uri = config
            .get("jwks_uri")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: jwks_uri",
                )
            })?
            .to_string();

```

- You may want to define a custom error type instead of using `reqwest::Error` for more precise error handling.
- If you prefer logging a warning before returning the error, you can add a log statement in each `ok_or_else` closure.
- Make sure to update the rest of the constructor to use the new variables (`client_id`, `issuer`, `jwks_uri`) as shown.
</issue_to_address>

### Comment 2
<location> `crates/core-api/src/errors.rs:20-33` </location>
<code_context>
+    SetupError,
+}
+
+impl IntoResponse for ApiError {
+    fn into_response(self) -> axum::response::Response {
+        // Mappings from Rust errors to standard HTTP status codes
+        match self {
+            Self::NotFound => StatusCode::NOT_FOUND,
+            Self::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
+            Self::BadRequest => StatusCode::BAD_REQUEST,
+            Self::Unauthorized => StatusCode::UNAUTHORIZED,
+            Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
+            Self::SetupError => StatusCode::INTERNAL_SERVER_ERROR,
+        }
+        .into_response()
+    }
+}
</code_context>

<issue_to_address>
**suggestion:** Consider including error details in the response body for better client feedback.

Returning only a status code limits client error handling. Adding a JSON error message would make debugging easier for clients.

```suggestion
use axum::{
    response::{IntoResponse, Response},
    http::StatusCode,
    Json,
};
use serde_json::json;

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let status = match self {
            Self::NotFound => StatusCode::NOT_FOUND,
            Self::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
            Self::BadRequest => StatusCode::BAD_REQUEST,
            Self::Unauthorized => StatusCode::UNAUTHORIZED,
            Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
            Self::SetupError => StatusCode::INTERNAL_SERVER_ERROR,
        };

        let body = json!({
            "error": format!("{:?}", self),
            "message": self.to_string(),
        });

        (status, Json(body)).into_response()
    }
}
```
</issue_to_address>

### Comment 3
<location> `crates/core-api/src/middleware.rs:8-12` </location>
<code_context>
+
+// Check for a correlation ID header, and if there is none, create one
+pub fn create_correlation_id<T>(req: &Request<T>) -> Uuid {
+    match req.headers().get("x-correlation-id") {
+        Some(header_value) => {
+            Uuid::from_str(header_value.to_str().unwrap_or_default()).unwrap_or_default()
+        }
+        None => Uuid::new_v4(),
+    }
+}
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Consider using a more robust header parsing for correlation IDs.

Logging a warning or error when an invalid UUID is encountered will help identify issues with incoming requests and prevent silent failures.
</issue_to_address>

### Comment 4
<location> `src/core.rs:8-13` </location>
<code_context>
+
+#[tokio::main]
+async fn main() -> anyhow::Result<()> {
+    let app_config: AppConfig = toml::from_str(
+        tokio::fs::read_to_string("AppSettings.toml")
+            .await
+            .expect("could not read environment file")
</code_context>

<issue_to_address>
**suggestion:** Consider handling missing or malformed config files more gracefully.

The code panics if the config file is missing or invalid. Please add error handling to provide a clear message or fallback behavior in these cases.

```suggestion
    let config_content = match tokio::fs::read_to_string("AppSettings.toml").await {
        Ok(content) => content,
        Err(e) => {
            eprintln!("Error reading AppSettings.toml: {}", e);
            eprintln!("Please ensure the configuration file exists and is readable.");
            std::process::exit(1);
        }
    };

    let app_config: AppConfig = match toml::from_str(config_content.as_str()) {
        Ok(cfg) => cfg,
        Err(e) => {
            eprintln!("Error parsing AppSettings.toml: {}", e);
            eprintln!("Please check the configuration file for syntax errors.");
            std::process::exit(1);
        }
    };
```
</issue_to_address>

### Comment 5
<location> `src/core.rs:30` </location>
<code_context>
+    #[cfg(feature = "loki")]
+    let loki_task_controller = if let Some(ref config) = app_config.loki {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Consider handling errors from tracing_loki::builder more robustly.

If tracing_loki::builder returns an error due to invalid configuration, ensure the error is logged or handled to prevent silent failures.
</issue_to_address>

### Comment 6
<location> `python/datafed_pkg/datafed/CLI.py:3143` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 7
<location> `python/datafed_pkg/datafed/CLI.py:3168` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 8
<location> `python/datafed_pkg/datafed/CLI.py:3174` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 9
<location> `python/datafed_pkg/datafed/CLI.py:3183` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 10
<location> `python/datafed_pkg/datafed/CommandLib.py:191` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 11
<location> `python/datafed_pkg/datafed/CommandLib.py:223-225` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 12
<location> `python/datafed_pkg/datafed/CommandLib.py:235-238` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 13
<location> `python/datafed_pkg/datafed/CommandLib.py:259` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 14
<location> `python/datafed_pkg/datafed/CommandLib.py:267-269` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 15
<location> `python/datafed_pkg/datafed/CommandLib.py:298-300` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 16
<location> `python/datafed_pkg/datafed/CommandLib.py:304-308` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 17
<location> `python/datafed_pkg/datafed/CommandLib.py:315-317` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 18
<location> `python/datafed_pkg/datafed/CommandLib.py:354-356` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 19
<location> `python/datafed_pkg/datafed/CommandLib.py:370-372` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 20
<location> `python/datafed_pkg/datafed/CommandLib.py:401-405` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 21
<location> `python/datafed_pkg/datafed/CommandLib.py:439-441` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 22
<location> `python/datafed_pkg/datafed/CommandLib.py:449-451` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 23
<location> `python/datafed_pkg/datafed/CommandLib.py:453-457` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 24
<location> `python/datafed_pkg/datafed/CommandLib.py:465-467` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 25
<location> `python/datafed_pkg/datafed/CommandLib.py:515-519` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Raise a specific error instead of the general `Exception` or `BaseException` ([`raise-specific-error`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/raise-specific-error))

<details><summary>Explanation</summary>If a piece of code raises a specific exception type
rather than the generic
[`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException)
or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),
the calling code can:

- get more information about what type of error it is
- define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

- Use one of the [built-in exceptions](https://docs.python.org/3/library/exceptions.html) of the standard library.
- [Define your own error class](https://docs.python.org/3/tutorial/errors.html#tut-userexceptions) that subclasses `Exception`.

So instead of having code raising `Exception` or `BaseException` like

```python
if incorrect_input(value):
    raise Exception("The input is incorrect")
```

you can have code raising a specific error like

```python
if incorrect_input(value):
    raise ValueError("The input is incorrect")
```

or

```python
class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
```
</details>
</issue_to_address>

### Comment 26
<location> `python/datafed_pkg/datafed/CommandLib.py:217` </location>
<code_context>
    def _get_core_api_base_url(self):
        """
        Resolve the base URL for the core REST API.
        """
        base_url = self.cfg.get("core_api_url")
        if base_url:
            base_url = base_url.strip()
            if not base_url:
                raise Exception(
                    "Core API base URL is empty. Set 'core_api_url' to a valid HTTP(S) URL."
                )
            if "://" not in base_url:
                base_url = "https://" + base_url
            return base_url.rstrip("/")

        host = self.cfg.get("server_host")
        port = self.cfg.get("core_api_port")
        if host and port:
            return "https://{}:{}".format(host, port)

        raise Exception(
            "Core API base URL is not configured. Set 'core_api_url' or provide both "
            "'server_host' and 'core_api_port'."
        )

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
- Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
- Use f-string instead of string concatenation ([`use-fstring-for-concatenation`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-concatenation/))
</issue_to_address>

### Comment 27
<location> `python/datafed_pkg/datafed/CommandLib.py:243` </location>
<code_context>
    def _format_api_error(self, response):
        body = response.text.strip()
        if len(body) > 200:
            body = body[:200] + "..."
        return body or "empty response body"

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use f-string instead of string concatenation ([`use-fstring-for-concatenation`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-concatenation/))

```suggestion
            body = f"{body[:200]}..."
```
</issue_to_address>

### Comment 28
<location> `python/datafed_pkg/datafed/CommandLib.py:251-259` </location>
<code_context>
    def _core_api_request(self, path, payload, method="POST", timeout=30):
        """
        Issue an HTTP request to the DataFed core REST API.
        """
        base_url = self._get_core_api_base_url()
        url = "{}/{}".format(base_url, path.lstrip("/"))
        verify = self._should_verify_tls()

        try:
            response = requests.request(
                method, url, json=payload, timeout=timeout, verify=verify
            )
        except requests.RequestException as exc:
            raise Exception("Device authorization request to {} failed: {}".format(url, exc))

        return response

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Replace call to format with f-string [×2] ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
- Explicitly raise from a previous error ([`raise-from-previous-error`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/raise-from-previous-error/))
</issue_to_address>

### Comment 29
<location> `python/datafed_pkg/datafed/CommandLib.py:267-269` </location>
<code_context>
    def _parse_json_response(self, response, context):
        try:
            return response.json()
        except ValueError as exc:
            raise Exception(
                "Failed to parse {} response: {}".format(context, exc)
            )

</code_context>

<issue_to_address>
**suggestion (code-quality):** We've found these issues:

- Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
- Explicitly raise from a previous error ([`raise-from-previous-error`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/raise-from-previous-error/))

```suggestion
            raise Exception(f"Failed to parse {context} response: {exc}") from exc
```
</issue_to_address>

### Comment 30
<location> `python/datafed_pkg/datafed/CommandLib.py:272-273` </location>
<code_context>
    def _compute_retry_after(self, response, fallback):
        retry_after = response.headers.get("Retry-After")
        if retry_after:
            try:
                wait_time = int(retry_after)
                if wait_time > 0:
                    return wait_time
            except ValueError:
                pass
        return fallback

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))

```suggestion
        if retry_after := response.headers.get("Retry-After"):
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +20 to +29
pub async fn new(oidc_config: OIDCConfig) -> Result<Self, reqwest::Error> {
let res = get(oidc_config.discovery_url).await?;
let config: Value = res.json().await?;

let device_authorization_endpoint = config
.get("device_authorization_endpoint")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Consider more robust error handling for missing OIDC config fields.

Defaulting to empty strings for missing required fields may cause issues downstream. Instead, return an error or log a warning when required fields are absent.

Suggested implementation:

        let device_authorization_endpoint = config
            .get("device_authorization_endpoint")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: device_authorization_endpoint",
                )
            })?
            .to_string();
        let token_endpoint = config
            .get("token_endpoint")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: token_endpoint",
                )
            })?
            .to_string();

        let client_id = config
            .get("client_id")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: client_id",
                )
            })?
            .to_string();

        let issuer = config
            .get("issuer")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: issuer",
                )
            })?
            .to_string();

        let jwks_uri = config
            .get("jwks_uri")
            .and_then(Value::as_str)
            .ok_or_else(|| {
                reqwest::Error::new(
                    reqwest::StatusCode::BAD_REQUEST,
                    "Missing required field: jwks_uri",
                )
            })?
            .to_string();
  • You may want to define a custom error type instead of using reqwest::Error for more precise error handling.
  • If you prefer logging a warning before returning the error, you can add a log statement in each ok_or_else closure.
  • Make sure to update the rest of the constructor to use the new variables (client_id, issuer, jwks_uri) as shown.

Comment on lines +20 to +33
impl IntoResponse for ApiError {
fn into_response(self) -> axum::response::Response {
// Mappings from Rust errors to standard HTTP status codes
match self {
Self::NotFound => StatusCode::NOT_FOUND,
Self::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
Self::BadRequest => StatusCode::BAD_REQUEST,
Self::Unauthorized => StatusCode::UNAUTHORIZED,
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
Self::SetupError => StatusCode::INTERNAL_SERVER_ERROR,
}
.into_response()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider including error details in the response body for better client feedback.

Returning only a status code limits client error handling. Adding a JSON error message would make debugging easier for clients.

Suggested change
impl IntoResponse for ApiError {
fn into_response(self) -> axum::response::Response {
// Mappings from Rust errors to standard HTTP status codes
match self {
Self::NotFound => StatusCode::NOT_FOUND,
Self::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
Self::BadRequest => StatusCode::BAD_REQUEST,
Self::Unauthorized => StatusCode::UNAUTHORIZED,
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
Self::SetupError => StatusCode::INTERNAL_SERVER_ERROR,
}
.into_response()
}
}
use axum::{
response::{IntoResponse, Response},
http::StatusCode,
Json,
};
use serde_json::json;
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let status = match self {
Self::NotFound => StatusCode::NOT_FOUND,
Self::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
Self::BadRequest => StatusCode::BAD_REQUEST,
Self::Unauthorized => StatusCode::UNAUTHORIZED,
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
Self::SetupError => StatusCode::INTERNAL_SERVER_ERROR,
};
let body = json!({
"error": format!("{:?}", self),
"message": self.to_string(),
});
(status, Json(body)).into_response()
}
}

Comment on lines +8 to +12
match req.headers().get("x-correlation-id") {
Some(header_value) => {
Uuid::from_str(header_value.to_str().unwrap_or_default()).unwrap_or_default()
}
None => Uuid::new_v4(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Consider using a more robust header parsing for correlation IDs.

Logging a warning or error when an invalid UUID is encountered will help identify issues with incoming requests and prevent silent failures.

Comment on lines +8 to +13
let app_config: AppConfig = toml::from_str(
tokio::fs::read_to_string("AppSettings.toml")
.await
.expect("could not read environment file")
.as_str(),
)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider handling missing or malformed config files more gracefully.

The code panics if the config file is missing or invalid. Please add error handling to provide a clear message or fallback behavior in these cases.

Suggested change
let app_config: AppConfig = toml::from_str(
tokio::fs::read_to_string("AppSettings.toml")
.await
.expect("could not read environment file")
.as_str(),
)?;
let config_content = match tokio::fs::read_to_string("AppSettings.toml").await {
Ok(content) => content,
Err(e) => {
eprintln!("Error reading AppSettings.toml: {}", e);
eprintln!("Please ensure the configuration file exists and is readable.");
std::process::exit(1);
}
};
let app_config: AppConfig = match toml::from_str(config_content.as_str()) {
Ok(cfg) => cfg,
Err(e) => {
eprintln!("Error parsing AppSettings.toml: {}", e);
eprintln!("Please check the configuration file for syntax errors.");
std::process::exit(1);
}
};


tokio::spawn(task);

tracing::subscriber::set_global_default(subscriber.with(loki_layer))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Consider handling errors from tracing_loki::builder more robustly.

If tracing_loki::builder returns an error due to invalid configuration, ensure the error is logged or handled to prevent silent failures.

Comment on lines +515 to +519
raise Exception(
"Token validation failed (HTTP {}): {}".format(
response.status_code, self._format_api_error(response)
)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Raise a specific error instead of the general Exception or BaseException (raise-specific-error)

ExplanationIf a piece of code raises a specific exception type rather than the generic [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception), the calling code can:
  • get more information about what type of error it is
  • define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

So instead of having code raising Exception or BaseException like

if incorrect_input(value):
    raise Exception("The input is incorrect")

you can have code raising a specific error like

if incorrect_input(value):
    raise ValueError("The input is incorrect")

or

class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")

def _format_api_error(self, response):
body = response.text.strip()
if len(body) > 200:
body = body[:200] + "..."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use f-string instead of string concatenation (use-fstring-for-concatenation)

Suggested change
body = body[:200] + "..."
body = f"{body[:200]}..."

Comment on lines +251 to +259
url = "{}/{}".format(base_url, path.lstrip("/"))
verify = self._should_verify_tls()

try:
response = requests.request(
method, url, json=payload, timeout=timeout, verify=verify
)
except requests.RequestException as exc:
raise Exception("Device authorization request to {} failed: {}".format(url, exc))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): We've found these issues:

Comment on lines +267 to +269
raise Exception(
"Failed to parse {} response: {}".format(context, exc)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): We've found these issues:

Suggested change
raise Exception(
"Failed to parse {} response: {}".format(context, exc)
)
raise Exception(f"Failed to parse {context} response: {exc}") from exc

Comment on lines +272 to +273
retry_after = response.headers.get("Retry-After")
if retry_after:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use named expression to simplify assignment and conditional (use-named-expression)

Suggested change
retry_after = response.headers.get("Retry-After")
if retry_after:
if retry_after := response.headers.get("Retry-After"):

@JoshuaSBrown JoshuaSBrown changed the base branch from devel to master November 20, 2025 19:07
@JoshuaSBrown JoshuaSBrown changed the base branch from master to devel November 20, 2025 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] - Dockerized version of Rust Server [Core, Auth] Implement First Version of Rust Backend for Device Authorization Flow and Token Validation

8 participants