slack-objects 0.0.post31__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.
Files changed (42) hide show
  1. slack_objects-0.0.post31/.gitignore +14 -0
  2. slack_objects-0.0.post31/.vs/VSWorkspaceState.json +8 -0
  3. slack_objects-0.0.post31/.vs/slnx.sqlite +0 -0
  4. slack_objects-0.0.post31/LICENSE +21 -0
  5. slack_objects-0.0.post31/PKG-INFO +201 -0
  6. slack_objects-0.0.post31/README.md +185 -0
  7. slack_objects-0.0.post31/pyproject.toml +29 -0
  8. slack_objects-0.0.post31/pytest.ini +3 -0
  9. slack_objects-0.0.post31/setup.cfg +4 -0
  10. slack_objects-0.0.post31/src/slack_objects/__init__.py +22 -0
  11. slack_objects-0.0.post31/src/slack_objects/_version.py +34 -0
  12. slack_objects-0.0.post31/src/slack_objects/api_caller.py +42 -0
  13. slack_objects-0.0.post31/src/slack_objects/base.py +30 -0
  14. slack_objects-0.0.post31/src/slack_objects/client.py +50 -0
  15. slack_objects-0.0.post31/src/slack_objects/config.py +29 -0
  16. slack_objects-0.0.post31/src/slack_objects/conversations.py +437 -0
  17. slack_objects-0.0.post31/src/slack_objects/files.py +331 -0
  18. slack_objects-0.0.post31/src/slack_objects/idp_groups.py +200 -0
  19. slack_objects-0.0.post31/src/slack_objects/messages.py +322 -0
  20. slack_objects-0.0.post31/src/slack_objects/rate_limits.py +51 -0
  21. slack_objects-0.0.post31/src/slack_objects/users.py +554 -0
  22. slack_objects-0.0.post31/src/slack_objects/workspaces.py +261 -0
  23. slack_objects-0.0.post31/src/slack_objects.egg-info/PKG-INFO +201 -0
  24. slack_objects-0.0.post31/src/slack_objects.egg-info/SOURCES.txt +40 -0
  25. slack_objects-0.0.post31/src/slack_objects.egg-info/dependency_links.txt +1 -0
  26. slack_objects-0.0.post31/src/slack_objects.egg-info/requires.txt +2 -0
  27. slack_objects-0.0.post31/src/slack_objects.egg-info/top_level.txt +1 -0
  28. slack_objects-0.0.post31/tests/_smoke_harness.py +273 -0
  29. slack_objects-0.0.post31/tests/conversations_example_test.py +64 -0
  30. slack_objects-0.0.post31/tests/conversations_smoke_test.py +50 -0
  31. slack_objects-0.0.post31/tests/files_example_test.py +124 -0
  32. slack_objects-0.0.post31/tests/files_smoke_test.py +57 -0
  33. slack_objects-0.0.post31/tests/idp_groups_example_test.py +269 -0
  34. slack_objects-0.0.post31/tests/idp_groups_smoke_test.py +37 -0
  35. slack_objects-0.0.post31/tests/messages_example_test.py +115 -0
  36. slack_objects-0.0.post31/tests/messages_smoke_test.py +42 -0
  37. slack_objects-0.0.post31/tests/run_all_smoke.py +29 -0
  38. slack_objects-0.0.post31/tests/users_example_test.py +63 -0
  39. slack_objects-0.0.post31/tests/users_smoke_test.py +167 -0
  40. slack_objects-0.0.post31/tests/users_test_AzureKeyVault.py +19 -0
  41. slack_objects-0.0.post31/tests/workspaces_example_test.py +67 -0
  42. slack_objects-0.0.post31/tests/workspaces_smoke_test.py +40 -0
