hive-nectar 0.0.6__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.

Files changed (56) hide show
  1. {hive_nectar-0.0.6.dist-info → hive_nectar-0.0.9.dist-info}/METADATA +5 -3
  2. hive_nectar-0.0.9.dist-info/RECORD +91 -0
  3. nectar/__init__.py +1 -0
  4. nectar/account.py +44 -48
  5. nectar/amount.py +6 -11
  6. nectar/block.py +8 -9
  7. nectar/blockchain.py +4 -5
  8. nectar/blockchaininstance.py +4 -4
  9. nectar/blockchainobject.py +5 -6
  10. nectar/blurt.py +3 -4
  11. nectar/cli.py +14 -14
  12. nectar/comment.py +10 -11
  13. nectar/community.py +527 -181
  14. nectar/conveyor.py +3 -4
  15. nectar/exceptions.py +30 -24
  16. nectar/hive.py +3 -4
  17. nectar/hivesigner.py +2 -2
  18. nectar/imageuploader.py +2 -3
  19. nectar/nodelist.py +83 -7
  20. nectar/price.py +6 -13
  21. nectar/rc.py +1 -2
  22. nectar/steem.py +3 -4
  23. nectar/storage.py +3 -4
  24. nectar/transactionbuilder.py +12 -3
  25. nectar/version.py +1 -1
  26. nectar/vote.py +8 -9
  27. nectar/wallet.py +1 -1
  28. nectarapi/__init__.py +1 -0
  29. nectarapi/exceptions.py +20 -14
  30. nectarapi/version.py +1 -1
  31. nectarbase/__init__.py +1 -0
  32. nectarbase/ledgertransactions.py +2 -3
  33. nectarbase/memo.py +9 -10
  34. nectarbase/objects.py +4 -5
  35. nectarbase/operations.py +3 -7
  36. nectarbase/version.py +1 -1
  37. nectargraphenebase/__init__.py +1 -1
  38. nectargraphenebase/account.py +16 -37
  39. nectargraphenebase/base58.py +5 -8
  40. nectargraphenebase/bip32.py +5 -11
  41. nectargraphenebase/bip38.py +6 -7
  42. nectargraphenebase/ecdsasig.py +32 -37
  43. nectargraphenebase/objects.py +6 -7
  44. nectargraphenebase/operations.py +2 -0
  45. nectargraphenebase/signedtransactions.py +10 -9
  46. nectargraphenebase/types.py +9 -19
  47. nectargraphenebase/unsignedtransactions.py +21 -28
  48. nectargraphenebase/version.py +1 -1
  49. nectarstorage/__init__.py +21 -1
  50. nectarstorage/masterpassword.py +2 -3
  51. nectarstorage/sqlite.py +1 -1
  52. hive_nectar-0.0.6.dist-info/RECORD +0 -92
  53. nectargraphenebase/py23.py +0 -38
  54. {hive_nectar-0.0.6.dist-info → hive_nectar-0.0.9.dist-info}/WHEEL +0 -0
  55. {hive_nectar-0.0.6.dist-info → hive_nectar-0.0.9.dist-info}/entry_points.txt +0 -0
  56. {hive_nectar-0.0.6.dist-info → hive_nectar-0.0.9.dist-info}/licenses/LICENSE.txt +0 -0
nectar/community.py CHANGED
@@ -2,11 +2,11 @@
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
 
8
9
  from nectar.instance import shared_blockchain_instance
9
- from nectargraphenebase.py23 import integer_types, string_types
10
10
 
11
11
  from .blockchainobject import BlockchainObject
12
12
  from .exceptions import AccountDoesNotExistsException, OfflineHasNoRPCException
@@ -19,27 +19,23 @@ log = logging.getLogger(__name__)
19
19
 
20
20
 
21
21
  class Community(BlockchainObject):
22
- """This class allows to easily access Community data
23
-
24
- :param str account: Name of the account
25
- :param Steem/Hive blockchain_instance: Hive or Steem
26
- instance
27
- :param bool lazy: Use lazy loading
28
- :param bool full: Obtain all account data including orders, positions,
29
- etc.
30
- :param Hive hive_instance: Hive instance
31
- :param Steem steem_instance: Steem instance
32
- :returns: Account data
33
- :rtype: dictionary
34
- :raises nectar.exceptions.AccountDoesNotExistsException: if account
35
- does not exist
36
-
37
- Instances of this class are dictionaries that come with additional
38
- methods (see below) that allow dealing with an community and its
39
- corresponding functions.
40
-
41
- .. code-block:: python
22
+ """A class representing a Hive community with methods to interact with it.
42
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:
43
39
  >>> from nectar.community import Community
