hive-nectar 0.0.7__py3-none-any.whl → 0.0.9__py3-none-any.whl

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.

Potentially problematic release.


This version of hive-nectar might be problematic. Click here for more details.

nectar/community.py CHANGED
@@ -2,6 +2,7 @@
2
2
  import json
3
3
  import logging
4
4
  from datetime import date, datetime, time
5
+ from typing import Union
5
6
 
6
7
  from prettytable import PrettyTable
7
8
 
@@ -18,27 +19,23 @@ log = logging.getLogger(__name__)
18
19
 
19
20
 
20
21
  class Community(BlockchainObject):
21
- """This class allows to easily access Community data
22
-
23
- :param str account: Name of the account
24
- :param Steem/Hive blockchain_instance: Hive or Steem
25
- instance
26
- :param bool lazy: Use lazy loading
27
- :param bool full: Obtain all account data including orders, positions,
28
- etc.
29
- :param Hive hive_instance: Hive instance
30
- :param Steem steem_instance: Steem instance
31
- :returns: Account data
32
- :rtype: dictionary
33
- :raises nectar.exceptions.AccountDoesNotExistsException: if account
34
- does not exist
35
-
36
- Instances of this class are dictionaries that come with additional
37
- methods (see below) that allow dealing with an community and its
38
- corresponding functions.
39
-
40
- .. code-block:: python
22
+ """A class representing a Hive community with methods to interact with it.
41
23
 
24
+ This class provides an interface to access and manipulate community data on the Hive blockchain.
25
+ It extends BlockchainObject and provides additional community-specific functionality.
26
+
27
+ Args:
28
+ community: Either a community name (str) or a dictionary containing community data
29
+ observer: Observer account for personalized results (default: "")
30
+ full: If True, fetch full community data (default: True)
31
+ lazy: If True, use lazy loading (default: False)
32
+ blockchain_instance: Hive or Steem instance for blockchain access
33
+ **kwargs: Additional arguments including 'hive_instance' or 'steem_instance'
34
+
35
+ Attributes:
36
+ type_id (int): Type identifier for blockchain objects (2 for communities)
37
+
38
+ Example:
42
39
  >>> from nectar.community import Community
43
40
  >>> from nectar import Hive
44
41
  >>> from nectar.nodelist import NodeList
@@ -48,28 +45,32 @@ class Community(BlockchainObject):
48
45
  >>> community = Community("hive-139531", blockchain_instance=stm)
49
46
  >>> print(community)
50
47
  <Community hive-139531>
51
- >>> print(community.balances) # doctest: +SKIP
52
-
53
- .. note:: This class comes with its own caching function to reduce the
54
- load on the API server. Instances of this class can be
55
- refreshed with ``Community.refresh()``. The cache can be
56
- cleared with ``Community.clear_cache()``
57
48
 
49
+ Note:
50
+ This class includes caching to reduce API server load. Use refresh() to update
51
+ the data and clear_cache() to clear the cache.
58
52
  """
59
53
 
60
54
  type_id = 2
61
55
 
62
56
  def __init__(
63
- self, community, observer="", full=True, lazy=False, blockchain_instance=None, **kwargs
64
- ):
65
- """Initialize an community
66
-
67
- :param str community: Name of the community
68
- :param Hive/Steem blockchain_instance: Hive/Steem
69
- instance
70
- :param bool lazy: Use lazy loading
71
- :param bool full: Obtain all community data including orders, positions,
72
- etc.
57
+ self,
58
+ community: Union[str, dict],
59
+ observer: str = "",
60
+ full: bool = True,
61
+ lazy: bool = False,
62
+ blockchain_instance=None,
63
+ **kwargs,
64
+ ) -> None:
65
+ """Initialize a Community instance.
66
+
67
+ Args:
68
+ community: Either a community name (str) or a dictionary containing community data
69
+ observer: Observer account for personalized results (default: "")
70
+ full: If True, fetch full community data (default: True)
71
+ lazy: If True, use lazy loading (default: False)
72
+ blockchain_instance: Hive or Steem instance for blockchain access
73
+ **kwargs: Additional arguments including 'hive_instance' or 'steem_instance'
73
74
  """
74
75
  self.full = full
75
76
  self.lazy = lazy
@@ -86,8 +87,16 @@ class Community(BlockchainObject):
86
87
  community, lazy=lazy, full=full, id_item="name", blockchain_instance=blockchain_instance
87
88
  )
88
89
 