@@ -0,0 +1,14 @@
1
+ ################################################################################
2
+ # This .gitignore file was automatically created by Microsoft(R) Visual Studio.
3
+ ################################################################################
4
+
5
+ /.vs/slack-objects.slnx
6
+ *.slnx
7
+ *.pyproj
8
+ /.vs/slack-objects/CopilotIndices/18.3.494.57194
9
+ /src/slack_objects/__pycache__
10
+ /.vs/CopilotSnapshots/*
11
+ /.vs/CopilotIndices/*
12
+ /.vs/slack-objects/CopilotIndices/*
13
+ /.vs/slack-objects/CopilotIndices/*
14
+ /tests/__pycache__
@@ -0,0 +1,8 @@
1
+ {
2
+ "ExpandedNodes": [
3
+ "",
4
+ "\\src",
5
+ "\\src\\slack-objects"
6
+ ],
7
+ "PreviewInSolutionExplorer": false
8
+ }
Binary file
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marcos Mercado
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: slack-objects
3
+ Version: 0.0.post31
4
+ Summary: This package defines classes for working with slack objects like users, conversations, messages, etc.
5
+ Author-email: "Marcos E. Mercado" <marcos_elias@hotmail.com>
6
+ Keywords: slack,objects,classes,slack objects,utilities,slack utilities,slack object types,slack types,types
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: slack-sdk
14
+ Requires-Dist: PC_Utils
15
+ Dynamic: license-file
16
+
17
+ # slack-objects
18
+
19
+ A focused Python package for working with **Slack objects** commonly used in administration and automation workflows.
20
+
21
+ The following Slack object types will be supported:
22
+
23
+ - **Users**
24
+ - **Conversations**
25
+ - **Messages**
26
+ - **Files**
27
+ - **Workspaces**
28
+ - **IDP_groups**
29
+
30
+
31
+ ---
32
+
33
+ ## Overview
34
+
35
+ `slack-objects` provides lightweight, reusable classes that wrap Slack Web API, Admin API, and SCIM operations in a consistent, object-oriented way. It is designed for:
36
+
37
+ - Slack administration automation
38
+ - Identity and access management flows
39
+ - Internal tooling and bots
40
+ - Auditing and cleanup scripts
41
+
42
+ The package does **not** aim to be a full Slack SDK replacement. Instead, it focuses on common higher-level tasks that typically require multiple API calls and boilerplate logic.
43
+
44
+ ---
45
+
46
+ ## Requirements
47
+
48
+ - Python **3.9+**
49
+ - Slack app with appropriate scopes
50
+ - Tokens provided via environment variables or from Azure KeyVault using PC_Azure package (`python -m pip install PC_Azure`)
51
+
52
+ Typical dependencies:
53
+ - `slack_sdk`
54
+ - `requests`
55
+ - `python-dotenv` (optional)
56
+ - `PC_Azure` (optional)
57
+
58
+ ---
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ pip install -r requirements.txt
64
+ ```
65
+
66
+ ## Classes and usage
67
+
68
+ ### `Users`
69
+
70
+ Purpose: actions related to Slack users.
71
+
72
+ Constructor:
73
+ ```python
74
+ Users(global_vars, client, logger, user_id="")
75
+ ```
76
+
77
+ Key methods:
78
+ - `is_contingent_worker()` → bool using name/display name label `[External]`.
79
+ - `is_guest()` → bool if `is_restricted` or `is_ultra_restricted`.
80
+ - `make_multi_channel_guest(token, scim_version='v1')` → `requests.Response` via SCIM (v1/v2).
81
+ - `remove_from_channels(token, client, logger, channel_ids)` → remove user from channels (admin API).
82
+ - `remove_from_workspaces(client, logger, workspace_ids, keep=[])` → remove user from workspaces.
83
+ - `ap_studio_process()` → composite flow: convert to MCG, remove from org-wide channels, remove from other workspaces.
84
+ - `get_userId_from_email(email)` → Slack user ID or empty string.
85
+ - `is_user_authorized(service_name, auth_level='read')` → bool based on IdP group membership.
86
+ - `invite_user(channel_ids, email, team_id, email_password_policy_enabled=False)` → invite a user, returns response string.
87
+
88
+ Example:
89
+ ```python
90
+ u = Users(global_vars, client, logger, user_id="U123")
91
+ if u.is_contingent_worker():
92
+ u.make_multi_channel_guest(token=global_vars.user_token)
93
+ ```
94
+
95
+ ### `Conversations`
96
+
97
+ Purpose: actions related to conversations (e.g., channels).
98
+
99
+ Constructor:
100
+ ```python
101
+ Conversations(global_vars, client, logger, channel_id)
102
+ ```
103
+
104
+ Key methods:
105
+ - `is_private()` → bool.
106
+ - `get_messages(channel_id="", include_all_metadata=False, limit=None, inclusive=True, latest=None, oldest=None)` → list of messages using `conversations.history` with pagination.
107
+
108
+ Example:
109
+ ```python
110
+ ch = Conversations(global_vars, client, logger, channel_id="C123")
111
+ msgs = ch.get_messages(limit=100)
112
+ ```
113
+
114
+ ### `Messages`
115
+
116
+ Purpose: manage Slack messages and blocks.
117
+
118
+ Constructor:
119
+ ```python
120
+ Messages(global_vars, client, logger, channel_id, ts, message=None)
121
+ ```
122
+
123
+ Key methods:
124
+ - `update_message(as_user=True, channel_id="", message_ts="", new_message_blocks=[], new_message_text="", new_message_attachments="")` → update message via `chat.update`.
125
+ - `replace_message_block(blocks=[], block_type="", block_id="", text="", new_block={}, new_block_id="")` → find a block by type or id and replace it, then update message.
126
+
127
+ Example:
128
+ ```python
129
+ msg = Messages(global_vars, client, logger, "C123", "1717000000.000100")
130
+ msg.update_message(new_message_text="Updated content")
131
+ ```
132
+
133
+ ### `Files`
134
+
135
+ Purpose: interact with files in Slack.
136
+
137
+ Constructor:
138
+ ```python
139
+ Files(global_vars, client, logger, file_id="", get_content=False)
140
+ ```
141
+
142
+ Key methods:
143
+ - `get_text_content()` → fetch content for text files via `url_private` (uses bot token).
144
+ - `upload_to_slack(title, channel="", thread_ts="")` → upload the current file content via `files_upload_v2`.
145
+ - `delete_file(file_id="")` → delete a file by id.
146
+ - `list_files(**args)` → simple wrapper around `files.list`.
147
+ - `get_file_source_message(channel: Channels, file_id="", user_id="")` → find the message where a file was shared (looks back ~5 messages).
148
+
149
+ Example:
150
+ ```python
151
+ f = Files(global_vars, client, logger, file_id="F123", get_content=True)
152
+ f.upload_to_slack(title="Processed file", channel="C123")
153
+ ```
154
+
155
+ ### `Workspaces`
156
+
157
+ Purpose: workspace info helper.
158
+
159
+ Constructor:
160
+ ```python
161
+ Workspaces(client, logger, workspace_id)
162
+ ```
163
+
164
+ Obtains attributes via `team.info`.
165
+
166
+ ### `IDP_groups`
167
+
168
+ Purpose: manage IdP (Okta) groups via SCIM.
169
+
170
+ Constructor:
171
+ ```python
172
+ IDP_groups(global_vars)
173
+ ```
174
+
175
+ Key methods:
176
+ - `get_groups()` → list of `{ 'group id', 'group name' }` (paginated).
177
+ - `get_members(group_id)` → list of members with `value` (user id) and `display` (name).
178
+ - `is_member(user_id, group_id)` → bool.
179
+
180
+ Example:
181
+ ```python
182
+ idp = IDP_groups(global_vars)
183
+ if idp.is_member("U123", "GP456"):
184
+ print("authorized")
185
+ ```
186
+
187
+ ## Tokens and rate limits
188
+
189
+ - Methods that call admin or SCIM APIs require the User OAuth token.
190
+ - Standard Web API calls may use the Bot token via `App.client`.
191
+ - Some methods respect internal wait times (e.g., `Tier_2`, `Tier_3`, `Tier_4`) to avoid rate limits. Configure these in `libraries_and_globals.py`.
192
+
193
+ ## Error handling
194
+
195
+ - Methods catch `SlackApiError` and log messages via the provided `logger`.
196
+ - Some methods post audit logs to channels configured in `global_vars`.
197
+
198
+ ## Notes
199
+
200
+ - SCIM version: production uses `v1`, sandbox may use `v2`.
201
+ - `Files.get_text_content()` is designed for `text/*` mimetypes.
@@ -0,0 +1,185 @@
1
+ # slack-objects
2
+
3
+ A focused Python package for working with **Slack objects** commonly used in administration and automation workflows.
4
+
5
+ The following Slack object types will be supported:
6
+
7
+ - **Users**
8
+ - **Conversations**
9
+ - **Messages**
10
+ - **Files**
11
+ - **Workspaces**
12
+ - **IDP_groups**
13
+
14
+
15
+ ---
16
+
17
+ ## Overview
18
+
19
+ `slack-objects` provides lightweight, reusable classes that wrap Slack Web API, Admin API, and SCIM operations in a consistent, object-oriented way. It is designed for:
20
+
21
+ - Slack administration automation
22
+ - Identity and access management flows
23
+ - Internal tooling and bots
24
+ - Auditing and cleanup scripts
25
+
26
+ The package does **not** aim to be a full Slack SDK replacement. Instead, it focuses on common higher-level tasks that typically require multiple API calls and boilerplate logic.
27
+
28
+ ---
29
+
30
+ ## Requirements
31
+
32
+ - Python **3.9+**
33
+ - Slack app with appropriate scopes
34
+ - Tokens provided via environment variables or from Azure KeyVault using PC_Azure package (`python -m pip install PC_Azure`)
35
+
36
+ Typical dependencies:
37
+ - `slack_sdk`
38
+ - `requests`
39
+ - `python-dotenv` (optional)
40
+ - `PC_Azure` (optional)
41
+
42
+ ---
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pip install -r requirements.txt
48
+ ```
49
+
50
+ ## Classes and usage
51
+
52
+ ### `Users`
53
+
54
+ Purpose: actions related to Slack users.
55
+
56
+ Constructor:
57
+ ```python
58
+ Users(global_vars, client, logger, user_id="")
59
+ ```
60
+
61
+ Key methods:
62
+ - `is_contingent_worker()` → bool using name/display name label `[External]`.
63
+ - `is_guest()` → bool if `is_restricted` or `is_ultra_restricted`.
64
+ - `make_multi_channel_guest(token, scim_version='v1')` → `requests.Response` via SCIM (v1/v2).
65
+ - `remove_from_channels(token, client, logger, channel_ids)` → remove user from channels (admin API).
66
+ - `remove_from_workspaces(client, logger, workspace_ids, keep=[])` → remove user from workspaces.
67
+ - `ap_studio_process()` → composite flow: convert to MCG, remove from org-wide channels, remove from other workspaces.
68
+ - `get_userId_from_email(email)` → Slack user ID or empty string.
69
+ - `is_user_authorized(service_name, auth_level='read')` → bool based on IdP group membership.
70
+ - `invite_user(channel_ids, email, team_id, email_password_policy_enabled=False)` → invite a user, returns response string.
71
+
72
+ Example:
73
+ ```python
74
+ u = Users(global_vars, client, logger, user_id="U123")
75
+ if u.is_contingent_worker():
76
+ u.make_multi_channel_guest(token=global_vars.user_token)
77
+ ```
78
+
79
+ ### `Conversations`
80
+
81
+ Purpose: actions related to conversations (e.g., channels).
82
+
83
+ Constructor:
84
+ ```python
85
+ Conversations(global_vars, client, logger, channel_id)
86
+ ```
87
+
88
+ Key methods:
89
+ - `is_private()` → bool.
90
+ - `get_messages(channel_id="", include_all_metadata=False, limit=None, inclusive=True, latest=None, oldest=None)` → list of messages using `conversations.history` with pagination.
91
+
92
+ Example:
93
+ ```python
94
+ ch = Conversations(global_vars, client, logger, channel_id="C123")
95
+ msgs = ch.get_messages(limit=100)
96
+ ```
97
+
98
+ ### `Messages`
99
+
100
+ Purpose: manage Slack messages and blocks.
101
+
102
+ Constructor:
103
+ ```python
104
+ Messages(global_vars, client, logger, channel_id, ts, message=None)
105
+ ```
106
+
107
+ Key methods:
108
+ - `update_message(as_user=True, channel_id="", message_ts="", new_message_blocks=[], new_message_text="", new_message_attachments="")` → update message via `chat.update`.
109
+ - `replace_message_block(blocks=[], block_type="", block_id="", text="", new_block={}, new_block_id="")` → find a block by type or id and replace it, then update message.
110
+
111
+ Example:
112
+ ```python
113
+ msg = Messages(global_vars, client, logger, "C123", "1717000000.000100")
114
+ msg.update_message(new_message_text="Updated content")
115
+ ```
116
+
117
+ ### `Files`
118
+
119
+ Purpose: interact with files in Slack.
120
+
121
+ Constructor:
122
+ ```python
123
+ Files(global_vars, client, logger, file_id="", get_content=False)
124
+ ```
125
+
126
+ Key methods:
127
+ - `get_text_content()` → fetch content for text files via `url_private` (uses bot token).
128
+ - `upload_to_slack(title, channel="", thread_ts="")` → upload the current file content via `files_upload_v2`.
129
+ - `delete_file(file_id="")` → delete a file by id.
130
+ - `list_files(**args)` → simple wrapper around `files.list`.
131
+ - `get_file_source_message(channel: Channels, file_id="", user_id="")` → find the message where a file was shared (looks back ~5 messages).
132
+
133
+ Example:
134
+ ```python
135
+ f = Files(global_vars, client, logger, file_id="F123", get_content=True)
136
+ f.upload_to_slack(title="Processed file", channel="C123")
137
+ ```
138
+
139
+ ### `Workspaces`
140
+
141
+ Purpose: workspace info helper.
142
+
143
+ Constructor:
144
+ ```python
145
+ Workspaces(client, logger, workspace_id)
146
+ ```
147
+
148
+ Obtains attributes via `team.info`.
149
+
150
+ ### `IDP_groups`
151
+
152
+ Purpose: manage IdP (Okta) groups via SCIM.
153
+
154
+ Constructor:
155
+ ```python
156
+ IDP_groups(global_vars)
157
+ ```
158
+
159
+ Key methods:
160
+ - `get_groups()` → list of `{ 'group id', 'group name' }` (paginated).
161
+ - `get_members(group_id)` → list of members with `value` (user id) and `display` (name).
162
+ - `is_member(user_id, group_id)` → bool.
163
+
164
+ Example:
165
+ ```python
166
+ idp = IDP_groups(global_vars)
167
+ if idp.is_member("U123", "GP456"):
168
+ print("authorized")
169
+ ```
170
+
171
+ ## Tokens and rate limits
172
+
173
+ - Methods that call admin or SCIM APIs require the User OAuth token.
174
+ - Standard Web API calls may use the Bot token via `App.client`.
175
+ - Some methods respect internal wait times (e.g., `Tier_2`, `Tier_3`, `Tier_4`) to avoid rate limits. Configure these in `libraries_and_globals.py`.
176
+
177
+ ## Error handling
178
+
179
+ - Methods catch `SlackApiError` and log messages via the provided `logger`.
180
+ - Some methods post audit logs to channels configured in `global_vars`.
181
+
182
+ ## Notes
183
+
184
+ - SCIM version: production uses `v1`, sandbox may use `v2`.
185
+ - `Files.get_text_content()` is designed for `text/*` mimetypes.
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 77.0.3", "setuptools_scm>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "slack-objects"
7
+ # version = "0.0.1" # Remove any existing version parameter.
8
+ dynamic = ["version"]
9
+ authors = [
10
+ {name="Marcos E. Mercado", email="marcos_elias@hotmail.com"},
11
+ ]
12
+ description = "This package defines classes for working with slack objects like users, conversations, messages, etc."
13
+ readme = "README.md"
14
+ requires-python = ">=3.8"
15
+ classifiers=[
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ] # Update these accordingly
20
+ keywords = ["slack", "objects", "classes", "slack objects", "utilities", "slack utilities", "slack object types", "slack types", "types"]
21
+ dependencies = [
22
+ "slack-sdk",
23
+ "PC_Utils",
24
+ ]
25
+
26
+ [tool.setuptools_scm]
27
+ version_file = "src/slack_objects/_version.py"
28
+ local_scheme = "no-local-version"
29
+ version_scheme = "post-release"
@@ -0,0 +1,3 @@
1
+ [pytest]
2
+ pythonpath =
3
+ src
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,22 @@
1
+ from .client import SlackObjectsClient
2
+ from .config import SlackObjectsConfig, RateTier #, IdPGroupConfig
3
+
4
+ from .users import Users
5
+ #from .conversations import Conversations
6
+ #from .messages import Messages
7
+ #from .files import Files
8
+ #from .workspaces import Workspaces
9
+ #from .idp_groups import IDP_groups
10
+
11
+ __all__ = [
12
+ "SlackObjectsClient",
13
+ "SlackObjectsConfig",
14
+ "RateTier",
15
+ "IdPGroupConfig",
16
+ "Users",
17
+ "Channels",
18
+ "Messages",
19
+ "Files",
20
+ "Workspaces",
21
+ "IDP_groups",
22
+ ]
@@ -0,0 +1,34 @@
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.0.post31'
32
+ __version_tuple__ = version_tuple = (0, 0, 'post31')
33
+
34
+ __commit_id__ = commit_id = 'gcf17794dd'
@@ -0,0 +1,42 @@
1
+ import time
2
+ from typing import Any, Optional
3
+
4
+ from slack_sdk.errors import SlackApiError
5
+
6
+ from .config import SlackObjectsConfig, RateTier
7
+ from .rate_limits import DEFAULT_RATE_POLICY, RateLimitPolicy
8
+
9
+
10
+ class SlackApiCaller:
11
+ """
12
+ Wrapper around Slack SDK client to handle rate limiting and API calls.
13
+
14
+ Example: self.api.call(self.client, "users.lookupByEmail", email=email)
15
+ """
16
+ def __init__(self, cfg: SlackObjectsConfig, policy: RateLimitPolicy = DEFAULT_RATE_POLICY):
17
+ self.cfg = cfg
18
+ self.policy = policy
19
+
20
+ def call(self, client, method: str, *, rate_tier: Optional[RateTier] = None, use_json: bool = False, **kwargs) -> dict:
21
+ tier = rate_tier or self.policy.tier_for(method) or self.cfg.default_rate_tier
22
+
23
+ try:
24
+ if use_json:
25
+ resp = client.api_call(method, json=kwargs)
26
+ else:
27
+ resp = client.api_call(method, params=kwargs)
28
+
29
+ data = resp.data if hasattr(resp, "data") else resp
30
+
31
+ # Space out subsequent calls
32
+ time.sleep(float(tier))
33
+ return data
34
+
35
+ except SlackApiError as e:
36
+ # Handle rate limiting properly
37
+ if e.response is not None and e.response.status_code == 429:
38
+ retry_after = int(e.response.headers.get("Retry-After", tier))
39
+ time.sleep(retry_after)
40
+ return self.call(client, method, rate_tier=tier, **kwargs)
41
+
42
+ raise
@@ -0,0 +1,30 @@
1
+ import logging
2
+ from dataclasses import dataclass, field
3
+ from typing import Optional, Any
4
+ from slack_sdk import WebClient
5
+
6
+ from .api_caller import SlackApiCaller
7
+ from .config import SlackObjectsConfig
8
+
9
+
10
+ @dataclass
11
+ class SlackObjectBase:
12
+ """
13
+ Base class that all object helpers inherit from.
14
+
15
+ Holds shared context so every object doesn't need to reinvent plumbing (i.e., don't need to pass cfg, client, logger, and the apicaller).
16
+ Logging is optional; a default package logger will be used if none is provided.
17
+ """
18
+ cfg: SlackObjectsConfig
19
+ client: WebClient
20
+ api: SlackApiCaller
21
+ logger: logging.Logger = field(default_factory=lambda: logging.getLogger("slack-objects")) # logger is guaranteed to exist via default_factory
22
+
23
+ def __post_init__(self) -> None:
24
+ # Required dependencies check
25
+ if self.cfg is None:
26
+ raise ValueError("cfg is required")
27
+ if self.client is None:
28
+ raise ValueError("client is required")
29
+ if self.api is None:
30
+ raise ValueError("api is required")
@@ -0,0 +1,50 @@
1
+ from slack_sdk import WebClient
2
+ import logging
3
+ from typing import Optional
4
+
5
+ from .config import SlackObjectsConfig
6
+ from .api_caller import SlackApiCaller
7
+ from .users import Users
8
+ from .messages import Messages
9
+ from .conversations import Conversations
10
+ from .files import Files
11
+ from .workspaces import Workspaces
12
+ from .idp_groups import IDP_groups
13
+
14
+
15
+ class SlackObjectsClient:
16
+ """
17
+ Central factory / context object.
18
+ Owns config, Slack client, and rate-limited API caller.
19
+ """
20
+
21
+ def __init__(self, cfg: SlackObjectsConfig, logger: logging.Logger | None = None):
22
+ self.cfg = cfg
23
+ self.logger = logger or logging.getLogger("slack-objects")
24
+
25
+ # Prefer bot token for general Web API calls; fall back to user token.
26
+ web_token = cfg.bot_token or cfg.user_token
27
+ if not web_token:
28
+ raise ValueError("SlackObjectsClient requires cfg.bot_token or cfg.user_token.")
29
+
30
+ self.web_client = WebClient(token=web_token)
31
+ self.api = SlackApiCaller(cfg)
32
+
33
+ def users(self, user_id: Optional[str] = None) -> Users:
34
+ base = Users(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
35
+ return base if user_id is None else base.with_user(user_id)
36
+
37
+ def conversations(self) -> Conversations:
38
+ return Conversations(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
39
+
40
+ def files(self) -> Files:
41
+ return Files(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
42
+
43
+ def messages(self) -> Messages:
44
+ return Messages(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
45
+
46
+ def workspaces(self) -> Workspaces:
47
+ return Workspaces(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
48
+
49
+ def idp_groups(self) -> IDP_groups:
50
+ return IDP_groups(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
@@ -0,0 +1,29 @@
1
+ from enum import Enum
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+
5
+ class RateTier(float, Enum):
6
+ """
7
+ Slack API rate-tier backoff defaults (seconds). These are defined to conform to Slack Web API rate limits.
8
+ https://docs.slack.dev/apis/web-api/rate-limits/
9
+ """
10
+
11
+ TIER_1 = 60.0 # 1+ per minute
12
+ TIER_2 = 3.0 # 20+ per minute
13
+ TIER_3 = 1.2 # 50+ per minute
14
+ TIER_4 = 0.6 # 100+ per minute
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class SlackObjectsConfig:
19
+ """
20
+ Configuration settings for slack-objects.
21
+
22
+ Tokens are optional at construction time.
23
+ Individual methods will raise clear errors if a required token is missing.
24
+ """
25
+ bot_token: Optional[str] = None
26
+ user_token: Optional[str] = None
27
+ scim_token: Optional[str] = None
28
+
29
+ default_rate_tier: RateTier = RateTier.TIER_2