Repository: posit-dev/posit-sdk-py

Issue:
Collections should have `__len__`
Relates to #35 and #46

Discussion:

Patch:
diff --git a/src/posit/connect/content.py b/src/posit/connect/content.py
index 5ce5949..40e4a62 100644
--- a/src/posit/connect/content.py
+++ b/src/posit/connect/content.py
@@ -231,3 +231,7 @@ class Content(Resources[ContentItem]):
 
     def delete(self) -> None:
         raise NotImplementedError()
+
+    def count(self) -> int:
+        results = self.session.get(self.url).json()
+        return len(results)
diff --git a/src/posit/connect/resources.py b/src/posit/connect/resources.py
index 5559e31..8a2986e 100644
--- a/src/posit/connect/resources.py
+++ b/src/posit/connect/resources.py
@@ -66,3 +66,7 @@ class Resources(ABC, Generic[T]):
     @abstractmethod
     def update(self, *args, **kwargs) -> T:
         raise NotImplementedError()
+
+    @abstractmethod
+    def count(self, *args, **kwargs) -> int:
+        raise NotImplementedError()
diff --git a/src/posit/connect/users.py b/src/posit/connect/users.py
index d61e903..7d90c04 100644
--- a/src/posit/connect/users.py
+++ b/src/posit/connect/users.py
@@ -2,7 +2,7 @@ from __future__ import annotations
 from typing import Callable, List, Optional
 
 
-from requests import Session
+import requests
 
 
 from . import urls
@@ -95,7 +95,7 @@ class User(Resource):
 
 
 class Users(Resources[User]):
-    def __init__(self, config: Config, session: Session) -> None:
+    def __init__(self, config: Config, session: requests.Session) -> None:
         self.url = urls.append_path(config.url, "v1/users")
         self.config = config
         self.session = session
@@ -148,3 +148,8 @@ class Users(Resources[User]):
 
     def delete(self) -> None:
         raise NotImplementedError()
+
+    def count(self) -> int:
+        response: requests.Response = self.session.get(self.url, json={"page_size": 1})
+        result: dict = response.json()
+        return result["total"]
diff --git a/tinkering.py b/tinkering.py
deleted file mode 100644
index a1e62f5..0000000
--- a/tinkering.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from posit.connect import Client
-
-with Client() as client:
-    client.get("v1/users")
-    client.users.get("f55ca95d-ce52-43ed-b31b-48dc4a07fe13")
-    client.users.find(lambda user: user.first_name.startswith("T"))
-    client.users.find_one(lambda user: user.first_name.startswith("T"))


Test patch:
diff --git a/tests/posit/connect/__api__/v1/users.json b/tests/posit/connect/__api__/v1/users.json
new file mode 100644
index 0000000..7affdba
--- /dev/null
+++ b/tests/posit/connect/__api__/v1/users.json
@@ -0,0 +1,19 @@
+{
+    "results": [
+        {
+            "email": "testuser@example.com",
+            "username": "testuser123",
+            "first_name": "Test",
+            "last_name": "User",
+            "user_role": "tester",
+            "created_time": "2021-01-01T10:00:00Z",
+            "updated_time": "2022-03-02T20:25:06Z",
+            "active_time": "2021-01-01T10:00:00Z",
+            "confirmed": true,
+            "locked": false,
+            "guid": "12345678-abcd-1234-efgh-1234567890ab"
+        }
+    ],
+    "current_page": 1,
+    "total": 1
+}
diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py
index 03e10b8..720644d 100644
--- a/tests/posit/connect/test_content.py
+++ b/tests/posit/connect/test_content.py
@@ -46,3 +46,13 @@ class TestContents:
         con = Client("12345", "https://connect.example")
         get_one = con.content.get("f2f37341-e21d-3d80-c698-a935ad614066")
         assert get_one.name == "Performance-Data-1671216053560"
+
+    @responses.activate
+    def test_count(self):
+        responses.get(
+            "https://connect.example/__api__/v1/content",
+            json=load_mock("v1/content.json"),
+        )
+        con = Client(api_key="12345", url="https://connect.example/")
+        count = con.content.count()
+        assert count == 3
diff --git a/tests/posit/connect/test_resources.py b/tests/posit/connect/test_resources.py
index 380eded..4dee451 100644
--- a/tests/posit/connect/test_resources.py
+++ b/tests/posit/connect/test_resources.py
@@ -85,6 +85,9 @@ class TestResources(Resources[Any]):
     def update(self) -> Any:
         return super().update()  # type: ignore [safe-super]
 
+    def count(self) -> int:
+        return super().count()  # type: ignore [safe-super]
+
     def test_create(self):
         with pytest.raises(NotImplementedError):
             self.create()
@@ -108,3 +111,7 @@ class TestResources(Resources[Any]):
     def test_update(self):
         with pytest.raises(NotImplementedError):
             self.update()
+
+    def test_count(self):
+        with pytest.raises(NotImplementedError):
+            self.count()
diff --git a/tests/posit/connect/test_users.py b/tests/posit/connect/test_users.py
index 9ebfcda..9a6f3cf 100644
--- a/tests/posit/connect/test_users.py
+++ b/tests/posit/connect/test_users.py
@@ -268,3 +268,14 @@ class TestUsers:
             match=r"cannot set attributes: use update\(\) instead",
         ):
             carlos.first_name = "Carlitos"
+
+    @responses.activate
+    def test_count(self):
+        responses.get(
+            "https://connect.example/__api__/v1/users",
+            json=load_mock("v1/users.json"),
+            match=[responses.matchers.json_params_matcher({"page_size": 1})],
+        )
+        con = Client(api_key="12345", url="https://connect.example/")
+        count = con.users.count()
+        assert count == 1