Skip to content

Commit dffa1c8

Browse files
feat: organization session policy SDK methods (#152)
* feat: add organization session policy SDK methods and generate local proto target Adds get_organization_session_policy and update_organization_session_policy wrapper methods, regenerates gRPC stubs from local protos, and adds generate-local Makefile target. * chore: bump proto ref to v0.1.120.2 * feat: regenerate protos for v0.1.120.2 and add session policy tests Updates generated stubs to match new flat UpdateOrganizationSessionPolicyRequest (SessionPolicyType enum rename, flat fields). Adds agentkit_logs generated module and removes stale buf/validate generated files. Adds integration test coverage for get/update/revert organization session policy. * fix: add --include-imports to generate-local and restore buf/validate stubs The generate-local target was missing --include-imports, causing buf/validate generated files to be absent while generated stubs still imported them. * chore: revert unrelated proto changes, remove generate-local, bump PROTO_REF * chore: ignore proto/ directory (local generate-local output) * chore: regenerate all proto stubs for v0.1.120.2 * chore: regenerate proto stubs from local scalekit proto and add generate-local target Resets pb2 files to main baseline then regenerates using local ../scalekit proto via `buf generate ../scalekit --include-imports` to avoid GitHub clone and resolve buf/validate deps correctly. Adds generate-local Makefile target for future use. * build * fix: remove blocking input() call from test_actions setUp * Revert "fix: remove blocking input() call from test_actions setUp" This reverts commit 8c84f5b. * build * chore: regenerate proto stubs from local scalekit proto * chore: regenerate proto stubs from published v0.1.121.2 * fix: address coderabbitai review comments - generate-local: rename trap handler to rollback_and_cleanup, add cleanup of TEMP_DIR and intermediate dirs on failure, and clear trap on success - test_revert_to_application_policy: add follow-up get to verify persisted state * build * fix: return raw grpc response from session policy methods and bump version to 2.10.0 - Remove response[0].policy extraction from get/update_organization_session_policy to match existing SDK patterns - Bump SDK version 2.9.0 → 2.10.0 - Update api_version date to 20260513 * fix: update session policy tests to unpack raw grpc response tuple * fix: correct return types for session policy methods to use response types
1 parent d0535c4 commit dffa1c8

6 files changed

Lines changed: 210 additions & 4 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ proto
170170

171171
# Claude Code documentation
172172
CLAUDE.md
173+
proto/
173174
.claude/*
174175

175176
# macOS
176-
.DS_Store
177+
.DS_Store

Makefile

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ VENV_PIP := $(VENV_PYTHON) -m pip
1414
PROTO_REPO_URL := https://github.com/scalekit-inc/scalekit.git
1515
PROTO_REF ?= v0.1.121.2
1616
PROTO_SUBDIR := proto
17+
LOCAL_PROTO_REPO ?= ../scalekit
1718

1819
TEMP_DIR := temp_scalekit
1920
SCALEKIT_DIR := scalekit
@@ -22,7 +23,7 @@ GOOGLE_DIR := google
2223
PROTO_DIR := proto
2324
PROTOC_DIR := protoc_gen_openapiv2
2425

25-
.PHONY: setup generate lint test tools-check create-venv prepare buf_generate restore generate_init_files cleanup copy_proto_dir
26+
.PHONY: setup generate generate-local lint test tools-check create-venv prepare buf_generate restore generate_init_files cleanup copy_proto_dir
2627

2728
setup: create-venv
2829
@echo "Installing SDK dependencies in $(VENV_DIR)..."
@@ -109,6 +110,28 @@ cleanup:
109110
rm -f .dirpath buf.yaml buf.lock
110111
@if [ -f buf.work.yaml.bak ]; then mv buf.work.yaml.bak buf.work.yaml; fi
111112

113+
generate-local: tools-check
114+
@echo "Using local proto sources from $(LOCAL_PROTO_REPO)..."
115+
@set -euo pipefail; \
116+
prepared=0; \
117+
rollback_and_cleanup() { \
118+
if [ "$$prepared" -eq 1 ] && [ -d "$(TEMP_DIR)" ]; then \
119+
echo "Generation failed; restoring $(SCALEKIT_DIR) from $(TEMP_DIR)..."; \
120+
rsync -a "$(TEMP_DIR)/" "$(SCALEKIT_DIR)/"; \
121+
fi; \
122+
rm -rf "$(TEMP_DIR)" "$(GOOGLE_DIR)" "$(PROTO_DIR)" "$(PROTOC_DIR)"; \
123+
rm -f .dirpath buf.yaml buf.lock; \
124+
if [ -f buf.work.yaml.bak ]; then mv buf.work.yaml.bak buf.work.yaml; fi; \
125+
}; \
126+
trap 'rollback_and_cleanup' EXIT; \
127+
$(MAKE) prepare; prepared=1; \
128+
buf generate $(LOCAL_PROTO_REPO) --include-imports; \
129+
$(MAKE) restore; prepared=0; \
130+
$(MAKE) generate_init_files; \
131+
$(MAKE) cleanup; \
132+
trap - EXIT
133+
@echo "Code generation complete."
134+
112135
lint: create-venv
113136
@echo "Running static checks..."
114137
$(VENV_PYTHON) -m compileall -q scalekit tests

scalekit/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Single source of truth for the SDK version.
22
# Import this in setup.py and scalekit/core.py — never hardcode the version elsewhere.
3-
__version__ = "2.9.0"
3+
__version__ = "2.10.0"

scalekit/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class CoreClient:
2929

3030
sdk_version = f"Scalekit-Python/{_sdk_version}"
3131
# YYYYMMDD
32-
api_version = "20260428"
32+
api_version = "20260513"
3333
user_agent = f"{sdk_version} Python/{platform.python_version()} ({platform.system()}; {platform.architecture()}"
3434

3535
def __init__(self, env_url, client_id, client_secret):

scalekit/organization.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@
2020
UpdateOrganizationSettingsRequest,
2121
OrganizationUserManagementSettings,
2222
UpsertUserManagementSettingsRequest,
23+
GetOrganizationSessionPolicyRequest,
24+
GetOrganizationSessionPolicyResponse,
25+
UpdateOrganizationSessionPolicyRequest,
26+
UpdateOrganizationSessionPolicyResponse,
27+
SessionPolicyType,
2328
)
29+
from scalekit.v1.commons.commons_pb2 import TimeUnit
2430
from scalekit.v1.organizations.organizations_pb2_grpc import OrganizationServiceStub
2531

2632

@@ -219,3 +225,72 @@ def upsert_user_management_settings(self, organization_id: str, max_allowed_user
219225
)
220226
)
221227
return response[0].settings
228+
229+
def get_organization_session_policy(self, organization_id: str) -> GetOrganizationSessionPolicyResponse:
230+
"""
231+
Get the session policy for an organization.
232+
233+
:param organization_id: Organization id
234+
:type organization_id : ``` str ```
235+
:returns:
236+
GetOrganizationSessionPolicyResponse
237+
"""
238+
return self.core_client.grpc_exec(
239+
self.organization_service.GetOrganizationSessionPolicy.with_call,
240+
GetOrganizationSessionPolicyRequest(organization_id=organization_id),
241+
)
242+
243+
def update_organization_session_policy(
244+
self,
245+
organization_id: str,
246+
policy_source: SessionPolicyType,
247+
absolute_session_timeout: Optional[int] = None,
248+
absolute_session_timeout_unit: Optional[TimeUnit] = None,
249+
idle_session_timeout_enabled: Optional[bool] = None,
250+
idle_session_timeout: Optional[int] = None,
251+
idle_session_timeout_unit: Optional[TimeUnit] = None,
252+
) -> UpdateOrganizationSessionPolicyResponse:
253+
"""
254+
Set a custom session policy for an organization or revert to application defaults.
255+
256+
:param organization_id: Organization id
257+
:type organization_id: ``` str ```
258+
:param policy_source: SessionPolicyType.APPLICATION or SessionPolicyType.CUSTOM
259+
:type policy_source: ``` SessionPolicyType ```
260+
:param absolute_session_timeout: Absolute session timeout value (optional)
261+
:type absolute_session_timeout: ``` int | None ```
262+
:param absolute_session_timeout_unit: Unit for absolute timeout (optional)
263+
:type absolute_session_timeout_unit: ``` TimeUnit | None ```
264+
:param idle_session_timeout_enabled: Whether idle session timeout is enabled (optional)
265+
:type idle_session_timeout_enabled: ``` bool | None ```
266+
:param idle_session_timeout: Idle session timeout value (optional)
267+
:type idle_session_timeout: ``` int | None ```
268+
:param idle_session_timeout_unit: Unit for idle timeout (optional)
269+
:type idle_session_timeout_unit: ``` TimeUnit | None ```
270+
:returns:
271+
UpdateOrganizationSessionPolicyResponse
272+
"""
273+
req = UpdateOrganizationSessionPolicyRequest(
274+
organization_id=organization_id,
275+
policy_source=policy_source,
276+
)
277+
if absolute_session_timeout is not None:
278+
req.absolute_session_timeout.CopyFrom(
279+
wrappers_pb2.Int32Value(value=absolute_session_timeout)
280+
)
281+
if absolute_session_timeout_unit is not None:
282+
req.absolute_session_timeout_unit = absolute_session_timeout_unit
283+
if idle_session_timeout_enabled is not None:
284+
req.idle_session_timeout_enabled.CopyFrom(
285+
wrappers_pb2.BoolValue(value=idle_session_timeout_enabled)
286+
)
287+
if idle_session_timeout is not None:
288+
req.idle_session_timeout.CopyFrom(
289+
wrappers_pb2.Int32Value(value=idle_session_timeout)
290+
)
291+
if idle_session_timeout_unit is not None:
292+
req.idle_session_timeout_unit = idle_session_timeout_unit
293+
return self.core_client.grpc_exec(
294+
self.organization_service.UpdateOrganizationSessionPolicy.with_call,
295+
req,
296+
)
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from faker import Faker
2+
3+
from basetest import BaseTest
4+
from scalekit.v1.organizations.organizations_pb2 import (
5+
CreateOrganization,
6+
SessionPolicyType,
7+
)
8+
from scalekit.v1.commons.commons_pb2 import TimeUnit
9+
10+
11+
class TestOrganizationSessionPolicy(BaseTest):
12+
"""Test cases for organization session policy management."""
13+
14+
def setUp(self):
15+
self.org_id = None
16+
17+
def _create_org(self):
18+
organization = CreateOrganization(display_name=Faker().company(), external_id=Faker().uuid4())
19+
response = self.scalekit_client.organization.create_organization(organization=organization)
20+
self.org_id = response[0].organization.id
21+
return self.org_id
22+
23+
def test_get_default_policy(self):
24+
"""New org should inherit APPLICATION policy by default."""
25+
org_id = self._create_org()
26+
27+
policy = self.scalekit_client.organization.get_organization_session_policy(
28+
organization_id=org_id
29+
)[0].policy
30+
31+
self.assertIsNotNone(policy)
32+
self.assertEqual(policy.policy_source, SessionPolicyType.APPLICATION)
33+
34+
def test_set_custom_policy(self):
35+
"""Setting a custom policy should persist and be retrievable."""
36+
org_id = self._create_org()
37+
38+
policy = self.scalekit_client.organization.update_organization_session_policy(
39+
organization_id=org_id,
40+
policy_source=SessionPolicyType.CUSTOM,
41+
absolute_session_timeout=360,
42+
absolute_session_timeout_unit=TimeUnit.MINUTES,
43+
idle_session_timeout_enabled=True,
44+
idle_session_timeout=60,
45+
idle_session_timeout_unit=TimeUnit.MINUTES,
46+
)[0].policy
47+
48+
self.assertIsNotNone(policy)
49+
self.assertEqual(policy.policy_source, SessionPolicyType.CUSTOM)
50+
51+
fetched = self.scalekit_client.organization.get_organization_session_policy(
52+
organization_id=org_id
53+
)[0].policy
54+
self.assertEqual(fetched.policy_source, SessionPolicyType.CUSTOM)
55+
self.assertTrue(fetched.HasField("absolute_session_timeout"))
56+
self.assertEqual(fetched.absolute_session_timeout.value, 360)
57+
self.assertTrue(fetched.HasField("idle_session_timeout_enabled"))
58+
self.assertTrue(fetched.idle_session_timeout_enabled.value)
59+
60+
def test_revert_to_application_policy(self):
61+
"""Setting policy source to APPLICATION should revert to application defaults."""
62+
org_id = self._create_org()
63+
64+
self.scalekit_client.organization.update_organization_session_policy(
65+
organization_id=org_id,
66+
policy_source=SessionPolicyType.CUSTOM,
67+
absolute_session_timeout=120,
68+
absolute_session_timeout_unit=TimeUnit.MINUTES,
69+
)
70+
71+
reverted = self.scalekit_client.organization.update_organization_session_policy(
72+
organization_id=org_id,
73+
policy_source=SessionPolicyType.APPLICATION,
74+
)[0].policy
75+
76+
self.assertIsNotNone(reverted)
77+
self.assertEqual(reverted.policy_source, SessionPolicyType.APPLICATION)
78+
79+
fetched = self.scalekit_client.organization.get_organization_session_policy(
80+
organization_id=org_id
81+
)[0].policy
82+
self.assertEqual(fetched.policy_source, SessionPolicyType.APPLICATION)
83+
84+
def test_set_idle_timeout_disabled(self):
85+
"""Setting idle_session_timeout_enabled=False should persist as false."""
86+
org_id = self._create_org()
87+
88+
policy = self.scalekit_client.organization.update_organization_session_policy(
89+
organization_id=org_id,
90+
policy_source=SessionPolicyType.CUSTOM,
91+
absolute_session_timeout=480,
92+
absolute_session_timeout_unit=TimeUnit.MINUTES,
93+
idle_session_timeout_enabled=False,
94+
)[0].policy
95+
96+
self.assertIsNotNone(policy)
97+
self.assertEqual(policy.policy_source, SessionPolicyType.CUSTOM)
98+
99+
fetched = self.scalekit_client.organization.get_organization_session_policy(
100+
organization_id=org_id
101+
)[0].policy
102+
self.assertTrue(fetched.HasField("idle_session_timeout_enabled"))
103+
self.assertFalse(fetched.idle_session_timeout_enabled.value)
104+
105+
def tearDown(self):
106+
if self.org_id:
107+
self.scalekit_client.organization.delete_organization(organization_id=self.org_id)

0 commit comments

Comments
 (0)