44
40
  >>> from nectar import Hive
45
41
  >>> from nectar.nodelist import NodeList
@@ -49,28 +45,32 @@ class Community(BlockchainObject):
49
45
  >>> community = Community("hive-139531", blockchain_instance=stm)
50
46
  >>> print(community)
51
47
  <Community hive-139531>
52
- >>> print(community.balances) # doctest: +SKIP
53
-
54
- .. note:: This class comes with its own caching function to reduce the
55
- load on the API server. Instances of this class can be
56
- refreshed with ``Community.refresh()``. The cache can be
57
- cleared with ``Community.clear_cache()``
58
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.
59
52
  """
60
53
 
61
54
  type_id = 2
62
55
 
63
56
  def __init__(
64
- self, community, observer="", full=True, lazy=False, blockchain_instance=None, **kwargs
65
- ):
66
- """Initialize an community
67
-
68
- :param str community: Name of the community
69
- :param Hive/Steem blockchain_instance: Hive/Steem
70
- instance
71
- :param bool lazy: Use lazy loading
72
- :param bool full: Obtain all community data including orders, positions,
73
- 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'
74
74
  """
75
75
  self.full = full
76
76
  self.lazy = lazy
@@ -87,8 +87,16 @@ class Community(BlockchainObject):
87
87
  community, lazy=lazy, full=full, id_item="name", blockchain_instance=blockchain_instance
88
88
  )
89
89
 
90
- def refresh(self):
91
- """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
+ """
92
100
  if not self.blockchain.is_connected():
93
101
  return
94
102
  self.blockchain.rpc.set_next_node_on_empty_reply(True)
@@ -110,208 +118,402 @@ class Community(BlockchainObject):
110
118
  blockchain_instance=self.blockchain,
111
119
  )
112
120
 
113
- def _parse_json_data(self, community):
114
- 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 = [
115
135
  "sum_pending",
116
136
  "subscribers",
117
137
  "num_pending",
118
138
  "num_authors",
119
139
  ]
120
- for p in parse_int:
121
- if p in community and isinstance(community.get(p), string_types):
122
- community[p] = int(community.get(p, 0))
123
- parse_times = ["created_at"]
124
- for p in parse_times:
125
- if p in community and isinstance(community.get(p), string_types):
126
- community[p] = addTzInfo(
127
- 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
+ )
128
152
  )
153
+
129
154
  return community
130
155
 
131
- 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
+ """
132
165
  output = self.copy()
