Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6de9a35
Add `SESSION_TIMEOUT` and `MAX_SESSION` env vars
jlegrand62 Feb 3, 2026
bb1c8aa
Use os.getenv for session timeout and max sessions
jlegrand62 Feb 3, 2026
7ab6b2f
Add token validation endpoint and request helper
jlegrand62 Feb 3, 2026
45b7737
Add `token_validation()` method to `plantdb_client`
jlegrand62 Feb 3, 2026
5c52c03
Update docstrings to use “JSON Web Token”
jlegrand62 Feb 3, 2026
511dece
Add new API endpoint helpers and type annotations
jlegrand62 Feb 3, 2026
6db618d
Update scans_info example and clarify fuzzy default
jlegrand62 Feb 3, 2026
bc0d89d
Expand REST API module docstring and clean up imports
jlegrand62 Feb 3, 2026
f20e5a1
Update example code in PlantDB client docstring
jlegrand62 Feb 3, 2026
869ad30
Add Argon2‑derived secret key support to JWT session manager
jlegrand62 Feb 4, 2026
bca61c3
Add comprehensive JWT session management with refresh tokens, UTC han…
jlegrand62 Feb 4, 2026
d05f850
Add comprehensive unit tests for session and JWT session managers
jlegrand62 Feb 4, 2026
d119cb2
Remove refactored SessionManager tests from `test_auth.py`
jlegrand62 Feb 4, 2026
eaf46a1
Add session validation and refresh token support to REST API
jlegrand62 Feb 4, 2026
928e976
Clean up REST API server tests
jlegrand62 Feb 4, 2026
a327d83
Remove stray debug print from upload handling
jlegrand62 Feb 4, 2026
b6a57c3
Add Argon2‑derived secret key support and refactor secret key handlin…
jlegrand62 Feb 4, 2026
0da94ae
Update secret key defaults and add refresh token support in REST API
jlegrand62 Feb 4, 2026
6fdd620
Increase secret key entropy and add session configuration options to …
jlegrand62 Feb 4, 2026
8004b2d
Reformat REST API CLI signatures and docstrings
jlegrand62 Feb 4, 2026
0a56219
Add token refresh support and refactor authentication in `plantdb_cli…
jlegrand62 Feb 4, 2026
d382789
Enhance URL handling and add token‑refresh support in REST client
jlegrand62 Feb 4, 2026
6f3fb52
Reformat docstrings and function signatures for consistency
jlegrand62 Feb 4, 2026
06384ef
Reformat `fsdb_rest_api.py` signatures, docstrings, and spacing
jlegrand62 Feb 4, 2026
5541711
Rename `base64` query parameter to `as_base64` and update handling
jlegrand62 Feb 4, 2026
07fe244
Add optional query parameters to image and sequence endpoints
jlegrand62 Feb 4, 2026
bbefa47
Add optional `as_base64` flag to `scan_image_url`
jlegrand62 Feb 4, 2026
c1f420d
Enhance logout responses and include username
jlegrand62 Feb 4, 2026
86ca22f
Enhance `logout` to return success flag and username
jlegrand62 Feb 4, 2026
d37c0ef
Add optional base64 handling and tighten return types in REST client
jlegrand62 Feb 4, 2026
dddf33e
Update request_refresh usage to handle success flag
jlegrand62 Feb 4, 2026
084d87a
Update client integration tests for authentication and server port
jlegrand62 Feb 4, 2026
925ba2a
Restrict Python version to <3.13 and add open3d dependency
jlegrand62 Feb 4, 2026
830452f
Update `request_logout` example to match new return signature
jlegrand62 Feb 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 158 additions & 10 deletions src/client/plantdb/client/api_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
>>> api_endpoints.scan('plant1', prefix='/api/v1')
'/api/v1/scans/plant1'
"""
from urllib import parse


def sanitize_name(name):
def sanitize_name(name) -> str:
"""Sanitizes and validates the provided name.

The function ensures that the input string adheres to predefined naming rules by:
Expand Down Expand Up @@ -58,9 +59,8 @@ def sanitize_name(name):
return sanitized_name


def url_prefix(endpoint_path):
"""
Wrap an endpoint path generator with an optional URL prefix.
def url_prefix(endpoint_path) :
"""Wrap an endpoint path generator with an optional URL prefix.

Examples
--------
Expand Down Expand Up @@ -282,6 +282,29 @@ def scans(**kwargs) -> str:
return "/scans"


@url_prefix
def scans_info(**kwargs) -> str:
"""Return the URL path to the list of scan dataset information endpoint.

Other Parameters
----------------
prefix : str
An optional prefix to prepend to the URL path.

Returns
-------
str
The URL path to the list of scan dataset information endpoint.

Examples
--------
>>> from plantdb.client import api_endpoints
>>> api_endpoints.scans_info(prefix='/api/v1')
'/api/v1/scans_info'
"""
return "/scans_info"


@url_prefix
def scan(scan_id: str, **kwargs) -> str:
"""Return the URL path to the scan endpoint.
Expand Down Expand Up @@ -313,7 +336,7 @@ def scan(scan_id: str, **kwargs) -> str:


@url_prefix
def image(scan_id: str, fileset_id: str, file_id: str, size: str, **kwargs) -> str:
def image(scan_id: str, fileset_id: str, file_id: str, size: str, as_base64:bool, **kwargs) -> str:
"""Return the URL path to the image endpoint.

Parameters
Expand All @@ -326,6 +349,8 @@ def image(scan_id: str, fileset_id: str, file_id: str, size: str, **kwargs) -> s
The name of the image.
size : str or int
The size parameter of the image request.
as_base64 : bool
A boolean flag indicating whether to return an image as a base64 string.

Returns
-------
Expand All @@ -335,13 +360,25 @@ def image(scan_id: str, fileset_id: str, file_id: str, size: str, **kwargs) -> s
Examples
--------
>>> from plantdb.client import api_endpoints
>>> api_endpoints.image('real_plant','images','00000_rgb', 'orig')
>>> api_endpoints.image('real_plant','images','00000_rgb', 'orig', False)
'/image/real_plant/images/00000_rgb?size=orig'
>>> api_endpoints.image('real_plant','images','00000_rgb', 'thumb', True)
'/image/real_plant/images/00000_rgb?size=thumb&as_base64=true'
"""
scan_id = sanitize_name(scan_id)
fileset_id = sanitize_name(fileset_id)
file_id = sanitize_name(file_id)
return f"/image/{scan_id}/{fileset_id}/{file_id}?size={size}"

# Assemble optional query parameters
query: dict[str, str] = {}
if size is not None:
query["size"] = str(size)
if as_base64 is not None:
# Use lower‑case JSON‑style booleans for consistency
query["as_base64"] = str(as_base64).lower()

query_str = f"?{parse.urlencode(query)}" if query else ""
return f"/image/{scan_id}/{fileset_id}/{file_id}{query_str}"


@url_prefix
Expand Down Expand Up @@ -370,7 +407,14 @@ def sequence(scan_id: str, type: str, **kwargs) -> str:
valid_types = ['all', 'angles', 'internodes', 'fruit_points', 'manual_angles', 'manual_internodes']
type = 'all' if type not in valid_types else type
scan_id = sanitize_name(scan_id)
return f"/sequence/{scan_id}?type={type}"

# Assemble optional query parameters
query: dict[str, str] = {}
if type is not None:
query["type"] = str(type)

query_str = f"?{parse.urlencode(query)}" if query else ""
return f"/sequence/{scan_id}{query_str}"


@url_prefix
Expand Down Expand Up @@ -453,9 +497,8 @@ def scan_file(scan_id: str, file_path: str, **kwargs) -> str:
return f"/files/{scan_id}/{file_path.lstrip('/')}"



@url_prefix
def create_user(**kwargs):
def create_user(**kwargs) -> str:
"""Create the user registration URL.

Returns
Expand All @@ -470,3 +513,108 @@ def create_user(**kwargs):
'/register'
"""
return f"/register"


@url_prefix
def create_scan(**kwargs) -> str:
"""URL to create a scan.

Returns
-------
str
The URL path to scan creation.
"""
return f"/api/scan"


@url_prefix
def create_fileset(**kwargs) -> str:
"""URL to create a fileset.

Returns
-------
str
The URL path to fileset creation.
"""
return f"/api/fileset"


@url_prefix
def create_file(**kwargs) -> str:
"""URL to create a file.

Returns
-------
str
The URL path to file creation.
"""
return f"/api/file"


@url_prefix
def list_scan_filesets(scan: str, **kwargs) -> str:
"""URL to list the filesets associated with the given scan name.

Returns
-------
str
The URL path to filesets.
"""
scan_id = sanitize_name(scan)
return f"/api/scan/{scan_id}/filesets"


@url_prefix
def list_fileset_files(scan: str, fileset: str, **kwargs) -> str:
"""URL to list the file associated with the given scan and filesets names.

Returns
-------
str
The URL path to filesets.
"""
scan_id = sanitize_name(scan)
fileset = sanitize_name(fileset)
return f"/api/scan/{scan_id}/{fileset}/files"


@url_prefix
def metadata_scan(scan: str, **kwargs) -> str:
"""URL to access the metadata associated with the given scan name.

Returns
-------
str
The URL path to scan metadata.
"""
scan_id = sanitize_name(scan)
return f"/api/scan/{scan_id}/metadata"


@url_prefix
def metadata_fileset(scan: str, fileset: str, **kwargs) -> str:
"""URL to access the fileset metadata associated with the given scan and fileset name.

Returns
-------
str
The URL path to fileset metadata.
"""
scan_id = sanitize_name(scan)
fileset = sanitize_name(fileset)
return f"/api/scan/{scan_id}/{fileset}/metadata"


@url_prefix
def metadata_files(scan: str, fileset: str, file: str, **kwargs) -> str:
"""URL to access the file metadata associated with the given scan and fileset name.

Returns
-------
str
The URL path to file metadata.
"""
scan_id = sanitize_name(scan)
fileset = sanitize_name(fileset)
file = sanitize_name(file)
return f"/api/scan/{scan_id}/{fileset}/{file}/metadata"
2 changes: 1 addition & 1 deletion src/client/plantdb/client/cli/fsdb_rest_api_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def sync_scan_archives(origin_url, target_url, filter_pattern=None, log_level=DE
Path(f_path).unlink()
# Refresh the scan in the target to load its infos:
try:
msg = request_refresh(scan_id, host=target_host, port=target_port)
success, msg = request_refresh(scan_id, host=target_host, port=target_port)
except HTTPError as e:
logger.error(f"Error refreshing target database for scan '{scan_id}': {e}")
continue
Expand Down
Loading
Loading