Skip to content

Commit 0614ae4

Browse files
authored
feat: add find_by method to content (#296)
Adds the find_by method to content. The find_by method is a convenience method for filtering a collection of records by attribute values. All filtering happens client-side. This method can be improved to utilize server-side filtering using query parameters.
1 parent c4e761e commit 0614ae4

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

integration/tests/posit/connect/test_content.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ def test_get(self):
2828
def test_find(self):
2929
assert self.client.content.find()
3030

31+
def test_find_by(self):
32+
assert self.client.content.find_by(guid=self.content["guid"]) == self.content
33+
3134
def test_find_one(self):
3235
assert self.client.content.find_one()
3336

src/posit/connect/content.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,131 @@ def find(self, include: Optional[str | list[Any]] = None, **conditions) -> List[
534534
for result in response.json()
535535
]
536536

537+
@overload
538+
def find_by(
539+
self,
540+
*,
541+
# Required
542+
name: str,
543+
# Content Metadata
544+
title: Optional[str] = None,
545+
description: Optional[str] = None,
546+
access_type: Literal["all", "acl", "logged_in"] = "acl",
547+
owner_guid: Optional[str] = None,
548+
# Timeout Settings
549+
connection_timeout: Optional[int] = None,
550+
read_timeout: Optional[int] = None,
551+
init_timeout: Optional[int] = None,
552+
idle_timeout: Optional[int] = None,
553+
# Process and Resource Limits
554+
max_processes: Optional[int] = None,
555+
min_processes: Optional[int] = None,
556+
max_conns_per_process: Optional[int] = None,
557+
load_factor: Optional[float] = None,
558+
cpu_request: Optional[float] = None,
559+
cpu_limit: Optional[float] = None,
560+
memory_request: Optional[int] = None,
561+
memory_limit: Optional[int] = None,
562+
amd_gpu_limit: Optional[int] = None,
563+
nvidia_gpu_limit: Optional[int] = None,
564+
# Execution Settings
565+
run_as: Optional[str] = None,
566+
run_as_current_user: Optional[bool] = False,
567+
default_image_name: Optional[str] = None,
568+
default_r_environment_management: Optional[bool] = None,
569+
default_py_environment_management: Optional[bool] = None,
570+
service_account_name: Optional[str] = None,
571+
) -> Optional[ContentItem]:
572+
"""Find the first content record matching the specified attributes. There is no implied ordering so if order matters, you should find it yourself.
573+
574+
Parameters
575+
----------
576+
name : str, optional
577+
URL-friendly identifier. Allows alphanumeric characters, hyphens ("-"), and underscores ("_").
578+
title : str, optional
579+
Content title. Default is None
580+
description : str, optional
581+
Content description.
582+
access_type : Literal['all', 'acl', 'logged_in'], optional
583+
How content manages viewers.
584+
owner_guid : str, optional
585+
The unique identifier of the user who owns this content item.
586+
connection_timeout : int, optional
587+
Max seconds without data exchange.
588+
read_timeout : int, optional
589+
Max seconds without data received.
590+
init_timeout : int, optional
591+
Max startup time for interactive apps.
592+
idle_timeout : int, optional
593+
Max idle time before process termination.
594+
max_processes : int, optional
595+
Max concurrent processes allowed.
596+
min_processes : int, optional
597+
Min concurrent processes required.
598+
max_conns_per_process : int, optional
599+
Max client connections per process.
600+
load_factor : float, optional
601+
Aggressiveness in spawning new processes (0.0 - 1.0).
602+
cpu_request : float, optional
603+
Min CPU units required (1 unit = 1 core).
604+
cpu_limit : float, optional
605+
Max CPU units allowed.
606+
memory_request : int, optional
607+
Min memory (bytes) required.
608+
memory_limit : int, optional
609+
Max memory (bytes) allowed.
610+
amd_gpu_limit : int, optional
611+
Number of AMD GPUs allocated.
612+
nvidia_gpu_limit : int, optional
613+
Number of NVIDIA GPUs allocated.
614+
run_as : str, optional
615+
UNIX user to execute the content.
616+
run_as_current_user : bool, optional
617+
Run process as the visiting user (for app content). Default is False.
618+
default_image_name : str, optional
619+
Default image for execution if not defined in the bundle.
620+
default_r_environment_management : bool, optional
621+
Manage R environment for the content.
622+
default_py_environment_management : bool, optional
623+
Manage Python environment for the content.
624+
service_account_name : str, optional
625+
Kubernetes service account name for running content.
626+
627+
Returns
628+
-------
629+
Optional[ContentItem]
630+
"""
631+
...
632+
633+
@overload
634+
def find_by(self, **attributes) -> Optional[ContentItem]:
635+
"""Find the first content record matching the specified attributes. There is no implied ordering so if order matters, you should find it yourself.
636+
637+
Returns
638+
-------
639+
Optional[ContentItem]
640+
"""
641+
...
642+
643+
def find_by(self, **attributes) -> Optional[ContentItem]:
644+
"""Find the first content record matching the specified attributes. There is no implied ordering so if order matters, you should find it yourself.
645+
646+
Returns
647+
-------
648+
Optional[ContentItem]
649+
650+
Example
651+
-------
652+
>>> find_by(name="example-content-name")
653+
"""
654+
results = self.find()
655+
results = (
656+
result
657+
for result in results
658+
if all(item in result.items() for item in attributes.items())
659+
)
660+
return next(results, None)
661+
537662
@overload
538663
def find_one(
539664
self,

tests/posit/connect/test_content.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,45 @@ def test_params_include_none(self):
406406
assert mock_get.call_count == 1
407407

408408

409+
class TestContentsFindBy:
410+
@responses.activate
411+
def test(self):
412+
# behavior
413+
mock_get = responses.get(
414+
"https://connect.example/__api__/v1/content",
415+
json=load_mock("v1/content.json"),
416+
)
417+
418+
# setup
419+
client = Client("https://connect.example", "12345")
420+
421+
# invoke
422+
content = client.content.find_by(name="team-admin-dashboard")
423+
424+
# assert
425+
assert mock_get.call_count == 1
426+
assert content
427+
assert content.name == "team-admin-dashboard"
428+
429+
@responses.activate
430+
def test_miss(self):
431+
# behavior
432+
mock_get = responses.get(
433+
"https://connect.example/__api__/v1/content",
434+
json=load_mock("v1/content.json"),
435+
)
436+
437+
# setup
438+
client = Client("https://connect.example", "12345")
439+
440+
# invoke
441+
content = client.content.find_by(name="does-not-exist")
442+
443+
# assert
444+
assert mock_get.call_count == 1
445+
assert content is None
446+
447+
409448
class TestContentsFindOne:
410449
@responses.activate
411450
def test(self):

0 commit comments

Comments
 (0)