133
- parse_int = [
166
+
167
+ # Convert integer fields to strings for JSON serialization
168
+ int_fields = [
134
169
  "sum_pending",
135
170
  "subscribers",
136
171
  "num_pending",
137
172
  "num_authors",
138
173
  ]
139
- parse_int_without_zero = []
140
- for p in parse_int:
141
- if p in output and isinstance(output[p], integer_types):
142
- output[p] = str(output[p])
143
- for p in parse_int_without_zero:
144
- if p in output and isinstance(output[p], integer_types) and output[p] != 0:
145
- output[p] = str(output[p])
146
-
147
- parse_times = [
148
- "created_at",
149
- ]
150
- for p in parse_times:
151
- if p in output:
152
- p_date = output.get(p, datetime(1970, 1, 1, 0, 0))
153
- if isinstance(p_date, (datetime, date, time)):
154
- 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", " ")
155
195
  else:
156
- output[p] = p_date
196
+ output[field] = date_val
157
197
  return json.loads(str(json.dumps(output)))
158
198
 
159
- def get_community_roles(self):
160
- """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
+ """
161
212
  community = self["name"]
162
213
  if not self.blockchain.is_connected():
163
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
+
164
220
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
165
- 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
166
229
 
167
- def get_subscribers(self):
168
- """Returns subscribers"""
230
+ Returns:
231
+ list: List of subscribers
232
+
233
+ Raises:
234
+ OfflineHasNoRPCException: If not connected to the blockchain
235
+ """
169
236
  community = self["name"]
170
237
  if not self.blockchain.is_connected():
171
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
+
172
244
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
173
- 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
174
256
 
175
- def get_activities(self, limit=100, last_id=None):
176
- """Returns community activity"""
257
+ Raises:
258
+ OfflineHasNoRPCException: If not connected to the blockchain
259
+ """
177
260
  community = self["name"]
178
261
  if not self.blockchain.is_connected():
179
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
+
180
268
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
181
- return self.blockchain.rpc.account_notifications(
182
- {"account": community, "limit": limit, "last_id": last_id}, api="bridge"
183
- )
269
+ return self.blockchain.rpc.account_notifications(params, api="bridge")
184
270
 
185
271
  def get_ranked_posts(
186
- self, observer=None, limit=100, start_author=None, start_permlink=None, sort="created"
187
- ):
188
- """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
+ """
189
294
  community = self["name"]
190
295
  if not self.blockchain.is_connected():
191
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
+
192
307
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
193
- return self.blockchain.rpc.get_ranked_posts(
194
- {
195
- "tag": community,
196
- "observer": observer,
197
- "limit": limit,
198
- "start_author": start_author,
199
- "start_permlink": start_permlink,
200
- "sort": sort,
201
- },
202
- api="bridge",
203
- )
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.
204
312
 
205
- def set_role(self, account, role, mod_account):
206
- """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)
207
317
 
208
- :param str account: Set role of this account
209
- :param str role: Can be member, mod, admin, owner, guest
210
- :param str mod_account: Account who broadcast this, (mods or higher)
318
+ Returns:
319
+ dict: Transaction result
211
320
 
321
+ Raises:
322
+ OfflineHasNoRPCException: If not connected to the blockchain
323
+ ValueError: If role is not one of the allowed values
212
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
+
213
329
  community = self["name"]
214
330
  if not self.blockchain.is_connected():
215
331
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
332
+
216
333
  json_body = [
217
334
  "setRole",
218
335
  {
219
336
  "community": community,
220
337
  "account": account,
221
- "role": role,
338
+ "role": role.lower(),
222
339
  },
223
340
  ]
224
341
  return self.blockchain.custom_json(
225
342
  "community", json_body, required_posting_auths=[mod_account]
226
343
  )
227
344
 
228
- def set_user_title(self, account, title, mod_account):
229
- """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)
230
352
 
231
- :param str account: Set role of this account
232
- :param str title: Title
233
- :param str mod_account: Account who broadcast this, (mods or higher)
353
+ Returns:
354
+ dict: Transaction result
234
355
 
356
+ Raises:
357
+ OfflineHasNoRPCException: If not connected to the blockchain
358
+ ValueError: If account or title is empty
235
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
+
236
366
  community = self["name"]
237
367
  if not self.blockchain.is_connected():
238
368
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
369
+
239
370
  json_body = [
240
371
  "setUserTitle",
241
372
  {
242
373
  "community": community,
243
374
  "account": account,
244
- "title": title,
375
+ "title": title.strip(),
245
376
  },
246
377
  ]
247
378
  return self.blockchain.custom_json(
248
379
  "community", json_body, required_posting_auths=[mod_account]
249
380
  )
250
381
 
251
- def mute_post(self, account, permlink, notes, mod_account):
252
- """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)
253
390
 
254
- :param str account: Set role of this account
255
- :param str permlink: permlink
256
- :param str notes: permlink
257
- :param str mod_account: Account who broadcast this, (mods or higher)
391
+ Returns:
392
+ dict: Transaction result
258
393
 
394
+ Raises:
395
+ OfflineHasNoRPCException: If not connected to the blockchain
396
+ ValueError: If any required parameter is invalid
259
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
+
260
407
  community = self["name"]
261
408
  if not self.blockchain.is_connected():
262
409
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
410
+
263
411
  json_body = [
264
412
  "mutePost",
265
- {"community": community, "account": account, "permlink": permlink, "notes": notes},
413
+ {
414
+ "community": community,
415
+ "account": account,
416
+ "permlink": permlink,
417
+ "notes": notes.strip(),
418
+ },
266
419
  ]
267
420
  return self.blockchain.custom_json(
268
421
  "community", json_body, required_posting_auths=[mod_account]
269
422
  )
270
423
 
271
- def unmute_post(self, account, permlink, notes, mod_account):
272
- """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)
273
432
 
