slack-objects 0.0.8__tar.gz → 0.0.9__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.0.8 → slack_objects-0.0.9}/PKG-INFO +1 -1
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/_version.py +3 -3
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/users.py +27 -37
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/PKG-INFO +1 -1
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/users_smoke_test.py +2 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/users_unit_test.py +45 -2
- {slack_objects-0.0.8 → slack_objects-0.0.9}/.github/copilot-instructions.md +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/.github/workflows/ci.yml +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/.github/workflows/publish-pypi.yml +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/.github/workflows/publish-testpypi.yml +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/.gitignore +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/.vs/VSWorkspaceState.json +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/.vs/slnx.sqlite +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/LICENSE +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/README.md +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/pyproject.toml +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/pytest.ini +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/setup.cfg +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/__init__.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/api_caller.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/base.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/client.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/config.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/conversations.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/files.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/idp_groups.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/messages.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/rate_limits.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/scim_base.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/usergroups.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/workspaces.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/SOURCES.txt +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/dependency_links.txt +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/requires.txt +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/top_level.txt +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Examples/examples_config.example.json +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Examples/users_search_by_email_example.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Examples/users_test_AzureKeyVault.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/conftest_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/run_all_scim_users_live_tests.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_idp_groups_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_create_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_deactivate_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_input_validation_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_make_guest_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_reactivate_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_update_attribute_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/_smoke_harness.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/api_caller_smoke_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/conversations_smoke_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/files_smoke_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/idp_groups_smoke_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/messages_smoke_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/run_all_smoke.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/security_smoke_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/usergroups_smoke_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/workspaces_smoke_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/config_unit_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/conversations_unit_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/files_unit_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/idp_groups_unit_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/messages_unit_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/rate_limits_unit_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/usergroups_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/workspaces_unit_test.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/conftest_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/run_all_users_live_tests.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/test_users_admin_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/test_users_classification_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/test_users_input_validation_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/test_users_read_live.py +0 -0
- {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/live_test_config.example.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slack-objects
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
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
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.9'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 9)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g82c79f1f4'
|
|
@@ -268,7 +268,11 @@ class Users(ScimMixin, SlackObjectBase):
|
|
|
268
268
|
if not uid:
|
|
269
269
|
raise ValueError("is_active_scim requires user_id (passed or bound)")
|
|
270
270
|
|
|
271
|
-
scim_resp = self._scim_request(
|
|
271
|
+
scim_resp = self._scim_request(
|
|
272
|
+
path=f"Users/{uid}",
|
|
273
|
+
method="GET",
|
|
274
|
+
raise_for_status=False,
|
|
275
|
+
)
|
|
272
276
|
if not scim_resp.ok:
|
|
273
277
|
return False
|
|
274
278
|
return bool(scim_resp.data.get("active", False))
|
|
@@ -511,15 +515,27 @@ class Users(ScimMixin, SlackObjectBase):
|
|
|
511
515
|
|
|
512
516
|
identifier = identifier.strip()
|
|
513
517
|
|
|
518
|
+
# Normalize: strip leading '@' so "@U1234" falls into the user-ID
|
|
519
|
+
# path and "@alice" falls into the username path — no special stage needed.
|
|
520
|
+
if identifier.startswith("@"):
|
|
521
|
+
identifier = identifier.removeprefix("@")
|
|
522
|
+
|
|
514
523
|
# ── 1. Slack user ID ──────────────────────────────────────
|
|
515
524
|
if self._looks_like_user_id(identifier):
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
525
|
+
try:
|
|
526
|
+
resp = self.get_user_info(identifier)
|
|
527
|
+
if resp.get("ok"):
|
|
528
|
+
return identifier
|
|
529
|
+
except SlackApiError:
|
|
530
|
+
pass # expected for deactivated / not-found users
|
|
519
531
|
|
|
520
532
|
# Slow path: SCIM (active + deactivated)
|
|
521
533
|
self.logger.info("Web API miss for %s — falling back to SCIM lookup", identifier)
|
|
522
|
-
scim_resp = self._scim_request(
|
|
534
|
+
scim_resp = self._scim_request(
|
|
535
|
+
path=f"Users/{identifier}",
|
|
536
|
+
method="GET",
|
|
537
|
+
raise_for_status=False,
|
|
538
|
+
)
|
|
523
539
|
if scim_resp.ok and scim_resp.data.get("id"):
|
|
524
540
|
return scim_resp.data["id"]
|
|
525
541
|
|
|
@@ -528,12 +544,9 @@ class Users(ScimMixin, SlackObjectBase):
|
|
|
528
544
|
# ── 2. Email address ──────────────────────────────────────
|
|
529
545
|
if EMAIL_RE.match(identifier):
|
|
530
546
|
# Fast path: Web API (active users only)
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return uid
|
|
535
|
-
except SlackApiError:
|
|
536
|
-
pass # expected for deactivated / not-found users
|
|
547
|
+
uid = self.get_user_id_from_email(identifier)
|
|
548
|
+
if uid:
|
|
549
|
+
return uid
|
|
537
550
|
|
|
538
551
|
# Slow path: SCIM (active + deactivated)
|
|
539
552
|
self.logger.info("Web API miss for %s — falling back to SCIM search", identifier)
|
|
@@ -543,32 +556,9 @@ class Users(ScimMixin, SlackObjectBase):
|
|
|
543
556
|
|
|
544
557
|
raise LookupError(f"No user found for email: {identifier}")
|
|
545
558
|
|
|
546
|
-
# ── 3.
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
# The caller may have passed "@U1234" — recognise the ID
|
|
551
|
-
# and verify via the cheaper Web API instead of SCIM.
|
|
552
|
-
if self._looks_like_user_id(stripped):
|
|
553
|
-
resp = self.get_user_info(stripped)
|
|
554
|
-
if resp.get("ok"):
|
|
555
|
-
return stripped
|
|
556
|
-
|
|
557
|
-
# Slow path: SCIM (active + deactivated)
|
|
558
|
-
self.logger.info("Web API miss for %s — falling back to SCIM lookup", stripped)
|
|
559
|
-
scim_resp = self._scim_request(path=f"Users/{stripped}", method="GET")
|
|
560
|
-
if scim_resp.ok and scim_resp.data.get("id"):
|
|
561
|
-
return scim_resp.data["id"]
|
|
562
|
-
|
|
563
|
-
raise LookupError(f"No user found for user ID: {stripped}")
|
|
564
|
-
|
|
565
|
-
uid = self._first_scim_user_id(self.scim_search_user_by_username(stripped))
|
|
566
|
-
if uid:
|
|
567
|
-
return uid
|
|
568
|
-
|
|
569
|
-
raise LookupError(f"No user found for username: @{stripped}")
|
|
570
|
-
|
|
571
|
-
# ── 4. Bare string — try as username via SCIM ─────────────
|
|
559
|
+
# ── 3. Bare string / username — SCIM only ─────────────────
|
|
560
|
+
# No Web API endpoint exists for username lookup; SCIM is the
|
|
561
|
+
# only option. This also handles "@alice" (already stripped above).
|
|
572
562
|
uid = self._first_scim_user_id(self.scim_search_user_by_username(identifier))
|
|
573
563
|
if uid:
|
|
574
564
|
return uid
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slack-objects
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
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
|
|
@@ -169,6 +169,8 @@ def main() -> None:
|
|
|
169
169
|
|
|
170
170
|
# Identifier resolution
|
|
171
171
|
CallSpec("resolve_user_id(user_id)", lambda: bound.resolve_user_id("U1")),
|
|
172
|
+
CallSpec("resolve_user_id(email)", lambda: bound.resolve_user_id("found@example.com")),
|
|
173
|
+
CallSpec("resolve_user_id(@username)", lambda: bound.resolve_user_id("@testuser")),
|
|
172
174
|
]
|
|
173
175
|
|
|
174
176
|
run_smoke("Users smoke (all public methods)", specs)
|
|
@@ -3,6 +3,7 @@ from typing import Any, Dict, Optional
|
|
|
3
3
|
from unittest.mock import MagicMock
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
|
+
from slack_sdk.errors import SlackApiError
|
|
6
7
|
|
|
7
8
|
from slack_objects.client import SlackObjectsClient
|
|
8
9
|
from slack_objects.config import SlackObjectsConfig, RateTier, USER_ID_RE
|
|
@@ -182,6 +183,9 @@ class TestResolveUserId:
|
|
|
182
183
|
)
|
|
183
184
|
with pytest.raises(LookupError, match="No user found for user ID"):
|
|
184
185
|
users.resolve_user_id("U00GHOST")
|
|
186
|
+
users._scim_request.assert_called_once_with(
|
|
187
|
+
path="Users/U00GHOST", method="GET", raise_for_status=False,
|
|
188
|
+
)
|
|
185
189
|
|
|
186
190
|
def test_email_active_user(self):
|
|
187
191
|
"""Active user email resolves via Web API (fast path)."""
|
|
@@ -221,7 +225,7 @@ class TestResolveUserId:
|
|
|
221
225
|
scim_resp = ScimResponse(ok=True, status_code=200, data={"Resources": []}, text="")
|
|
222
226
|
users.scim_search_user_by_username = MagicMock(return_value=scim_resp)
|
|
223
227
|
|
|
224
|
-
with pytest.raises(LookupError, match="No user found for
|
|
228
|
+
with pytest.raises(LookupError, match="No user found for identifier"):
|
|
225
229
|
users.resolve_user_id("@ghost")
|
|
226
230
|
|
|
227
231
|
def test_bare_username(self):
|
|
@@ -258,6 +262,43 @@ class TestResolveUserId:
|
|
|
258
262
|
)
|
|
259
263
|
with pytest.raises(LookupError, match="No user found for user ID"):
|
|
260
264
|
users.resolve_user_id("@U00GHOST")
|
|
265
|
+
users._scim_request.assert_called_once_with(
|
|
266
|
+
path="Users/U00GHOST", method="GET", raise_for_status=False,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def test_user_id_slack_api_error_falls_back_to_scim(self):
|
|
270
|
+
"""SlackApiError from users.info falls through to SCIM lookup."""
|
|
271
|
+
users = _make_users()
|
|
272
|
+
users.get_user_info = MagicMock(
|
|
273
|
+
side_effect=SlackApiError("users_not_found", response=MagicMock())
|
|
274
|
+
)
|
|
275
|
+
scim_resp = ScimResponse(
|
|
276
|
+
ok=True, status_code=200,
|
|
277
|
+
data={"id": "USCIM", "userName": "scim-user", "active": True},
|
|
278
|
+
text="",
|
|
279
|
+
)
|
|
280
|
+
users._scim_request = MagicMock(return_value=scim_resp)
|
|
281
|
+
|
|
282
|
+
assert users.resolve_user_id("USCIM") == "USCIM"
|
|
283
|
+
users._scim_request.assert_called_once_with(
|
|
284
|
+
path="Users/USCIM", method="GET", raise_for_status=False,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def test_user_id_slack_api_error_and_scim_miss_raises(self):
|
|
288
|
+
"""SlackApiError + SCIM miss still raises LookupError."""
|
|
289
|
+
users = _make_users()
|
|
290
|
+
users.get_user_info = MagicMock(
|
|
291
|
+
side_effect=SlackApiError("users_not_found", response=MagicMock())
|
|
292
|
+
)
|
|
293
|
+
users._scim_request = MagicMock(
|
|
294
|
+
return_value=ScimResponse(ok=False, status_code=404, data={}, text="")
|
|
295
|
+
)
|
|
296
|
+
with pytest.raises(LookupError, match="No user found for user ID"):
|
|
297
|
+
users.resolve_user_id("U00GHOST")
|
|
298
|
+
users._scim_request.assert_called_once_with(
|
|
299
|
+
path="Users/U00GHOST", method="GET", raise_for_status=False,
|
|
300
|
+
)
|
|
301
|
+
|
|
261
302
|
|
|
262
303
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
263
304
|
# is_user_authorized
|
|
@@ -373,7 +414,9 @@ class TestIsActiveScim:
|
|
|
373
414
|
return_value=ScimResponse(ok=True, status_code=200, data={"id": "U_OTHER", "active": True}, text=""),
|
|
374
415
|
)
|
|
375
416
|
assert bound.is_active_scim(user_id="U_OTHER") is True
|
|
376
|
-
bound._scim_request.assert_called_once_with(
|
|
417
|
+
bound._scim_request.assert_called_once_with(
|
|
418
|
+
path="Users/U_OTHER", method="GET", raise_for_status=False,
|
|
419
|
+
)
|
|
377
420
|
|
|
378
421
|
def test_no_user_id_raises(self):
|
|
379
422
|
"""Unbound instance with no user_id raises ValueError."""
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_input_validation_live.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_update_attribute_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
|