slack-objects 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slack_objects-0.1.2/.github/copilot-instructions.md +8 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/PKG-INFO +22 -6
- {slack_objects-0.1.0 → slack_objects-0.1.2}/README.md +21 -5
- slack_objects-0.1.2/src/slack_objects/_version.py +24 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/users.py +12 -4
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects.egg-info/PKG-INFO +22 -6
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_users_update_attribute_live.py +15 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/users_smoke_test.py +17 -5
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/UnitTests/users_unit_test.py +90 -0
- slack_objects-0.1.0/.github/copilot-instructions.md +0 -5
- slack_objects-0.1.0/src/slack_objects/_version.py +0 -34
- {slack_objects-0.1.0 → slack_objects-0.1.2}/.github/workflows/ci.yml +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/.github/workflows/publish-pypi.yml +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/.github/workflows/publish-testpypi.yml +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/.gitignore +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/LICENSE +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/pyproject.toml +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/pytest.ini +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/setup.cfg +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/__init__.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/api_caller.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/base.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/client.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/config.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/conversations.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/files.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/idp_groups.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/messages.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/rate_limits.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/scim_base.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/usergroups.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects/workspaces.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects.egg-info/SOURCES.txt +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects.egg-info/dependency_links.txt +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects.egg-info/requires.txt +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/src/slack_objects.egg-info/top_level.txt +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Examples/examples_config.example.json +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Examples/users_search_by_email_example.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Examples/users_test_AzureKeyVault.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/conftest_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/run_all_scim_users_live_tests.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_idp_groups_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_users_create_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_users_deactivate_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_users_input_validation_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_users_make_guest_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_users_reactivate_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_users_update_email_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/_smoke_harness.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/api_caller_smoke_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/conversations_smoke_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/files_smoke_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/idp_groups_smoke_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/messages_smoke_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/run_all_smoke.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/security_smoke_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/usergroups_smoke_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Smoke/workspaces_smoke_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/UnitTests/config_unit_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/UnitTests/conversations_unit_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/UnitTests/files_unit_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/UnitTests/idp_groups_unit_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/UnitTests/messages_unit_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/UnitTests/rate_limits_unit_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/UnitTests/usergroups_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/UnitTests/workspaces_unit_test.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Users/conftest_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Users/run_all_users_live_tests.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Users/test_users_admin_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Users/test_users_classification_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Users/test_users_input_validation_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/Users/test_users_read_live.py +0 -0
- {slack_objects-0.1.0 → slack_objects-0.1.2}/tests/live_test_config.example.json +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Copilot Instructions
|
|
2
|
+
|
|
3
|
+
## Project Guidelines
|
|
4
|
+
- Follow Unix philosophy: build small, composable functions where higher-level methods leverage lower-level primitives. Don't mix concerns (e.g., don't embed identifier resolution inside a SCIM CRUD method). Keep signatures consistent across sibling methods.
|
|
5
|
+
- Prefer consistency with the `Users.is_active`/`is_active_scim` pattern when structuring related methods (separate explicit SCIM method from higher-level orchestration).
|
|
6
|
+
|
|
7
|
+
## Testing Guidelines
|
|
8
|
+
- Unit test files in `tests/UnitTests` must follow the naming convention `*_unit_test.py` (not `test_*` prefix). The following files should adhere to this convention: `users_unit_test.py`, `conversations_unit_test.py`, `messages_unit_test.py`, `files_unit_test.py`, `workspaces_unit_test.py`, `idp_groups_unit_test.py`, `config_unit_test.py`, and two others with the `_unit_test.py` suffix.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slack-objects
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Opinionated, testable Python wrappers for Slack’s Web, Admin, and SCIM APIs, organized by object domain (users, conversations, messages, files, workspaces, and IdP groups). Designed for automation and administration workflows.
|
|
5
5
|
Author-email: "Marcos E. Mercado" <marcos_elias@hotmail.com>
|
|
6
6
|
Keywords: slack,objects,classes,slack objects,utilities,slack utilities,slack object types,slack types,types
|
|
@@ -23,7 +23,7 @@ Dynamic: license-file
|
|
|
23
23
|
|
|
24
24
|
A focused Python package for working with **Slack objects** commonly used in administration and automation workflows.
|
|
25
25
|
|
|
26
|
-
`slack-objects` provides **opinionated, testable wrappers** around the Slack Web API, Admin API, and
|
|
26
|
+
`slack-objects` provides **opinionated, testable wrappers** around the Slack Web API, Admin API, SCIM API, and Discovery API—favoring object-based access over raw endpoint calls.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -55,7 +55,7 @@ Instead, it focuses on higher-level object operations that typically require:
|
|
|
55
55
|
- multiple API calls
|
|
56
56
|
- pagination
|
|
57
57
|
- rate limiting
|
|
58
|
-
- Admin API or
|
|
58
|
+
- Admin API, SCIM, or Discovery API usage
|
|
59
59
|
- non-trivial orchestration logic
|
|
60
60
|
|
|
61
61
|
---
|
|
@@ -73,7 +73,7 @@ cfg = SlackObjectsConfig(
|
|
|
73
73
|
bot_token="xoxb-...",
|
|
74
74
|
user_token="xoxp-...",
|
|
75
75
|
scim_token="xoxp-...",
|
|
76
|
-
# see SlackObjectsConfig for additional options (scim_base_url, http_timeout_seconds, etc.)
|
|
76
|
+
# see SlackObjectsConfig for additional options (team_id, scim_base_url, http_timeout_seconds, etc.)
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
slack = SlackObjectsClient(cfg)
|
|
@@ -139,6 +139,8 @@ The codebase is designed to be tested **without hitting Slack**.
|
|
|
139
139
|
|
|
140
140
|
## Installation
|
|
141
141
|
|
|
142
|
+
Requires **Python 3.9+**.
|
|
143
|
+
|
|
142
144
|
```bash
|
|
143
145
|
pip install slack-objects
|
|
144
146
|
```
|
|
@@ -154,7 +156,8 @@ cfg = SlackObjectsConfig(
|
|
|
154
156
|
bot_token="xoxb-...",
|
|
155
157
|
user_token="xoxp-...",
|
|
156
158
|
scim_token="xoxp-...",
|
|
157
|
-
|
|
159
|
+
team_id="T0123ABC", # workspace ID; required for org-wide tokens calling workspace-scoped Web APIs
|
|
160
|
+
default_rate_tier=RateTier.TIER_2, # fallback sleep between API calls when no specific tier matches (default)
|
|
158
161
|
)
|
|
159
162
|
```
|
|
160
163
|
|
|
@@ -175,10 +178,23 @@ python -m tests.Smoke.run_all_smoke
|
|
|
175
178
|
|
|
176
179
|
---
|
|
177
180
|
|
|
181
|
+
## Rate Limiting
|
|
182
|
+
All Slack Web/Admin API calls go through `SlackApiCaller`, which:
|
|
183
|
+
|
|
184
|
+
- Sleeps according to the resolved rate tier after every successful call
|
|
185
|
+
- Automatically retries on HTTP **429** (rate-limited) responses up to **5 times**, respecting the `Retry-After` header
|
|
186
|
+
|
|
187
|
+
Rate tiers are resolved in priority order:
|
|
188
|
+
explicit per-call tier → method-specific override → prefix rule → `default_rate_tier` from config.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
178
192
|
## Notes
|
|
179
193
|
|
|
180
194
|
- SCIM v2 is the default; v1 is supported where applicable
|
|
181
|
-
- `PC_Utils` is
|
|
195
|
+
- `PC_Utils` is a required dependency (used for datetime handling in `set_guest_expiration_date`)
|
|
182
196
|
- This package is intended for automation and administration workflows
|
|
183
197
|
- `resolve_user_id` accepts flexible identifiers (user ID, email, or @username) and verifies existence via Web API + SCIM fallback
|
|
184
198
|
- `is_user_authorized` supports IdP-group-based authorization checks with configurable read/write access levels
|
|
199
|
+
- SCIM user operations are available on `Users`: create, deactivate, reactivate, update attributes, update email, and convert to multi-channel guest
|
|
200
|
+
- Discovery API is used for `Users.get_channels` and `Conversations.get_members` (requires appropriate token scopes)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A focused Python package for working with **Slack objects** commonly used in administration and automation workflows.
|
|
4
4
|
|
|
5
|
-
`slack-objects` provides **opinionated, testable wrappers** around the Slack Web API, Admin API, and
|
|
5
|
+
`slack-objects` provides **opinionated, testable wrappers** around the Slack Web API, Admin API, SCIM API, and Discovery API—favoring object-based access over raw endpoint calls.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -34,7 +34,7 @@ Instead, it focuses on higher-level object operations that typically require:
|
|
|
34
34
|
- multiple API calls
|
|
35
35
|
- pagination
|
|
36
36
|
- rate limiting
|
|
37
|
-
- Admin API or
|
|
37
|
+
- Admin API, SCIM, or Discovery API usage
|
|
38
38
|
- non-trivial orchestration logic
|
|
39
39
|
|
|
40
40
|
---
|
|
@@ -52,7 +52,7 @@ cfg = SlackObjectsConfig(
|
|
|
52
52
|
bot_token="xoxb-...",
|
|
53
53
|
user_token="xoxp-...",
|
|
54
54
|
scim_token="xoxp-...",
|
|
55
|
-
# see SlackObjectsConfig for additional options (scim_base_url, http_timeout_seconds, etc.)
|
|
55
|
+
# see SlackObjectsConfig for additional options (team_id, scim_base_url, http_timeout_seconds, etc.)
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
slack = SlackObjectsClient(cfg)
|
|
@@ -118,6 +118,8 @@ The codebase is designed to be tested **without hitting Slack**.
|
|
|
118
118
|
|
|
119
119
|
## Installation
|
|
120
120
|
|
|
121
|
+
Requires **Python 3.9+**.
|
|
122
|
+
|
|
121
123
|
```bash
|
|
122
124
|
pip install slack-objects
|
|
123
125
|
```
|
|
@@ -133,7 +135,8 @@ cfg = SlackObjectsConfig(
|
|
|
133
135
|
bot_token="xoxb-...",
|
|
134
136
|
user_token="xoxp-...",
|
|
135
137
|
scim_token="xoxp-...",
|
|
136
|
-
|
|
138
|
+
team_id="T0123ABC", # workspace ID; required for org-wide tokens calling workspace-scoped Web APIs
|
|
139
|
+
default_rate_tier=RateTier.TIER_2, # fallback sleep between API calls when no specific tier matches (default)
|
|
137
140
|
)
|
|
138
141
|
```
|
|
139
142
|
|
|
@@ -154,10 +157,23 @@ python -m tests.Smoke.run_all_smoke
|
|
|
154
157
|
|
|
155
158
|
---
|
|
156
159
|
|
|
160
|
+
## Rate Limiting
|
|
161
|
+
All Slack Web/Admin API calls go through `SlackApiCaller`, which:
|
|
162
|
+
|
|
163
|
+
- Sleeps according to the resolved rate tier after every successful call
|
|
164
|
+
- Automatically retries on HTTP **429** (rate-limited) responses up to **5 times**, respecting the `Retry-After` header
|
|
165
|
+
|
|
166
|
+
Rate tiers are resolved in priority order:
|
|
167
|
+
explicit per-call tier → method-specific override → prefix rule → `default_rate_tier` from config.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
157
171
|
## Notes
|
|
158
172
|
|
|
159
173
|
- SCIM v2 is the default; v1 is supported where applicable
|
|
160
|
-
- `PC_Utils` is
|
|
174
|
+
- `PC_Utils` is a required dependency (used for datetime handling in `set_guest_expiration_date`)
|
|
161
175
|
- This package is intended for automation and administration workflows
|
|
162
176
|
- `resolve_user_id` accepts flexible identifiers (user ID, email, or @username) and verifies existence via Web API + SCIM fallback
|
|
163
177
|
- `is_user_authorized` supports IdP-group-based authorization checks with configurable read/write access levels
|
|
178
|
+
- SCIM user operations are available on `Users`: create, deactivate, reactivate, update attributes, update email, and convert to multi-channel guest
|
|
179
|
+
- Discovery API is used for `Users.get_channels` and `Conversations.get_members` (requires appropriate token scopes)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 2)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'ge0d1b7810'
|
|
@@ -626,12 +626,20 @@ class Users(ScimMixin, SlackObjectBase):
|
|
|
626
626
|
def scim_update_user_attribute(
|
|
627
627
|
self,
|
|
628
628
|
*,
|
|
629
|
-
user_id: str,
|
|
629
|
+
user_id: Optional[str] = None,
|
|
630
630
|
attribute: str,
|
|
631
631
|
new_value: Any,
|
|
632
632
|
) -> ScimResponse:
|
|
633
|
-
"""SCIM PATCH Users/<id>
|
|
634
|
-
|
|
633
|
+
"""SCIM PATCH Users/<id> for simple scalar attributes.
|
|
634
|
+
|
|
635
|
+
For email updates, use :meth:`scim_update_email` instead — it handles
|
|
636
|
+
the version-specific payload formatting needed to avoid multi-primary
|
|
637
|
+
conflicts.
|
|
638
|
+
"""
|
|
639
|
+
uid = user_id or self.user_id
|
|
640
|
+
if not uid:
|
|
641
|
+
raise ValueError("scim_update_user_attribute requires user_id (passed or bound)")
|
|
642
|
+
validate_scim_id(uid, "user_id")
|
|
635
643
|
|
|
636
644
|
scim_version = self.cfg.scim_version
|
|
637
645
|
if scim_version == "v2":
|
|
@@ -644,7 +652,7 @@ class Users(ScimMixin, SlackObjectBase):
|
|
|
644
652
|
else:
|
|
645
653
|
raise NotImplementedError(f"Invalid SCIM version: {scim_version}")
|
|
646
654
|
|
|
647
|
-
return self._scim_request(path=f"Users/{
|
|
655
|
+
return self._scim_request(path=f"Users/{uid}", method="PATCH", payload=payload)
|
|
648
656
|
|
|
649
657
|
def scim_update_email(
|
|
650
658
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slack-objects
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Opinionated, testable Python wrappers for Slack’s Web, Admin, and SCIM APIs, organized by object domain (users, conversations, messages, files, workspaces, and IdP groups). Designed for automation and administration workflows.
|
|
5
5
|
Author-email: "Marcos E. Mercado" <marcos_elias@hotmail.com>
|
|
6
6
|
Keywords: slack,objects,classes,slack objects,utilities,slack utilities,slack object types,slack types,types
|
|
@@ -23,7 +23,7 @@ Dynamic: license-file
|
|
|
23
23
|
|
|
24
24
|
A focused Python package for working with **Slack objects** commonly used in administration and automation workflows.
|
|
25
25
|
|
|
26
|
-
`slack-objects` provides **opinionated, testable wrappers** around the Slack Web API, Admin API, and
|
|
26
|
+
`slack-objects` provides **opinionated, testable wrappers** around the Slack Web API, Admin API, SCIM API, and Discovery API—favoring object-based access over raw endpoint calls.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -55,7 +55,7 @@ Instead, it focuses on higher-level object operations that typically require:
|
|
|
55
55
|
- multiple API calls
|
|
56
56
|
- pagination
|
|
57
57
|
- rate limiting
|
|
58
|
-
- Admin API or
|
|
58
|
+
- Admin API, SCIM, or Discovery API usage
|
|
59
59
|
- non-trivial orchestration logic
|
|
60
60
|
|
|
61
61
|
---
|
|
@@ -73,7 +73,7 @@ cfg = SlackObjectsConfig(
|
|
|
73
73
|
bot_token="xoxb-...",
|
|
74
74
|
user_token="xoxp-...",
|
|
75
75
|
scim_token="xoxp-...",
|
|
76
|
-
# see SlackObjectsConfig for additional options (scim_base_url, http_timeout_seconds, etc.)
|
|
76
|
+
# see SlackObjectsConfig for additional options (team_id, scim_base_url, http_timeout_seconds, etc.)
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
slack = SlackObjectsClient(cfg)
|
|
@@ -139,6 +139,8 @@ The codebase is designed to be tested **without hitting Slack**.
|
|
|
139
139
|
|
|
140
140
|
## Installation
|
|
141
141
|
|
|
142
|
+
Requires **Python 3.9+**.
|
|
143
|
+
|
|
142
144
|
```bash
|
|
143
145
|
pip install slack-objects
|
|
144
146
|
```
|
|
@@ -154,7 +156,8 @@ cfg = SlackObjectsConfig(
|
|
|
154
156
|
bot_token="xoxb-...",
|
|
155
157
|
user_token="xoxp-...",
|
|
156
158
|
scim_token="xoxp-...",
|
|
157
|
-
|
|
159
|
+
team_id="T0123ABC", # workspace ID; required for org-wide tokens calling workspace-scoped Web APIs
|
|
160
|
+
default_rate_tier=RateTier.TIER_2, # fallback sleep between API calls when no specific tier matches (default)
|
|
158
161
|
)
|
|
159
162
|
```
|
|
160
163
|
|
|
@@ -175,10 +178,23 @@ python -m tests.Smoke.run_all_smoke
|
|
|
175
178
|
|
|
176
179
|
---
|
|
177
180
|
|
|
181
|
+
## Rate Limiting
|
|
182
|
+
All Slack Web/Admin API calls go through `SlackApiCaller`, which:
|
|
183
|
+
|
|
184
|
+
- Sleeps according to the resolved rate tier after every successful call
|
|
185
|
+
- Automatically retries on HTTP **429** (rate-limited) responses up to **5 times**, respecting the `Retry-After` header
|
|
186
|
+
|
|
187
|
+
Rate tiers are resolved in priority order:
|
|
188
|
+
explicit per-call tier → method-specific override → prefix rule → `default_rate_tier` from config.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
178
192
|
## Notes
|
|
179
193
|
|
|
180
194
|
- SCIM v2 is the default; v1 is supported where applicable
|
|
181
|
-
- `PC_Utils` is
|
|
195
|
+
- `PC_Utils` is a required dependency (used for datetime handling in `set_guest_expiration_date`)
|
|
182
196
|
- This package is intended for automation and administration workflows
|
|
183
197
|
- `resolve_user_id` accepts flexible identifiers (user ID, email, or @username) and verifies existence via Web API + SCIM fallback
|
|
184
198
|
- `is_user_authorized` supports IdP-group-based authorization checks with configurable read/write access levels
|
|
199
|
+
- SCIM user operations are available on `Users`: create, deactivate, reactivate, update attributes, update email, and convert to multi-channel guest
|
|
200
|
+
- Discovery API is used for `Users.get_channels` and `Conversations.get_members` (requires appropriate token scopes)
|
{slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_users_update_attribute_live.py
RENAMED
|
@@ -190,6 +190,21 @@ class TestScimUpdateUserAttribute:
|
|
|
190
190
|
)
|
|
191
191
|
_pause()
|
|
192
192
|
|
|
193
|
+
# ----- bound user_id (no explicit user_id) -----
|
|
194
|
+
|
|
195
|
+
def test_update_attribute_bound_user(self, ctx, users):
|
|
196
|
+
"""Update attribute using bound user_id (no explicit user_id arg)."""
|
|
197
|
+
bound = users.with_user(ctx.active_member_id)
|
|
198
|
+
resp = bound.scim_update_user_attribute(
|
|
199
|
+
attribute="title",
|
|
200
|
+
new_value="Bound Test Title",
|
|
201
|
+
)
|
|
202
|
+
assert resp.ok, f"Expected ok for bound user: {resp.data}"
|
|
203
|
+
_pause()
|
|
204
|
+
|
|
205
|
+
bound.scim_update_user_attribute(attribute="title", new_value="")
|
|
206
|
+
_pause()
|
|
207
|
+
|
|
193
208
|
# ----- deactivated user -----
|
|
194
209
|
|
|
195
210
|
def test_update_attribute_deactivated_user(self, ctx, users):
|
|
@@ -110,17 +110,18 @@ def main() -> None:
|
|
|
110
110
|
CallSpec("get_user_id_from_email(miss)", lambda: users.get_user_id_from_email("missing@example.com")),
|
|
111
111
|
|
|
112
112
|
# Profile helpers
|
|
113
|
-
CallSpec("get_user_profile(
|
|
113
|
+
CallSpec("get_user_profile()", lambda: bound.get_user_profile()),
|
|
114
114
|
CallSpec("get_user_profile(by id)", lambda: users.get_user_profile("U1")),
|
|
115
|
-
CallSpec("set_user_profile_field(
|
|
115
|
+
CallSpec("set_user_profile_field()", lambda: bound.set_user_profile_field("status_text", "hello")),
|
|
116
116
|
CallSpec("set_user_profile_field(by id)", lambda: users.set_user_profile_field("status_text", "hello", user_id="U1")),
|
|
117
117
|
|
|
118
118
|
# Classification helpers (need attributes)
|
|
119
119
|
CallSpec("is_contingent_worker()", lambda: (_refresh_bound(), bound.is_contingent_worker())),
|
|
120
120
|
CallSpec("is_guest()", lambda: (_refresh_bound(), bound.is_guest())),
|
|
121
121
|
CallSpec("is_active()", lambda: (_refresh_bound(), bound.is_active())),
|
|
122
|
-
CallSpec("is_active(
|
|
122
|
+
CallSpec("is_active(by id)", lambda: (_refresh_bound(), bound.is_active(user_id="U2"))),
|
|
123
123
|
CallSpec("is_active_scim()", lambda: bound.is_active_scim()),
|
|
124
|
+
CallSpec("is_active_scim(by id)", lambda: bound.is_active_scim(user_id="U1")),
|
|
124
125
|
|
|
125
126
|
# Admin helpers
|
|
126
127
|
CallSpec(
|
|
@@ -128,6 +129,7 @@ def main() -> None:
|
|
|
128
129
|
lambda: bound.invite_user(channel_ids=["C1", "C2"], email="new@example.com", team_id="T1"),
|
|
129
130
|
),
|
|
130
131
|
CallSpec("wipe_all_sessions()", lambda: bound.wipe_all_sessions()),
|
|
132
|
+
CallSpec("wipe_all_sessions(by id)", lambda: bound.wipe_all_sessions(user_id="U1")),
|
|
131
133
|
CallSpec("add_to_workspace()", lambda: bound.add_to_workspace("U1", "T1")),
|
|
132
134
|
CallSpec("remove_from_workspace()", lambda: bound.remove_from_workspace("U1", "T1")),
|
|
133
135
|
CallSpec("add_to_conversation()", lambda: bound.add_to_conversation(["U1"], "C1")),
|
|
@@ -152,24 +154,34 @@ def main() -> None:
|
|
|
152
154
|
"set_guest_expiration_date()",
|
|
153
155
|
lambda: bound.set_guest_expiration_date("2030-01-01", workspace_id="T1"),
|
|
154
156
|
),
|
|
157
|
+
CallSpec(
|
|
158
|
+
"set_guest_expiration_date(by id)",
|
|
159
|
+
lambda: bound.set_guest_expiration_date("2030-01-01", user_id="U1", workspace_id="T1"),
|
|
160
|
+
),
|
|
155
161
|
|
|
156
162
|
# SCIM helpers (scim_version now comes from cfg, not a kwarg)
|
|
157
163
|
CallSpec("scim_create_user()", lambda: bound.scim_create_user("testuser", "test@example.com")),
|
|
158
164
|
CallSpec("scim_deactivate_user()", lambda: bound.scim_deactivate_user("U1")),
|
|
159
165
|
CallSpec("scim_reactivate_user()", lambda: bound.scim_reactivate_user()),
|
|
166
|
+
CallSpec("scim_reactivate_user(by id)", lambda: bound.scim_reactivate_user(user_id="U1")),
|
|
160
167
|
CallSpec(
|
|
161
|
-
"scim_update_user_attribute()",
|
|
168
|
+
"scim_update_user_attribute(by id)",
|
|
162
169
|
lambda: bound.scim_update_user_attribute(user_id="U1", attribute="active", new_value=False),
|
|
163
170
|
),
|
|
171
|
+
CallSpec(
|
|
172
|
+
"scim_update_user_attribute()",
|
|
173
|
+
lambda: bound.scim_update_user_attribute(attribute="active", new_value=False),
|
|
174
|
+
),
|
|
164
175
|
CallSpec(
|
|
165
176
|
"scim_update_email(by id)",
|
|
166
177
|
lambda: bound.scim_update_email(user_id="U1", new_email="newemail@example.com"),
|
|
167
178
|
),
|
|
168
179
|
CallSpec(
|
|
169
|
-
"scim_update_email(
|
|
180
|
+
"scim_update_email()",
|
|
170
181
|
lambda: bound.scim_update_email(new_email="newemail@example.com"),
|
|
171
182
|
),
|
|
172
183
|
CallSpec("make_multi_channel_guest()", lambda: bound.make_multi_channel_guest()),
|
|
184
|
+
CallSpec("make_multi_channel_guest(by id)", lambda: bound.make_multi_channel_guest(user_id="U1")),
|
|
173
185
|
|
|
174
186
|
# SCIM search primitives
|
|
175
187
|
CallSpec("scim_search_user_by_email()", lambda: bound.scim_search_user_by_email("test@example.com")),
|
|
@@ -573,3 +573,93 @@ class TestScimUpdateEmail:
|
|
|
573
573
|
users = _make_users()
|
|
574
574
|
with pytest.raises(ValueError):
|
|
575
575
|
users.scim_update_email(user_id="", new_email="x@example.com")
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
579
|
+
# scim_update_user_attribute
|
|
580
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
581
|
+
|
|
582
|
+
class TestScimUpdateUserAttribute:
|
|
583
|
+
"""scim_update_user_attribute — version-aware SCIM PATCH for scalar attributes."""
|
|
584
|
+
|
|
585
|
+
def test_v2_payload_structure(self):
|
|
586
|
+
"""v2 uses PatchOp schema with replace operation."""
|
|
587
|
+
users = _make_users()
|
|
588
|
+
bound = users.with_user("U1")
|
|
589
|
+
bound._scim_request = MagicMock(
|
|
590
|
+
return_value=ScimResponse(ok=True, status_code=200, data={}, text=""),
|
|
591
|
+
)
|
|
592
|
+
bound.scim_update_user_attribute(user_id="U1", attribute="active", new_value=False)
|
|
593
|
+
|
|
594
|
+
bound._scim_request.assert_called_once()
|
|
595
|
+
_, kwargs = bound._scim_request.call_args
|
|
596
|
+
op = kwargs["payload"]["Operations"][0]
|
|
597
|
+
assert op["op"] == "replace"
|
|
598
|
+
assert op["path"] == "active"
|
|
599
|
+
assert op["value"] is False
|
|
600
|
+
|
|
601
|
+
def test_v1_payload_structure(self):
|
|
602
|
+
"""v1 sets attribute directly on payload root."""
|
|
603
|
+
users = _make_users()
|
|
604
|
+
bound = users.with_user("U1")
|
|
605
|
+
object.__setattr__(bound.cfg, "scim_version", "v1")
|
|
606
|
+
bound._scim_request = MagicMock(
|
|
607
|
+
return_value=ScimResponse(ok=True, status_code=200, data={}, text=""),
|
|
608
|
+
)
|
|
609
|
+
bound.scim_update_user_attribute(user_id="U1", attribute="active", new_value=False)
|
|
610
|
+
|
|
611
|
+
bound._scim_request.assert_called_once()
|
|
612
|
+
_, kwargs = bound._scim_request.call_args
|
|
613
|
+
assert kwargs["payload"]["active"] is False
|
|
614
|
+
|
|
615
|
+
def test_falls_back_to_bound_user_id(self):
|
|
616
|
+
"""Omitting user_id uses self.user_id."""
|
|
617
|
+
users = _make_users()
|
|
618
|
+
bound = users.with_user("U1")
|
|
619
|
+
bound._scim_request = MagicMock(
|
|
620
|
+
return_value=ScimResponse(ok=True, status_code=200, data={}, text=""),
|
|
621
|
+
)
|
|
622
|
+
bound.scim_update_user_attribute(attribute="displayName", new_value="Alice")
|
|
623
|
+
|
|
624
|
+
bound._scim_request.assert_called_once()
|
|
625
|
+
_, kwargs = bound._scim_request.call_args
|
|
626
|
+
assert kwargs["path"] == "Users/U1"
|
|
627
|
+
|
|
628
|
+
def test_explicit_user_id_overrides_bound(self):
|
|
629
|
+
"""Passing user_id explicitly takes precedence over bound user_id."""
|
|
630
|
+
users = _make_users()
|
|
631
|
+
bound = users.with_user("U_IGNORED")
|
|
632
|
+
bound._scim_request = MagicMock(
|
|
633
|
+
return_value=ScimResponse(ok=True, status_code=200, data={}, text=""),
|
|
634
|
+
)
|
|
635
|
+
bound.scim_update_user_attribute(user_id="U_OTHER", attribute="active", new_value=True)
|
|
636
|
+
|
|
637
|
+
bound._scim_request.assert_called_once()
|
|
638
|
+
_, kwargs = bound._scim_request.call_args
|
|
639
|
+
assert kwargs["path"] == "Users/U_OTHER"
|
|
640
|
+
|
|
641
|
+
def test_no_user_id_raises(self):
|
|
642
|
+
"""Unbound instance with no user_id raises ValueError."""
|
|
643
|
+
users = _make_users()
|
|
644
|
+
with pytest.raises(ValueError, match="requires user_id"):
|
|
645
|
+
users.scim_update_user_attribute(attribute="active", new_value=False)
|
|
646
|
+
|
|
647
|
+
def test_invalid_scim_version_raises(self):
|
|
648
|
+
"""Unsupported scim_version raises NotImplementedError."""
|
|
649
|
+
users = _make_users()
|
|
650
|
+
bound = users.with_user("U1")
|
|
651
|
+
object.__setattr__(bound.cfg, "scim_version", "v99")
|
|
652
|
+
with pytest.raises(NotImplementedError, match="Invalid SCIM version"):
|
|
653
|
+
bound.scim_update_user_attribute(attribute="active", new_value=False)
|
|
654
|
+
|
|
655
|
+
def test_invalid_user_id_raises(self):
|
|
656
|
+
"""Path-traversal user_id is rejected by validate_scim_id."""
|
|
657
|
+
users = _make_users()
|
|
658
|
+
with pytest.raises(ValueError):
|
|
659
|
+
users.scim_update_user_attribute(user_id="../../bad", attribute="active", new_value=False)
|
|
660
|
+
|
|
661
|
+
def test_empty_user_id_raises(self):
|
|
662
|
+
"""Empty user_id is rejected by validate_scim_id."""
|
|
663
|
+
users = _make_users()
|
|
664
|
+
with pytest.raises(ValueError):
|
|
665
|
+
users.scim_update_user_attribute(user_id="", attribute="active", new_value=False)
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# Copilot Instructions
|
|
2
|
-
|
|
3
|
-
## Project Guidelines
|
|
4
|
-
- Follow Unix philosophy: build small, composable functions where higher-level methods leverage lower-level primitives. Don't mix concerns (e.g., don't embed identifier resolution inside a SCIM CRUD method). Keep signatures consistent across sibling methods.
|
|
5
|
-
- Prefer consistency with the `Users.is_active`/`is_active_scim` pattern when structuring related methods (separate explicit SCIM method from higher-level orchestration).
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# file generated by setuptools-scm
|
|
2
|
-
# don't change, don't track in version control
|
|
3
|
-
|
|
4
|
-
__all__ = [
|
|
5
|
-
"__version__",
|
|
6
|
-
"__version_tuple__",
|
|
7
|
-
"version",
|
|
8
|
-
"version_tuple",
|
|
9
|
-
"__commit_id__",
|
|
10
|
-
"commit_id",
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
TYPE_CHECKING = False
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from typing import Tuple
|
|
16
|
-
from typing import Union
|
|
17
|
-
|
|
18
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
-
COMMIT_ID = Union[str, None]
|
|
20
|
-
else:
|
|
21
|
-
VERSION_TUPLE = object
|
|
22
|
-
COMMIT_ID = object
|
|
23
|
-
|
|
24
|
-
version: str
|
|
25
|
-
__version__: str
|
|
26
|
-
__version_tuple__: VERSION_TUPLE
|
|
27
|
-
version_tuple: VERSION_TUPLE
|
|
28
|
-
commit_id: COMMIT_ID
|
|
29
|
-
__commit_id__: COMMIT_ID
|
|
30
|
-
|
|
31
|
-
__version__ = version = '0.1.0'
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
33
|
-
|
|
34
|
-
__commit_id__ = commit_id = 'gf712fe717'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{slack_objects-0.1.0 → slack_objects-0.1.2}/tests/SCIM/test_scim_users_input_validation_live.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|