274
- :param str account: post author
275
- :param str permlink: permlink
276
- :param str notes: notes
277
- :param str mod_account: Account who broadcast this, (mods or higher)
433
+ Returns:
434
+ dict: Transaction result
278
435
 
436
+ Raises:
437
+ OfflineHasNoRPCException: If not connected to the blockchain
438
+ ValueError: If any required parameter is invalid
279
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
+
280
449
  community = self["name"]
281
450
  if not self.blockchain.is_connected():
282
451
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
452
+
283
453
  json_body = [
284
454
  "unmutePost",
285
- {"community": community, "account": account, "permlink": permlink, "notes": notes},
455
+ {
456
+ "community": community,
457
+ "account": account,
458
+ "permlink": permlink,
459
+ "notes": notes.strip(),
460
+ },
286
461
  ]
287
462
  return self.blockchain.custom_json(
288
463
  "community", json_body, required_posting_auths=[mod_account]
289
464
  )
290
465
 
291
- def update_props(self, title, about, is_nsfw, description, flag_text, admin_account):
292
- """Updates the community properties
293
-
294
- :param str title: Community title
295
- :param str about: about
296
- :param bool is_nsfw: is_nsfw
297
- :param str description: description
298
- :param str flag_text: flag_text
299
- :param str admin_account: Account who broadcast this, (admin or higher)
300
-
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
301
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
+
302
503
  community = self["name"]
303
504
  if not self.blockchain.is_connected():
304
505
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
506
+
305
507
  json_body = [
306
508
  "updateProps",
307
509
  {
308
510
  "community": community,
309
511
  "props": {
310
- "title": title,
311
- "about": about,
312
- "is_nsfw": is_nsfw,
313
- "description": description,
314
- "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(),
315
517
  },
316
518
  },
317
519
  ]
@@ -319,15 +521,29 @@ class Community(BlockchainObject):
319
521
  "community", json_body, required_posting_auths=[admin_account]
320
522
  )
321
523
 
322
- def subscribe(self, account):
323
- """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
324
532
 
325
- :param str account: account who suscribe to the community (is also broadcasting the custom_json)
533
+ Returns:
534
+ dict: Transaction result
326
535
 
536
+ Raises:
537
+ OfflineHasNoRPCException: If not connected to the blockchain
538
+ ValueError: If account is invalid
327
539
  """
540
+ if not account or not isinstance(account, str):
541
+ raise ValueError("Account must be a non-empty string")
542
+
328
543
  community = self["name"]
329
544
  if not self.blockchain.is_connected():
330
545
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
546
+
331
547
  json_body = [
332
548
  "subscribe",
333
549
  {
@@ -336,17 +552,35 @@ class Community(BlockchainObject):
336
552
  ]
337
553
  return self.blockchain.custom_json("community", json_body, required_posting_auths=[account])
338
554
 
339
- def pin_post(self, account, permlink, mod_account):
340
- """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)
341
565
 
342
- :param str account: post author
343
- :param str permlink: permlink
344
- :param str mod_account: Account who broadcast this, (mods or higher)
566
+ Returns:
567
+ dict: Transaction result
345
568
 
569
+ Raises:
570
+ OfflineHasNoRPCException: If not connected to the blockchain
571
+ ValueError: If any required parameter is invalid
346
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
+
347
580
  community = self["name"]
348
581
  if not self.blockchain.is_connected():