89
- def refresh(self):
90
- """Refresh/Obtain an community's data from the API server"""
90
+ def refresh(self) -> None:
91
+ """Refresh the community's data from the blockchain.
92
+
93
+ This method updates the community's data by fetching the latest information
94
+ from the blockchain. It raises an exception if the community doesn't exist.
95
+
96
+ Raises:
97
+ AccountDoesNotExistsException: If the community doesn't exist on the blockchain
98
+ OfflineHasNoRPCException: If not connected to the blockchain
99
+ """
91
100
  if not self.blockchain.is_connected():
92
101
  return
93
102
  self.blockchain.rpc.set_next_node_on_empty_reply(True)
@@ -109,208 +118,402 @@ class Community(BlockchainObject):
109
118
  blockchain_instance=self.blockchain,
110
119
  )
111
120
 
112
- def _parse_json_data(self, community):
113
- parse_int = [
121
+ def _parse_json_data(self, community: dict) -> dict:
122
+ """Parse and convert community JSON data into proper Python types.
123
+
124
+ This internal method converts string representations of numbers to integers
125
+ and parses date strings into datetime objects with timezone information.
126
+
127
+ Args:
128
+ community: Dictionary containing raw community data from the API
129
+
130
+ Returns:
131
+ dict: Processed community data with proper Python types
132
+ """
133
+ # Convert string numbers to integers
134
+ int_fields = [
114
135
  "sum_pending",
115
136
  "subscribers",
116
137
  "num_pending",
117
138
  "num_authors",
118
139
  ]
119
- for p in parse_int:
120
- if p in community and isinstance(community.get(p), str):
121
- community[p] = int(community.get(p, 0))
122
- parse_times = ["created_at"]
123
- for p in parse_times:
124
- if p in community and isinstance(community.get(p), str):
125
- community[p] = addTzInfo(
126
- datetime.strptime(community.get(p, "1970-01-01 00:00:00"), "%Y-%m-%d %H:%M:%S")
140
+ for field in int_fields:
141
+ if field in community and isinstance(community.get(field), str):
142
+ community[field] = int(community.get(field, 0))
143
+
144
+ # Parse date strings into datetime objects
145
+ date_fields = ["created_at"]
146
+ for field in date_fields:
147
+ if field in community and isinstance(community.get(field), str):
148
+ community[field] = addTzInfo(
149
+ datetime.strptime(
150
+ community.get(field, "1970-01-01 00:00:00"), "%Y-%m-%d %H:%M:%S"
151
+ )
127
152
  )
153
+
128
154
  return community
129
155
 
130
- def json(self):
156
+ def json(self) -> dict:
157
+ """Convert the community data to a JSON-serializable dictionary.
158
+
159
+ This method prepares the community data for JSON serialization by converting
160
+ non-JSON-serializable types (like datetime objects) to strings.
161
+
162
+ Returns:
163
+ dict: A dictionary containing the community data in a JSON-serializable format
164
+ """
131
165
  output = self.copy()
132
- parse_int = [
166
+
167
+ # Convert integer fields to strings for JSON serialization
168
+ int_fields = [
133
169
  "sum_pending",
134
170
  "subscribers",
135
171
  "num_pending",
136
172
  "num_authors",
137
173
  ]
138
- parse_int_without_zero = []
139
- for p in parse_int:
140
- if p in output and isinstance(output[p], int):
141
- output[p] = str(output[p])
142
- for p in parse_int_without_zero:
143
- if p in output and isinstance(output[p], int) and output[p] != 0:
144
- output[p] = str(output[p])
145
-
146
- parse_times = [
147
- "created_at",
148
- ]
149
- for p in parse_times:
150
- if p in output:
151
- p_date = output.get(p, datetime(1970, 1, 1, 0, 0))
152
- if isinstance(p_date, (datetime, date, time)):
153
- output[p] = formatTimeString(p_date).replace("T", " ")
174
+
175
+ # Fields that should only be converted if non-zero
176
+ int_non_zero_fields = []
177
+
178
+ # Convert regular integer fields
179
+ for field in int_fields:
180
+ if field in output and isinstance(output[field], int):
181
+ output[field] = str(output[field])
182
+
183
+ # Convert non-zero integer fields
184
+ for field in int_non_zero_fields:
185
+ if field in output and isinstance(output[field], int) and output[field] != 0:
186
+ output[field] = str(output[field])
187
+
188
+ # Convert datetime fields to ISO format strings
189
+ date_fields = ["created_at"]
190
+ for field in date_fields:
191
+ if field in output:
192
+ date_val = output.get(field, datetime(1970, 1, 1, 0, 0))
193
+ if isinstance(date_val, (datetime, date, time)):
194
+ output[field] = formatTimeString(date_val).replace("T", " ")
154
195
  else:
155
- output[p] = p_date
196
+ output[field] = date_val
156
197
  return json.loads(str(json.dumps(output)))
157
198
 
158
- def get_community_roles(self):
159
- """Lists community roles"""
199
+ def get_community_roles(self, limit: int = 100, last: str = None) -> list:
200
+ """Lists community roles
201
+
202
+ Args:
203
+ limit: Maximum number of roles to return (default: 100)
204
+ last: Account name of the last role from previous page for pagination
205
+
206
+ Returns:
207
+ list: List of community roles
208
+
209
+ Raises:
210
+ OfflineHasNoRPCException: If not connected to the blockchain
211
+ """
160
212
  community = self["name"]
161
213
  if not self.blockchain.is_connected():
162
214
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
215
+
216
+ params = {"community": community, "limit": limit}
217
+ if last is not None:
218
+ params["last"] = last
219
+
163
220
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
164
- return self.blockchain.rpc.list_community_roles({"community": community}, api="bridge")
221
+ return self.blockchain.rpc.list_community_roles(params, api="bridge")
222
+
223
+ def get_subscribers(self, limit: int = 100, last: str = None) -> list:
224
+ """Returns subscribers
225
+
226
+ Args:
227
+ limit: Maximum number of subscribers to return (default: 100)
228
+ last: Account name of the last subscriber from previous page for pagination
165
229
 
166
- def get_subscribers(self):
167
- """Returns subscribers"""
230
+ Returns:
231
+ list: List of subscribers
232
+
233
+ Raises:
234
+ OfflineHasNoRPCException: If not connected to the blockchain
235
+ """
168
236
  community = self["name"]
169
237
  if not self.blockchain.is_connected():
170
238
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
239
+
240
+ params = {"community": community, "limit": limit}
241
+ if last is not None:
242
+ params["last"] = last
243
+
171
244
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
172
- return self.blockchain.rpc.list_subscribers({"community": community}, api="bridge")
245
+ return self.blockchain.rpc.list_subscribers(params, api="bridge")
246
+
247
+ def get_activities(self, limit: int = 100, last_id: str = None) -> list:
248
+ """Returns community activity
249
+
250
+ Args:
251
+ limit: Maximum number of activities to return (default: 100)
252
+ last_id: ID of the last activity from previous page for pagination
253
+
254
+ Returns:
255
+ list: List of community activities
173
256
 
174
- def get_activities(self, limit=100, last_id=None):
175
- """Returns community activity"""
257
+ Raises:
258
+ OfflineHasNoRPCException: If not connected to the blockchain
259
+ """
176
260
  community = self["name"]
177
261
  if not self.blockchain.is_connected():
178
262
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
263
+
264
+ params = {"account": community, "limit": limit}
265
+ if last_id is not None:
266
+ params["last_id"] = last_id
267
+
179
268
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
180
- return self.blockchain.rpc.account_notifications(
181
- {"account": community, "limit": limit, "last_id": last_id}, api="bridge"
182
- )
269
+ return self.blockchain.rpc.account_notifications(params, api="bridge")
183
270
 
184
271
  def get_ranked_posts(
185
- self, observer=None, limit=100, start_author=None, start_permlink=None, sort="created"
186
- ):
187
- """Returns community post"""
272
+ self,
273
+ observer: str = None,
274
+ limit: int = 100,
275
+ start_author: str = None,
276
+ start_permlink: str = None,
277
+ sort: str = "created",
278
+ ) -> list:
279
+ """Returns community posts
280
+
281
+ Args:
282
+ observer: Account name of the observer (optional)
283
+ limit: Maximum number of posts to return (default: 100)
284
+ start_author: Author of the post to start from for pagination (optional)
285
+ start_permlink: Permlink of the post to start from for pagination (optional)
286
+ sort: Sort order (default: "created")
287
+
288
+ Returns:
289
+ list: List of community posts
290
+
291
+ Raises:
292
+ OfflineHasNoRPCException: If not connected to the blockchain
293
+ """
188
294
  community = self["name"]
189
295
  if not self.blockchain.is_connected():
190
296
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
297
+
298
+ params = {"tag": community, "limit": limit, "sort": sort}
299
+
300
+ if observer is not None:
301
+ params["observer"] = observer
302
+ if start_author is not None:
303
+ params["start_author"] = start_author
304
+ if start_permlink is not None:
305
+ params["start_permlink"] = start_permlink
306
+
191
307
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
192
- return self.blockchain.rpc.get_ranked_posts(
193
- {
194
- "tag": community,
195
- "observer": observer,
196
- "limit": limit,
197
- "start_author": start_author,
198
- "start_permlink": start_permlink,
199
- "sort": sort,
200
- },
201
- api="bridge",
202
- )
308
+ return self.blockchain.rpc.get_ranked_posts(params, api="bridge")
309
+
310
+ def set_role(self, account: str, role: str, mod_account: str) -> dict:
311
+ """Set role for a given account in the community.
203
312
 
204
- def set_role(self, account, role, mod_account):
205
- """Set role for a given account
313
+ Args:
314
+ account: Account name to set the role for
315
+ role: Role to assign (member, mod, admin, owner, or guest)
316
+ mod_account: Account name of the moderator performing this action (must be mod or higher)
206
317
 
207
- :param str account: Set role of this account
208
- :param str role: Can be member, mod, admin, owner, guest
209
- :param str mod_account: Account who broadcast this, (mods or higher)
318
+ Returns:
319
+ dict: Transaction result
210
320
 
321
+ Raises:
322
+ OfflineHasNoRPCException: If not connected to the blockchain
323
+ ValueError: If role is not one of the allowed values
211
324
  """
325
+ valid_roles = {"member", "mod", "admin", "owner", "guest"}
326
+ if role.lower() not in valid_roles:
327
+ raise ValueError(f"Invalid role. Must be one of: {', '.join(valid_roles)}")
328
+
212
329
  community = self["name"]
213
330
  if not self.blockchain.is_connected():
214
331
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
332
+
215
333
  json_body = [
216
334
  "setRole",
217
335
  {
218
336
  "community": community,
219
337
  "account": account,
220
- "role": role,
338
+ "role": role.lower(),
221
339
  },
222
340
  ]
223
341
  return self.blockchain.custom_json(
224
342
  "community", json_body, required_posting_auths=[mod_account]
225
343
  )
226
344
 
227
- def set_user_title(self, account, title, mod_account):
228
- """Set title for a given account
345
+ def set_user_title(self, account: str, title: str, mod_account: str) -> dict:
346
+ """Set the title for a given account in the community.
347
+
348
+ Args:
349
+ account: Account name to set the title for
350
+ title: Title to assign to the account
351
+ mod_account: Account name of the moderator performing this action (must be mod or higher)
229
352
 
230
- :param str account: Set role of this account
231
- :param str title: Title
232
- :param str mod_account: Account who broadcast this, (mods or higher)
353
+ Returns:
354
+ dict: Transaction result
233
355
 
356
+ Raises:
357
+ OfflineHasNoRPCException: If not connected to the blockchain
358
+ ValueError: If account or title is empty
234
359
  """
360
+ if not account or not isinstance(account, str):
361
+ raise ValueError("Account must be a non-empty string")
362
+
363
+ if not title or not isinstance(title, str):
364
+ raise ValueError("Title must be a non-empty string")
365
+
235
366
  community = self["name"]
236
367
  if not self.blockchain.is_connected():
237
368
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
369
+
238
370
  json_body = [
239
371
  "setUserTitle",
240
372
  {
241
373
  "community": community,
242
374
  "account": account,
243
- "title": title,
375
+ "title": title.strip(),
244
376
  },
245
377
  ]
246
378
  return self.blockchain.custom_json(
247
379
  "community", json_body, required_posting_auths=[mod_account]
248
380
  )
249
381
 
250
- def mute_post(self, account, permlink, notes, mod_account):
251
- """Mutes a post
382
+ def mute_post(self, account: str, permlink: str, notes: str, mod_account: str) -> dict:
383
+ """Mutes a post in the community.
384
+
385
+ Args:
386
+ account: Author of the post to mute
387
+ permlink: Permlink of the post to mute
388
+ notes: Reason for muting the post
389
+ mod_account: Account name of the moderator performing this action (must be mod or higher)
252
390
 
253
- :param str account: Set role of this account
254
- :param str permlink: permlink
255
- :param str notes: permlink
256
- :param str mod_account: Account who broadcast this, (mods or higher)
391
+ Returns:
392
+ dict: Transaction result
257
393
 
394
+ Raises:
395
+ OfflineHasNoRPCException: If not connected to the blockchain
396
+ ValueError: If any required parameter is invalid
258
397
  """
398
+ if not account or not isinstance(account, str):
399
+ raise ValueError("Account must be a non-empty string")
400
+ if not permlink or not isinstance(permlink, str):
401
+ raise ValueError("Permlink must be a non-empty string")
402
+ if not isinstance(notes, str):
403
+ raise ValueError("Notes must be a string")
404
+ if not mod_account or not isinstance(mod_account, str):
405
+ raise ValueError("Moderator account must be a non-empty string")
406
+
259
407
  community = self["name"]
260
408
  if not self.blockchain.is_connected():
261
409
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
410
+
262
411
  json_body = [
263
412
  "mutePost",
264
- {"community": community, "account": account, "permlink": permlink, "notes": notes},
413
+ {
414
+ "community": community,
415
+ "account": account,
416
+ "permlink": permlink,
417
+ "notes": notes.strip(),
418
+ },
265
419
  ]
266
420
  return self.blockchain.custom_json(
267
421
  "community", json_body, required_posting_auths=[mod_account]
268
422
  )
269
423
 
270
- def unmute_post(self, account, permlink, notes, mod_account):
271
- """Unmute a post
424
+ def unmute_post(self, account: str, permlink: str, notes: str, mod_account: str) -> dict:
425
+ """Unmute a previously muted post in the community.
426
+
427
+ Args:
428
+ account: Author of the post to unmute
429
+ permlink: Permlink of the post to unmute
430
+ notes: Reason for unmuting the post
431
+ mod_account: Account name of the moderator performing this action (must be mod or higher)
272
432
 
273
- :param str account: post author
274
- :param str permlink: permlink
275
- :param str notes: notes
276
- :param str mod_account: Account who broadcast this, (mods or higher)
433
+ Returns:
434
+ dict: Transaction result
277
435
 
436
+ Raises:
437
+ OfflineHasNoRPCException: If not connected to the blockchain
438
+ ValueError: If any required parameter is invalid
278
439
  """
440
+ if not account or not isinstance(account, str):
441
+ raise ValueError("Account must be a non-empty string")
442
+ if not permlink or not isinstance(permlink, str):
443
+ raise ValueError("Permlink must be a non-empty string")
444
+ if not isinstance(notes, str):
445
+ raise ValueError("Notes must be a string")
446
+ if not mod_account or not isinstance(mod_account, str):
447
+ raise ValueError("Moderator account must be a non-empty string")
448
+
279
449
  community = self["name"]
280
450
  if not self.blockchain.is_connected():
281
451
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
452
+
282
453
  json_body = [
283
454
  "unmutePost",
284
- {"community": community, "account": account, "permlink": permlink, "notes": notes},
455
+ {
456
+ "community": community,
457
+ "account": account,
458
+ "permlink": permlink,
459
+ "notes": notes.strip(),
460
+ },
285
461
  ]
286
462
  return self.blockchain.custom_json(
287
463
  "community", json_body, required_posting_auths=[mod_account]
288
464
  )
289
465
 
290
- def update_props(self, title, about, is_nsfw, description, flag_text, admin_account):
291
- """Updates the community properties
292
-
293
- :param str title: Community title
294
- :param str about: about
295
- :param bool is_nsfw: is_nsfw
296
- :param str description: description
297
- :param str flag_text: flag_text
298
- :param str admin_account: Account who broadcast this, (admin or higher)
299
-
466
+ def update_props(
467
+ self,
468
+ title: str,
469
+ about: str,
470
+ is_nsfw: bool,
471
+ description: str,
472
+ flag_text: str,
473
+ admin_account: str,
474
+ ) -> dict:
475
+ """Update community properties.
476
+
477
+ Args:
478
+ title: New title for the community (must be non-empty)
479
+ about: Brief description of the community
480
+ is_nsfw: Whether the community contains NSFW content
481
+ description: Detailed description of the community
482
+ flag_text: Text shown when flagging content in this community
483
+ admin_account: Account name of the admin performing this action
484
+
485
+ Returns:
486
+ dict: Transaction result
487
+
488
+ Raises:
489
+ OfflineHasNoRPCException: If not connected to the blockchain
490
+ ValueError: If any required parameter is invalid
300
491
  """
492
+ if not title or not isinstance(title, str):
493
+ raise ValueError("Title must be a non-empty string")
494
+ if not isinstance(about, str):
495
+ about = ""
496
+ if not isinstance(description, str):
497
+ description = ""
498
+ if not isinstance(flag_text, str):
499
+ flag_text = ""
500
+ if not admin_account or not isinstance(admin_account, str):
501
+ raise ValueError("Admin account must be a non-empty string")
502
+
301
503
  community = self["name"]
302
504
  if not self.blockchain.is_connected():
303
505
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
506
+
304
507
  json_body = [
305
508
  "updateProps",
306
509
  {
307
510
  "community": community,
308
511
  "props": {
309
- "title": title,
310
- "about": about,
311
- "is_nsfw": is_nsfw,
312
- "description": description,
313
- "flag_text": flag_text,
512
+ "title": title.strip(),
513
+ "about": about.strip(),
514
+ "is_nsfw": bool(is_nsfw),
515
+ "description": description.strip(),
516
+ "flag_text": flag_text.strip(),
314
517
  },
315
518
  },
316
519
  ]
@@ -318,15 +521,29 @@ class Community(BlockchainObject):
318
521
  "community", json_body, required_posting_auths=[admin_account]
319
522
  )
320
523
 
321
- def subscribe(self, account):
322
- """subscribe to a community
524
+ def subscribe(self, account: str) -> dict:
525
+ """Subscribe an account to this community.
526
+
527
+ The account that calls this method will be subscribed to the community.
528
+ The same account must be used to sign the transaction.
529
+
530
+ Args:
531
+ account: Account name that wants to subscribe to the community
323
532
 
324
- :param str account: account who suscribe to the community (is also broadcasting the custom_json)
533
+ Returns:
534
+ dict: Transaction result
325
535
 
536
+ Raises:
537
+ OfflineHasNoRPCException: If not connected to the blockchain
538
+ ValueError: If account is invalid
326
539
  """
540
+ if not account or not isinstance(account, str):
541
+ raise ValueError("Account must be a non-empty string")
542
+
327
543
  community = self["name"]
328
544
  if not self.blockchain.is_connected():
329
545
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
546
+
330
547
  json_body = [
331
548
  "subscribe",
332
549
  {
@@ -335,17 +552,35 @@ class Community(BlockchainObject):
335
552
  ]
336
553
  return self.blockchain.custom_json("community", json_body, required_posting_auths=[account])
337
554
 
338
- def pin_post(self, account, permlink, mod_account):
339
- """Stickes a post to the top of a community
555
+ def pin_post(self, account: str, permlink: str, mod_account: str) -> dict:
556
+ """Pin a post to the top of the community feed.
557
+
558
+ This method allows community moderators to pin a specific post to the top of the
559
+ community's feed. The post will remain pinned until it is manually unpinned.
560
+
561
+ Args:
562
+ account: Author of the post to pin
563
+ permlink: Permlink of the post to pin
564
+ mod_account: Account name of the moderator performing this action (must be mod or higher)
340
565
 
341
- :param str account: post author
342
- :param str permlink: permlink
343
- :param str mod_account: Account who broadcast this, (mods or higher)
566
+ Returns:
567
+ dict: Transaction result
344
568
 
569
+ Raises:
570
+ OfflineHasNoRPCException: If not connected to the blockchain
571
+ ValueError: If any required parameter is invalid
345
572
  """
573
+ if not account or not isinstance(account, str):
574
+ raise ValueError("Account must be a non-empty string")
575
+ if not permlink or not isinstance(permlink, str):
576
+ raise ValueError("Permlink must be a non-empty string")
577
+ if not mod_account or not isinstance(mod_account, str):
578
+ raise ValueError("Moderator account must be a non-empty string")
579
+
346
580
  community = self["name"]
347
581
  if not self.blockchain.is_connected():
348
582
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
583
+
349
584
  json_body = [
350
585
  "pinPost",
351
586
  {
@@ -358,15 +593,29 @@ class Community(BlockchainObject):
358
593
  "community", json_body, required_posting_auths=[mod_account]
359
594
  )
360
595
 
361
- def unsubscribe(self, account):
362
- """unsubscribe a community
596
+ def unsubscribe(self, account: str) -> dict:
597
+ """Unsubscribe an account from this community.
598
+
599
+ The account that calls this method will be unsubscribed from the community.
600
+ The same account must be used to sign the transaction.
363
601
 
364
- :param str account: account who unsuscribe to the community (is also broadcasting the custom_json)
602
+ Args:
603
+ account: Account name that wants to unsubscribe from the community
365
604
 
605
+ Returns:
606
+ dict: Transaction result
607
+
608
+ Raises:
609
+ OfflineHasNoRPCException: If not connected to the blockchain
610
+ ValueError: If account is invalid
366
611
  """
612
+ if not account or not isinstance(account, str):
613
+ raise ValueError("Account must be a non-empty string")
614
+
367
615
  community = self["name"]
368
616
  if not self.blockchain.is_connected():
369
617
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
618
+
370
619
  json_body = [
371
620
  "unsubscribe",
372
621
  {
@@ -375,17 +624,35 @@ class Community(BlockchainObject):
375
624
  ]
376
625
  return self.blockchain.custom_json("community", json_body, required_posting_auths=[account])
377
626
 
378
- def unpin_post(self, account, permlink, mod_account):
379
- """Removes a post from the top of a community
627
+ def unpin_post(self, account: str, permlink: str, mod_account: str) -> dict:
628
+ """Remove a post from being pinned at the top of the community feed.
629
+
630
+ This method allows community moderators to unpin a previously pinned post.
631
+ After unpinning, the post will return to its normal position in the feed.
380
632
 
381
- :param str account: post author
382
- :param str permlink: permlink
383
- :param str mod_account: Account who broadcast this, (mods or higher)
633
+ Args:
634
+ account: Author of the post to unpin
635
+ permlink: Permlink of the post to unpin
636
+ mod_account: Account name of the moderator performing this action (must be mod or higher)
384
637
 
638
+ Returns:
639
+ dict: Transaction result
640
+
641
+ Raises:
642
+ OfflineHasNoRPCException: If not connected to the blockchain
643
+ ValueError: If any required parameter is invalid
385
644
  """
645
+ if not account or not isinstance(account, str):
646
+ raise ValueError("Account must be a non-empty string")
647
+ if not permlink or not isinstance(permlink, str):
648
+ raise ValueError("Permlink must be a non-empty string")
649
+ if not mod_account or not isinstance(mod_account, str):
650
+ raise ValueError("Moderator account must be a non-empty string")
651
+
386
652
  community = self["name"]
387
653
  if not self.blockchain.is_connected():
388
654
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
655
+
389
656
  json_body = [
390
657
  "unpinPost",
391
658
  {
@@ -398,20 +665,47 @@ class Community(BlockchainObject):
398
665
  "community", json_body, required_posting_auths=[mod_account]
399
666
  )
400
667
 
401
- def flag_post(self, account, permlink, notes, reporter):
402
- """Suggest a post for the review queue
668
+ def flag_post(self, account: str, permlink: str, notes: str, reporter: str) -> dict:
669
+ """Report a post to the community moderators for review.
403
670
 
404
- :param str account: post author
405
- :param str permlink: permlink
406
- :param str notes: notes
407
- :param str reporter: Account who broadcast this
671
+ This method allows community members to flag posts that may violate
672
+ community guidelines. The post will be added to the community's
673
+ review queue for moderators to evaluate.
674
+
675
+ Args:
676
+ account: Author of the post being reported
677
+ permlink: Permlink of the post being reported
678
+ notes: Explanation of why the post is being reported
679
+ reporter: Account name of the user reporting the post
680
+
681
+ Returns:
682
+ dict: Transaction result
683
+
684
+ Raises:
685
+ OfflineHasNoRPCException: If not connected to the blockchain
686
+ ValueError: If any required parameter is invalid
408
687
  """
688
+ if not account or not isinstance(account, str):
689
+ raise ValueError("Account must be a non-empty string")
690
+ if not permlink or not isinstance(permlink, str):
691
+ raise ValueError("Permlink must be a non-empty string")
692
+ if not notes or not isinstance(notes, str):
693
+ raise ValueError("Notes must be a string")
694
+ if not reporter or not isinstance(reporter, str):
695
+ raise ValueError("Reporter account must be a non-empty string")
696
+
409
697
  community = self["name"]
410
698
  if not self.blockchain.is_connected():
411
699
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
700
+
412
701
  json_body = [
413
702
  "flagPost",
414
- {"community": community, "account": account, "permlink": permlink, "notes": notes},
703
+ {
704
+ "community": community,
705
+ "account": account,
706
+ "permlink": permlink,
707
+ "notes": notes.strip(),
708
+ },
415
709
  ]
416
710
  return self.blockchain.custom_json(
417
711
  "community", json_body, required_posting_auths=[reporter]
@@ -419,7 +713,21 @@ class Community(BlockchainObject):
419
713
 
420
714
 
421
715
  class CommunityObject(list):
422
- def printAsTable(self):
716
+ """A list-like container for Community objects with additional utility methods."""
717
+
718
+ def printAsTable(self) -> None:
719
+ """Print a formatted table of communities with key metrics.
720
+
721
+ The table includes the following columns:
722
+ - Nr.: Sequential number
723
+ - Name: Community name
724
+ - Title: Community title
725
+ - lang: Language code
726
+ - subscribers: Number of subscribers
727
+ - sum_pending: Sum of pending payouts
728
+ - num_pending: Number of pending posts
729
+ - num_authors: Number of unique authors
730
+ """
423
731
  t = PrettyTable(
424
732
  [
425
733
  "Nr.",
@@ -452,26 +760,45 @@ class CommunityObject(list):
452
760
 
453
761
 
454
762
  class Communities(CommunityObject):
455
- """Obtain a list of communities
456
-
457
- :param list name_list: list of accounts to fetch
458
- :param int batch_limit: (optional) maximum number of accounts
459
- to fetch per call, defaults to 100
460
- :param Steem/Hive blockchain_instance: Steem() or Hive() instance to use when
461
- accessing a RPCcreator = Account(creator, blockchain_instance=self)
763
+ """A list of communities with additional querying capabilities.
764
+
765
+ This class extends CommunityObject to provide methods for fetching and
766
+ searching communities from the blockchain.
767
+
768
+ Args:
769
+ sort: Sort order for communities (default: "rank")
770
+ observer: Observer account for personalized results (optional)
771
+ last: Last community name for pagination (optional)
772
+ limit: Maximum number of communities to fetch (default: 100)
773
+ lazy: If True, use lazy loading (default: False)
774
+ full: If True, fetch full community data (default: True)
775
+ blockchain_instance: Hive or Steem instance to use for blockchain access
776
+ **kwargs: Additional arguments including 'steem_instance' or 'hive_instance'
462
777
  """
463
778
 
464
779
  def __init__(
465
780
  self,
466
- sort="rank",
467
- observer=None,
468
- last=None,
469
- limit=100,
470
- lazy=False,
471
- full=True,
781
+ sort: str = "rank",
782
+ observer: str = None,
783
+ last: str = None,
784
+ limit: int = 100,
785
+ lazy: bool = False,
786
+ full: bool = True,
472
787
  blockchain_instance=None,
473
788
  **kwargs,
474
- ):
789
+ ) -> None:
790
+ """Initialize the Communities list with the given parameters.
791
+
792
+ Args:
793
+ sort: Sort order for communities (default: "rank")
794
+ observer: Observer account for personalized results (optional)
795
+ last: Last community name for pagination (optional)
796
+ limit: Maximum number of communities to fetch (default: 100)
797
+ lazy: If True, use lazy loading (default: False)
798
+ full: If True, fetch full community data (default: True)
799
+ blockchain_instance: Hive or Steem instance to use for blockchain access
800
+ **kwargs: Additional arguments including 'steem_instance' or 'hive_instance'
801
+ """
475
802
  if blockchain_instance is None:
476
803
  if kwargs.get("steem_instance"):
477
804
  blockchain_instance = kwargs["steem_instance"]
@@ -481,32 +808,52 @@ class Communities(CommunityObject):
481
808
 
482
809
  if not self.blockchain.is_connected():
483
810
  return
811
+
484
812
  communities = []
485
813
  community_cnt = 0
486
- batch_limit = 100
487
- if batch_limit > limit:
488
- batch_limit = limit
814
+ batch_limit = min(100, limit) # Ensure we don't exceed the limit
489
815
 
490
816
  while community_cnt < limit:
491
817
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
492
- communities += self.blockchain.rpc.list_communities(
818
+ batch = self.blockchain.rpc.list_communities(
493
819
  {"sort": sort, "observer": observer, "last": last, "limit": batch_limit},
494
820
  api="bridge",
495
821
  )
496
- community_cnt += batch_limit
822
+ if not batch: # No more communities to fetch
823
+ break
824
+
825
+ communities.extend(batch)
826
+ community_cnt += len(batch)
497
827
  last = communities[-1]["name"]
498
828
 
829
+ # Adjust batch size for the next iteration if needed
830
+ if community_cnt + batch_limit > limit:
831
+ batch_limit = limit - community_cnt
832
+
499
833
  super(Communities, self).__init__(
500
834
  [
501
835
  Community(x, lazy=lazy, full=full, blockchain_instance=self.blockchain)
502
- for x in communities
836
+ for x in communities[:limit] # Ensure we don't exceed the limit
503
837
  ]
504
838
  )
505
839
 
506
- def search_title(self, title):
507
- """Returns all communites which have a title similar to title"""
840
+ def search_title(self, title: str) -> "CommunityObject":
841
+ """Search for communities with titles containing the given string.
842
+
843
+ The search is case-insensitive.
844
+
845
+ Args:
846
+ title: Text to search for in community titles
847
+
848
+ Returns:
849
+ CommunityObject: A new CommunityObject containing matching communities
850
+ """
851
+ if not title or not isinstance(title, str):
852
+ raise ValueError("Title must be a non-empty string")
853
+
508
854
  ret = CommunityObject()
855
+ title_lower = title.lower()
509
856
  for community in self:
510
- if title.lower() in community["title"].lower():
857
+ if title_lower in community["title"].lower():
511
858
  ret.append(community)
512
859
  return ret