349
582
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
583
+
350
584
  json_body = [
351
585
  "pinPost",
352
586
  {
@@ -359,15 +593,29 @@ class Community(BlockchainObject):
359
593
  "community", json_body, required_posting_auths=[mod_account]
360
594
  )
361
595
 
362
- def unsubscribe(self, account):
363
- """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.
364
601
 
365
- :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
366
604
 
605
+ Returns:
606
+ dict: Transaction result
607
+
608
+ Raises:
609
+ OfflineHasNoRPCException: If not connected to the blockchain
610
+ ValueError: If account is invalid
367
611
  """
612
+ if not account or not isinstance(account, str):
613
+ raise ValueError("Account must be a non-empty string")
614
+
368
615
  community = self["name"]
369
616
  if not self.blockchain.is_connected():
370
617
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
618
+
371
619
  json_body = [
372
620
  "unsubscribe",
373
621
  {
@@ -376,17 +624,35 @@ class Community(BlockchainObject):
376
624
  ]
377
625
  return self.blockchain.custom_json("community", json_body, required_posting_auths=[account])
378
626
 
379
- def unpin_post(self, account, permlink, mod_account):
380
- """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.
381
632
 
382
- :param str account: post author
383
- :param str permlink: permlink
384
- :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)
385
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
386
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
+
387
652
  community = self["name"]
388
653
  if not self.blockchain.is_connected():
389
654
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
655
+
390
656
  json_body = [
391
657
  "unpinPost",
392
658
  {
@@ -399,20 +665,47 @@ class Community(BlockchainObject):
399
665
  "community", json_body, required_posting_auths=[mod_account]
400
666
  )
401
667
 
402
- def flag_post(self, account, permlink, notes, reporter):
403
- """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.
404
670
 
405
- :param str account: post author
406
- :param str permlink: permlink
407
- :param str notes: notes
408
- :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
409
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
+
410
697
  community = self["name"]
411
698
  if not self.blockchain.is_connected():
412
699
  raise OfflineHasNoRPCException("No RPC available in offline mode!")
700
+
413
701
  json_body = [
414
702
  "flagPost",
415
- {"community": community, "account": account, "permlink": permlink, "notes": notes},
703
+ {
704
+ "community": community,
705
+ "account": account,
706
+ "permlink": permlink,
707
+ "notes": notes.strip(),
708
+ },
416
709
  ]
417
710
  return self.blockchain.custom_json(
418
711
  "community", json_body, required_posting_auths=[reporter]
@@ -420,7 +713,21 @@ class Community(BlockchainObject):
420
713
 
421
714
 
422
715
  class CommunityObject(list):
423
- 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
+ """
424
731
  t = PrettyTable(
425
732
  [
426
733
  "Nr.",
@@ -453,26 +760,45 @@ class CommunityObject(list):
453
760
 
454
761
 
455
762
  class Communities(CommunityObject):
456
- """Obtain a list of communities
457
-
458
- :param list name_list: list of accounts to fetch
459
- :param int batch_limit: (optional) maximum number of accounts
460
- to fetch per call, defaults to 100
461
- :param Steem/Hive blockchain_instance: Steem() or Hive() instance to use when
462
- 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'
463
777
  """
464
778
 
465
779
  def __init__(
466
780
  self,
467
- sort="rank",
468
- observer=None,
469
- last=None,
470
- limit=100,
471
- lazy=False,
472
- 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,
473
787
  blockchain_instance=None,
474
788
  **kwargs,
475
- ):
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
+ """
476
802
  if blockchain_instance is None:
477
803
  if kwargs.get("steem_instance"):
478
804
  blockchain_instance = kwargs["steem_instance"]
@@ -482,32 +808,52 @@ class Communities(CommunityObject):
482
808
 
483
809
  if not self.blockchain.is_connected():
484
810
  return
811
+
485
812
  communities = []
486
813
  community_cnt = 0
487
- batch_limit = 100
488
- if batch_limit > limit:
489
- batch_limit = limit
814
+ batch_limit = min(100, limit) # Ensure we don't exceed the limit
490
815
 
491
816
  while community_cnt < limit:
492
817
  self.blockchain.rpc.set_next_node_on_empty_reply(False)
493
- communities += self.blockchain.rpc.list_communities(
818
+ batch = self.blockchain.rpc.list_communities(
494
819
  {"sort": sort, "observer": observer, "last": last, "limit": batch_limit},
495
820
  api="bridge",
496
821
  )
497
- 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)
498
827
  last = communities[-1]["name"]
499
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
+
500
833
  super(Communities, self).__init__(
501
834
  [
502
835
  Community(x, lazy=lazy, full=full, blockchain_instance=self.blockchain)
503
- for x in communities
836
+ for x in communities[:limit] # Ensure we don't exceed the limit
504
837
  ]
505
838
  )
506
839
 
507
- def search_title(self, title):
508
- """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
+
509
854
  ret = CommunityObject()
855
+ title_lower = title.lower()
510
856
  for community in self:
511
- if title.lower() in community["title"].lower():
857
+ if title_lower in community["title"].lower():
512
858
  ret.append(community)
513
859
  return ret