hive-nectar 0.2.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.
Files changed (87) hide show
  1. hive_nectar-0.2.9.dist-info/METADATA +194 -0
  2. hive_nectar-0.2.9.dist-info/RECORD +87 -0
  3. hive_nectar-0.2.9.dist-info/WHEEL +4 -0
  4. hive_nectar-0.2.9.dist-info/entry_points.txt +2 -0
  5. hive_nectar-0.2.9.dist-info/licenses/LICENSE.txt +23 -0
  6. nectar/__init__.py +37 -0
  7. nectar/account.py +5076 -0
  8. nectar/amount.py +553 -0
  9. nectar/asciichart.py +303 -0
  10. nectar/asset.py +122 -0
  11. nectar/block.py +574 -0
  12. nectar/blockchain.py +1242 -0
  13. nectar/blockchaininstance.py +2590 -0
  14. nectar/blockchainobject.py +263 -0
  15. nectar/cli.py +5937 -0
  16. nectar/comment.py +1552 -0
  17. nectar/community.py +854 -0
  18. nectar/constants.py +95 -0
  19. nectar/discussions.py +1437 -0
  20. nectar/exceptions.py +152 -0
  21. nectar/haf.py +381 -0
  22. nectar/hive.py +630 -0
  23. nectar/imageuploader.py +114 -0
  24. nectar/instance.py +113 -0
  25. nectar/market.py +876 -0
  26. nectar/memo.py +542 -0
  27. nectar/message.py +379 -0
  28. nectar/nodelist.py +309 -0
  29. nectar/price.py +603 -0
  30. nectar/profile.py +74 -0
  31. nectar/py.typed +0 -0
  32. nectar/rc.py +333 -0
  33. nectar/snapshot.py +1024 -0
  34. nectar/storage.py +62 -0
  35. nectar/transactionbuilder.py +659 -0
  36. nectar/utils.py +630 -0
  37. nectar/version.py +3 -0
  38. nectar/vote.py +722 -0
  39. nectar/wallet.py +472 -0
  40. nectar/witness.py +728 -0
  41. nectarapi/__init__.py +12 -0
  42. nectarapi/exceptions.py +126 -0
  43. nectarapi/graphenerpc.py +596 -0
  44. nectarapi/node.py +194 -0
  45. nectarapi/noderpc.py +79 -0
  46. nectarapi/openapi.py +107 -0
  47. nectarapi/py.typed +0 -0
  48. nectarapi/rpcutils.py +98 -0
  49. nectarapi/version.py +3 -0
  50. nectarbase/__init__.py +15 -0
  51. nectarbase/ledgertransactions.py +106 -0
  52. nectarbase/memo.py +242 -0
  53. nectarbase/objects.py +521 -0
  54. nectarbase/objecttypes.py +21 -0
  55. nectarbase/operationids.py +102 -0
  56. nectarbase/operations.py +1357 -0
  57. nectarbase/py.typed +0 -0
  58. nectarbase/signedtransactions.py +89 -0
  59. nectarbase/transactions.py +11 -0
  60. nectarbase/version.py +3 -0
  61. nectargraphenebase/__init__.py +27 -0
  62. nectargraphenebase/account.py +1121 -0
  63. nectargraphenebase/aes.py +49 -0
  64. nectargraphenebase/base58.py +197 -0
  65. nectargraphenebase/bip32.py +575 -0
  66. nectargraphenebase/bip38.py +110 -0
  67. nectargraphenebase/chains.py +15 -0
  68. nectargraphenebase/dictionary.py +2 -0
  69. nectargraphenebase/ecdsasig.py +309 -0
  70. nectargraphenebase/objects.py +130 -0
  71. nectargraphenebase/objecttypes.py +8 -0
  72. nectargraphenebase/operationids.py +5 -0
  73. nectargraphenebase/operations.py +25 -0
  74. nectargraphenebase/prefix.py +13 -0
  75. nectargraphenebase/py.typed +0 -0
  76. nectargraphenebase/signedtransactions.py +221 -0
  77. nectargraphenebase/types.py +557 -0
  78. nectargraphenebase/unsignedtransactions.py +288 -0
  79. nectargraphenebase/version.py +3 -0
  80. nectarstorage/__init__.py +57 -0
  81. nectarstorage/base.py +317 -0
  82. nectarstorage/exceptions.py +15 -0
  83. nectarstorage/interfaces.py +244 -0
  84. nectarstorage/masterpassword.py +237 -0
  85. nectarstorage/py.typed +0 -0
  86. nectarstorage/ram.py +27 -0
  87. nectarstorage/sqlite.py +343 -0
nectar/price.py ADDED
@@ -0,0 +1,603 @@
1
+ from decimal import Decimal
2
+ from fractions import Fraction
3
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
4
+
5
+ from nectar.instance import shared_blockchain_instance
6
+
7
+ from .amount import Amount, check_asset
8
+ from .asset import Asset
9
+ from .exceptions import InvalidAssetException
10
+
11
+ if TYPE_CHECKING:
12
+ from .market import Market
13
+ from .utils import assets_from_string, formatTimeString
14
+
15
+
16
+ class Price(dict):
17
+ """This class deals with all sorts of prices of any pair of assets to
18
+ simplify dealing with the tuple::
19
+
20
+ (quote, base)
21
+
22
+ each being an instance of :class:`nectar.amount.Amount`. The
23
+ amount themselves define the price.
24
+
25
+ .. note::
26
+
27
+ The price (floating) is derived as ``base/quote``
28
+
29
+ :param list args: Allows to deal with different representations of a price
30
+ :param Asset base: Base asset
31
+ :param Asset quote: Quote asset
32
+ :param Hive blockchain_instance: Hive instance
33
+ :returns: All data required to represent a price
34
+ :rtype: dictionary
35
+
36
+ Way to obtain a proper instance:
37
+
38
+ * ``args`` is a str with a price and two assets
39
+ * ``args`` can be a floating number and ``base`` and ``quote`` being instances of :class:`nectar.asset.Asset`
40
+ * ``args`` can be a floating number and ``base`` and ``quote`` being instances of ``str``
41
+ * ``args`` can be dict with keys ``price``, ``base``, and ``quote`` (*graphene balances*)
42
+ * ``args`` can be dict with keys ``base`` and ``quote``
43
+ * ``args`` can be dict with key ``receives`` (filled orders)
44
+ * ``args`` being a list of ``[quote, base]`` both being instances of :class:`nectar.amount.Amount`
45
+ * ``args`` being a list of ``[quote, base]`` both being instances of ``str`` (``amount symbol``)
46
+ * ``base`` and ``quote`` being instances of :class:`nectar.asset.Amount`
47
+
48
+ This allows instantiations like:
49
+
50
+ * ``Price("0.315 HBD/HIVE")``
51
+ * ``Price(0.315, base="HBD", quote="HIVE")``
52
+ * ``Price(0.315, base=Asset("HBD"), quote=Asset("HIVE"))``
53
+ * ``Price({"base": {"amount": 1, "asset_id": "HBD"}, "quote": {"amount": 10, "asset_id": "HBD"}})``
54
+ * ``Price(quote="10 HIVE", base="1 HBD")``
55
+ * ``Price("10 HIVE", "1 HBD")``
56
+ * ``Price(Amount("10 HIVE"), Amount("1 HBD"))``
57
+ * ``Price(1.0, "HBD/HIVE")``
58
+
59
+ Instances of this class can be used in regular mathematical expressions
60
+ (``+-*/%``) such as:
61
+
62
+ .. code-block:: python
63
+
64
+ >>> from nectar.price import Price
65
+ >>> from nectar import Hive
66
+ >>> hv = Hive("https://api.hive.blog")
67
+ >>> Price("0.3314 HBD/HIVE", blockchain_instance=hv) * 2
68
+ 0.662804 HBD/HIVE
69
+ >>> Price(0.3314, "HBD", "HIVE", blockchain_instance=hv)
70
+ 0.331402 HBD/HIVE
71
+
72
+ """
73
+
74
+ def __init__(
75
+ self,
76
+ price: Optional[Union[str, Dict[str, Any], "Price"]] = None,
77
+ base: Optional[Union[str, Amount, Asset]] = None,
78
+ quote: Optional[Union[str, Amount, Asset]] = None,
79
+ base_asset: Optional[str] = None, # to identify sell/buy
80
+ blockchain_instance: Optional[Any] = None,
81
+ ) -> None:
82
+ """
83
+ Initialize a Price object representing a ratio between a base and quote asset.
84
+
85
+ This constructor accepts multiple input forms and normalizes them into internal
86
+ "base" and "quote" Amount entries. Supported usages:
87
+ - price: str like "X BASE/QUOTE" with no base/quote: parses symbols and creates
88
+ Amounts from the fractional representation of X.
89
+ - price: dict with "base" and "quote": loads Amounts directly (raises AssertionError
90
+ if a top-level "price" key is present).
91
+ - price: numeric (float/int/Decimal) with base and quote provided as Asset or
92
+ symbol strings: converts the numeric value to a Fraction and builds Amounts.
93
+ - price: str representing an Amount and base: when price is a string and base is
94
+ a symbol string, price and base are used to build quote/base Amounts.
95
+ - price and base as Amount instances: accepts Amount objects directly.
96
+ - price is None with base and quote as symbol strings or Amounts: loads assets
97
+ or Amounts respectively.
98
+
99
+ Parameters (not exhaustive):
100
+ - price: numeric, str, dict, or Amount — the price or a representation used to
101
+ derive base/quote Amounts.
102
+ - base: Asset, Amount, or str — identifies the base side (or a symbol string
103
+ used to parse both symbols when combined with a numeric price).
104
+ - quote: Asset, Amount, or str — identifies the quote side.
105
+ - base_asset: optional; used only as an identifier flag for buy/sell contexts.
106
+ - blockchain_instance: blockchain context used to construct Asset/Amount (omitted
107
+ from param listing as a shared service).
108
+
109
+ Raises:
110
+ - AssertionError: if a dict `price` includes a top-level "price" key.
111
+ - ValueError: if the combination of inputs cannot be parsed into base and quote.
112
+ """
113
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
114
+ if price == "":
115
+ price = None
116
+ if price is not None and isinstance(price, str) and not base and not quote:
117
+ price, assets = price.split(" ")
118
+ base_symbol, quote_symbol = assets_from_string(assets)
119
+ base = Asset(base_symbol, blockchain_instance=self.blockchain)
120
+ quote = Asset(quote_symbol, blockchain_instance=self.blockchain)
121
+ frac = Fraction(float(price)).limit_denominator(10 ** base["precision"])
122
+ self["quote"] = Amount(
123
+ amount=frac.denominator, asset=quote, blockchain_instance=self.blockchain
124
+ )
125
+ self["base"] = Amount(
126
+ amount=frac.numerator, asset=base, blockchain_instance=self.blockchain
127
+ )
128
+
129
+ elif price is not None and isinstance(price, dict) and "base" in price and "quote" in price:
130
+ if "price" in price:
131
+ raise AssertionError("You cannot provide a 'price' this way")
132
+ # Regular 'price' objects according to hive-core
133
+ # base_id = price["base"]["asset_id"]
134
+ # if price["base"]["asset_id"] == base_id:
135
+ self["base"] = Amount(price["base"], blockchain_instance=self.blockchain)
136
+ self["quote"] = Amount(price["quote"], blockchain_instance=self.blockchain)
137
+ # else:
138
+ # self["quote"] = Amount(price["base"], blockchain_instance=self.blockchain)
139
+ # self["base"] = Amount(price["quote"], blockchain_instance=self.blockchain)
140
+
141
+ elif price is not None and isinstance(base, Asset) and isinstance(quote, Asset):
142
+ if isinstance(price, Price):
143
+ frac = Fraction(float(price["price"])).limit_denominator(10 ** base["precision"])
144
+ elif isinstance(price, (int, float, str)):
145
+ frac = Fraction(float(price)).limit_denominator(10 ** base["precision"])
146
+ else:
147
+ raise ValueError("Unsupported price type")
148
+ self["quote"] = Amount(
149
+ amount=frac.denominator, asset=quote, blockchain_instance=self.blockchain
150
+ )
151
+ self["base"] = Amount(
152
+ amount=frac.numerator, asset=base, blockchain_instance=self.blockchain
153
+ )
154
+
155
+ elif price is not None and isinstance(base, str) and isinstance(quote, str):
156
+ base = Asset(base, blockchain_instance=self.blockchain)
157
+ quote = Asset(quote, blockchain_instance=self.blockchain)
158
+ if isinstance(price, Price):
159
+ frac = Fraction(float(price["price"])).limit_denominator(10 ** base["precision"])
160
+ elif isinstance(price, (int, float, str)):
161
+ frac = Fraction(float(price)).limit_denominator(10 ** base["precision"])
162
+ else:
163
+ raise ValueError("Unsupported price type")
164
+ self["quote"] = Amount(
165
+ amount=frac.denominator, asset=quote, blockchain_instance=self.blockchain
166
+ )
167
+ self["base"] = Amount(
168
+ amount=frac.numerator, asset=base, blockchain_instance=self.blockchain
169
+ )
170
+
171
+ elif price is None and isinstance(base, str) and isinstance(quote, str):
172
+ self["quote"] = Amount(quote, blockchain_instance=self.blockchain)
173
+ self["base"] = Amount(base, blockchain_instance=self.blockchain)
174
+ elif price is not None and isinstance(price, str) and isinstance(base, str):
175
+ self["quote"] = Amount(price, blockchain_instance=self.blockchain)
176
+ self["base"] = Amount(base, blockchain_instance=self.blockchain)
177
+ # len(args) > 1
178
+
179
+ elif isinstance(price, Amount) and isinstance(base, Amount):
180
+ self["quote"], self["base"] = price, base
181
+
182
+ # len(args) == 0
183
+ elif price is None and isinstance(base, Amount) and isinstance(quote, Amount):
184
+ self["quote"] = quote
185
+ self["base"] = base
186
+
187
+ elif (
188
+ isinstance(price, float) or isinstance(price, int) or isinstance(price, Decimal)
189
+ ) and isinstance(base, str):
190
+ base_symbol, quote_symbol = assets_from_string(base)
191
+ base = Asset(base_symbol, blockchain_instance=self.blockchain)
192
+ quote = Asset(quote_symbol, blockchain_instance=self.blockchain)
193
+ frac = Fraction(float(price)).limit_denominator(10 ** base["precision"])
194
+ self["quote"] = Amount(
195
+ amount=frac.denominator, asset=quote, blockchain_instance=self.blockchain
196
+ )
197
+ self["base"] = Amount(
198
+ amount=frac.numerator, asset=base, blockchain_instance=self.blockchain
199
+ )
200
+
201
+ else:
202
+ raise ValueError("Couldn't parse 'Price'.")
203
+
204
+ def __setitem__(self, key: str, value: Any) -> None:
205
+ dict.__setitem__(self, key, value)
206
+ if (
207
+ "quote" in self and "base" in self and self["base"] and self["quote"]
208
+ ): # don't derive price for deleted Orders
209
+ dict.__setitem__(
210
+ self, "price", self._safedivide(self["base"]["amount"], self["quote"]["amount"])
211
+ )
212
+
213
+ def copy(self) -> "Price":
214
+ return Price(
215
+ None,
216
+ base=self["base"].copy(),
217
+ quote=self["quote"].copy(),
218
+ blockchain_instance=self.blockchain,
219
+ )
220
+
221
+ def _safedivide(
222
+ self, a: Union[int, float, Decimal], b: Union[int, float, Decimal]
223
+ ) -> Union[int, float, Decimal]:
224
+ if b != 0.0:
225
+ return float(a) / float(b)
226
+ else:
227
+ return float("inf")
228
+
229
+ def symbols(self) -> Tuple[str, str]:
230
+ return self["base"]["symbol"], self["quote"]["symbol"]
231
+
232
+ def as_base(self, base: Union[str, Asset]) -> "Price":
233
+ """
234
+ Return a copy of this Price expressed with the given asset as the base.
235
+
236
+ If `base` matches the current base symbol this returns a shallow copy.
237
+ If `base` matches the current quote symbol this returns a copy with base and quote inverted.
238
+ Raises InvalidAssetException if `base` is neither the base nor the quote of this price.
239
+
240
+ Parameters:
241
+ base (str): Asset symbol to use as the base (e.g., "HIVE" or "HBD").
242
+
243
+ Returns:
244
+ Price: A new Price instance whose base asset is `base`.
245
+ """
246
+ if base == self["base"]["symbol"]:
247
+ return self.copy()
248
+ elif base == self["quote"]["symbol"]:
249
+ return self.copy().invert()
250
+ else:
251
+ raise InvalidAssetException
252
+
253
+ def as_quote(self, quote: Union[str, Asset]) -> "Price":
254
+ """
255
+ Return a Price instance expressed with the given quote asset symbol.
256
+
257
+ If `quote` matches the current quote symbol, returns a copy of this Price.
258
+ If `quote` matches the current base symbol, returns a copied, inverted Price.
259
+ A new object is always returned (the original is not modified).
260
+
261
+ Parameters:
262
+ quote (str): Asset symbol to use as the quote (e.g., "HBD" or "HIVE").
263
+
264
+ Returns:
265
+ Price: A Price object with `quote` as the quote asset.
266
+
267
+ Raises:
268
+ InvalidAssetException: If `quote` does not match either the current base or quote symbol.
269
+ """
270
+ if quote == self["quote"]["symbol"]:
271
+ return self.copy()
272
+ elif quote == self["base"]["symbol"]:
273
+ return self.copy().invert()
274
+ else:
275
+ raise InvalidAssetException
276
+
277
+ def invert(self) -> "Price":
278
+ """
279
+ Invert the price in place, swapping base and quote assets (e.g., HBD/HIVE -> HIVE/HBD).
280
+
281
+ Returns:
282
+ self: The same Price instance after inversion.
283
+
284
+ Example:
285
+ >>> from nectar.price import Price
286
+ >>> from nectar import Hive
287
+ >>> hv = Hive("https://api.hive.blog")
288
+ >>> Price("0.3314 HBD/HIVE", blockchain_instance=hv).invert()
289
+ 3.017483 HIVE/HBD
290
+ """
291
+ tmp = self["quote"]
292
+ self["quote"] = self["base"]
293
+ self["base"] = tmp
294
+ return self
295
+
296
+ def json(self) -> Dict[str, Any]:
297
+ return {"base": self["base"].json(), "quote": self["quote"].json()}
298
+
299
+ def __repr__(self) -> str:
300
+ return "{price:.{precision}f} {base}/{quote}".format(
301
+ price=self["price"],
302
+ base=self["base"]["symbol"],
303
+ quote=self["quote"]["symbol"],
304
+ precision=(self["base"]["asset"]["precision"] + self["quote"]["asset"]["precision"]),
305
+ )
306
+
307
+ def __float__(self) -> float:
308
+ return float(self["price"])
309
+
310
+ def _check_other(self, other: "Price") -> None:
311
+ if not other["base"]["symbol"] == self["base"]["symbol"]:
312
+ raise AssertionError()
313
+ if not other["quote"]["symbol"] == self["quote"]["symbol"]:
314
+ raise AssertionError()
315
+
316
+ def __mul__(self, other: Union["Price", Amount, float, int]) -> "Price":
317
+ a = self.copy()
318
+ if isinstance(other, Price):
319
+ # Rotate/invert other
320
+ if (
321
+ self["quote"]["symbol"] not in other.symbols()
322
+ and self["base"]["symbol"] not in other.symbols()
323
+ ):
324
+ raise InvalidAssetException
325
+
326
+ # base/quote = a/b
327
+ # a/b * b/c = a/c
328
+ a = self.copy()
329
+ if self["quote"]["symbol"] == other["base"]["symbol"]:
330
+ a["base"] = Amount(
331
+ float(self["base"]) * float(other["base"]),
332
+ self["base"]["symbol"],
333
+ blockchain_instance=self.blockchain,
334
+ )
335
+ a["quote"] = Amount(
336
+ float(self["quote"]) * float(other["quote"]),
337
+ other["quote"]["symbol"],
338
+ blockchain_instance=self.blockchain,
339
+ )
340
+ # a/b * c/a = c/b
341
+ elif self["base"]["symbol"] == other["quote"]["symbol"]:
342
+ a["base"] = Amount(
343
+ float(self["base"]) * float(other["base"]),
344
+ other["base"]["symbol"],
345
+ blockchain_instance=self.blockchain,
346
+ )
347
+ a["quote"] = Amount(
348
+ float(self["quote"]) * float(other["quote"]),
349
+ self["quote"]["symbol"],
350
+ blockchain_instance=self.blockchain,
351
+ )
352
+ else:
353
+ raise ValueError("Wrong rotation of prices")
354
+ elif isinstance(other, Amount):
355
+ check_asset(other["asset"], self["quote"]["asset"], self.blockchain)
356
+ a = other.copy() * self["price"]
357
+ a["asset"] = self["base"]["asset"].copy()
358
+ a["symbol"] = self["base"]["asset"]["symbol"]
359
+ else:
360
+ a["base"] *= other
361
+ return a
362
+
363
+ def __imul__(self, other: Union["Price", Amount, float, int]) -> "Price":
364
+ if isinstance(other, Price):
365
+ tmp = self * other
366
+ self["base"] = tmp["base"]
367
+ self["quote"] = tmp["quote"]
368
+ else:
369
+ self["base"] *= other
370
+ return self
371
+
372
+ def __div__(self, other: Union["Price", Amount, float, int]) -> Union["Price", float]:
373
+ a = self.copy()
374
+ if isinstance(other, Price):
375
+ # Rotate/invert other
376
+ if sorted(self.symbols()) == sorted(other.symbols()):
377
+ return float(self.as_base(self["base"]["symbol"])) / float(
378
+ other.as_base(self["base"]["symbol"])
379
+ )
380
+ elif self["quote"]["symbol"] in other.symbols():
381
+ other = other.as_base(self["quote"]["symbol"])
382
+ elif self["base"]["symbol"] in other.symbols():
383
+ other = other.as_base(self["base"]["symbol"])
384
+ else:
385
+ raise InvalidAssetException
386
+ a["base"] = Amount(
387
+ float(self["base"].amount / other["base"].amount),
388
+ other["quote"]["symbol"],
389
+ blockchain_instance=self.blockchain,
390
+ )
391
+ a["quote"] = Amount(
392
+ float(self["quote"].amount / other["quote"].amount),
393
+ self["quote"]["symbol"],
394
+ blockchain_instance=self.blockchain,
395
+ )
396
+ elif isinstance(other, Amount):
397
+ check_asset(other["asset"], self["quote"]["asset"], self.blockchain)
398
+ a = other.copy() / self["price"]
399
+ a["asset"] = self["base"]["asset"].copy()
400
+ a["symbol"] = self["base"]["asset"]["symbol"]
401
+ else:
402
+ a["base"] /= other
403
+ return a
404
+
405
+ def __idiv__(self, other: Union["Price", Amount, float, int]) -> "Price":
406
+ if isinstance(other, Price):
407
+ tmp = self / other
408
+ # tmp can be either Price or float, handle both cases
409
+ if isinstance(tmp, (int, float)):
410
+ # If division returned a float, we can't do in-place modification
411
+ # Convert to Price by updating the base amount
412
+ self["base"] = Amount(
413
+ float(tmp) * float(self["base"]),
414
+ self["base"]["symbol"],
415
+ blockchain_instance=self.blockchain,
416
+ )
417
+ else:
418
+ # tmp is a Price, do normal in-place update
419
+ self["base"] = tmp["base"]
420
+ self["quote"] = tmp["quote"]
421
+ else:
422
+ self["base"] /= other
423
+ return self
424
+
425
+ def __floordiv__(self, other: Any) -> "Price":
426
+ raise NotImplementedError("This is not possible as the price is a ratio")
427
+
428
+ def __ifloordiv__(self, other: Any) -> "Price":
429
+ raise NotImplementedError("This is not possible as the price is a ratio")
430
+
431
+ def __lt__(self, other: Union["Price", Amount, float, int]) -> bool:
432
+ if isinstance(other, Price):
433
+ self._check_other(other)
434
+ return self["price"] < other["price"]
435
+ else:
436
+ return self["price"] < float(other or 0)
437
+
438
+ def __le__(self, other: Union["Price", Amount, float, int]) -> bool:
439
+ if isinstance(other, Price):
440
+ self._check_other(other)
441
+ return self["price"] <= other["price"]
442
+ else:
443
+ return self["price"] <= float(other or 0)
444
+
445
+ def __eq__(self, other: object) -> bool:
446
+ if isinstance(other, Price):
447
+ self._check_other(other)
448
+ return self["price"] == other["price"]
449
+ if isinstance(other, (float, int)):
450
+ return self["price"] == float(other or 0)
451
+ return False
452
+
453
+ def __ne__(self, other: object) -> bool:
454
+ if isinstance(other, Price):
455
+ self._check_other(other)
456
+ return self["price"] != other["price"]
457
+ if isinstance(other, (float, int)):
458
+ return self["price"] != float(other or 0)
459
+ return True
460
+
461
+ def __ge__(self, other: Union["Price", Amount, float, int]) -> bool:
462
+ if isinstance(other, Price):
463
+ self._check_other(other)
464
+ return self["price"] >= other["price"]
465
+ else:
466
+ return self["price"] >= float(other or 0)
467
+
468
+ def __gt__(self, other: Union["Price", Amount, float, int]) -> bool:
469
+ if isinstance(other, Price):
470
+ self._check_other(other)
471
+ return self["price"] > other["price"]
472
+ else:
473
+ return self["price"] > float(other or 0)
474
+
475
+ __truediv__ = __div__
476
+ __truemul__ = __mul__
477
+ __str__ = __repr__
478
+
479
+ @property
480
+ def market(self) -> "Market":
481
+ """Open the corresponding market
482
+
483
+ :returns: Instance of :class:`nectar.market.Market` for the
484
+ corresponding pair of assets.
485
+ """
486
+ from .market import Market
487
+
488
+ return Market(
489
+ base=self["base"]["asset"],
490
+ quote=self["quote"]["asset"],
491
+ blockchain_instance=self.blockchain,
492
+ )
493
+
494
+
495
+ class Order(Price):
496
+ """This class inherits :class:`nectar.price.Price` but has the ``base``
497
+ and ``quote`` Amounts not only be used to represent the price (as a
498
+ ratio of base and quote) but instead has those amounts represent the
499
+ amounts of an actual order!
500
+
501
+ :param Hive blockchain_instance: Hive instance
502
+
503
+ .. note::
504
+
505
+ If an order is marked as deleted, it will carry the
506
+ 'deleted' key which is set to ``True`` and all other
507
+ data be ``None``.
508
+ """
509
+
510
+ def __init__(
511
+ self,
512
+ base: Union[Dict[str, Any], Amount],
513
+ quote: Optional[Amount] = None,
514
+ blockchain_instance: Optional[Any] = None,
515
+ **kwargs: Any,
516
+ ) -> None:
517
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
518
+
519
+ if isinstance(base, dict) and "sell_price" in base:
520
+ super().__init__(base["sell_price"], blockchain_instance=self.blockchain)
521
+ self["id"] = base.get("id")
522
+ elif isinstance(base, dict) and "min_to_receive" in base and "amount_to_sell" in base:
523
+ super().__init__(
524
+ Amount(base["min_to_receive"], blockchain_instance=self.blockchain),
525
+ Amount(base["amount_to_sell"], blockchain_instance=self.blockchain),
526
+ blockchain_instance=self.blockchain,
527
+ )
528
+ self["id"] = base.get("id")
529
+ elif isinstance(base, Amount) and isinstance(quote, Amount):
530
+ super().__init__(None, base=base, quote=quote, blockchain_instance=self.blockchain)
531
+ else:
532
+ raise ValueError("Unknown format to load Order")
533
+
534
+ def __repr__(self) -> str:
535
+ if "deleted" in self and self["deleted"]:
536
+ return "deleted order %s" % self["id"]
537
+ else:
538
+ t = ""
539
+ if "time" in self and self["time"]:
540
+ t += "(%s) " % self["time"]
541
+ if "type" in self and self["type"]:
542
+ t += "%s " % str(self["type"])
543
+ if "quote" in self and self["quote"]:
544
+ t += "%s " % str(self["quote"])
545
+ if "base" in self and self["base"]:
546
+ t += "%s " % str(self["base"])
547
+ return t + "@ " + Price.__repr__(self)
548
+
549
+ __str__ = __repr__
550
+
551
+
552
+ class FilledOrder(Price):
553
+ """This class inherits :class:`nectar.price.Price` but has the ``base``
554
+ and ``quote`` Amounts not only be used to represent the price (as a
555
+ ratio of base and quote) but instead has those amounts represent the
556
+ amounts of an actually filled order!
557
+
558
+ :param Hive blockchain_instance: Hive instance
559
+
560
+ .. note:: Instances of this class come with an additional ``date`` key
561
+ that shows when the order has been filled!
562
+ """
563
+
564
+ def __init__(
565
+ self, order: Dict[str, Any], blockchain_instance: Optional[Any] = None, **kwargs: Any
566
+ ) -> None:
567
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
568
+ if isinstance(order, dict) and "current_pays" in order and "open_pays" in order:
569
+ # filled orders from account history
570
+ if "op" in order:
571
+ order = order["op"]
572
+
573
+ super().__init__(
574
+ Amount(order["open_pays"], blockchain_instance=self.blockchain),
575
+ Amount(order["current_pays"], blockchain_instance=self.blockchain),
576
+ blockchain_instance=self.blockchain,
577
+ )
578
+ if "date" in order:
579
+ self["date"] = formatTimeString(order["date"])
580
+
581
+ else:
582
+ raise ValueError("Couldn't parse 'Price'.")
583
+
584
+ def json(self) -> Dict[str, Any]:
585
+ return {
586
+ "date": formatTimeString(self["date"]),
587
+ "current_pays": self["base"].json(),
588
+ "open_pays": self["quote"].json(),
589
+ }
590
+
591
+ def __repr__(self) -> str:
592
+ t = ""
593
+ if "date" in self and self["date"]:
594
+ t += "(%s) " % self["date"]
595
+ if "type" in self and self["type"]:
596
+ t += "%s " % str(self["type"])
597
+ if "quote" in self and self["quote"]:
598
+ t += "%s " % str(self["quote"])
599
+ if "base" in self and self["base"]:
600
+ t += "%s " % str(self["base"])
601
+ return t + "@ " + Price.__repr__(self)
602
+
603
+ __str__ = __repr__
nectar/profile.py ADDED
@@ -0,0 +1,74 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ from collections.abc import Mapping
5
+
6
+
7
+ class DotDict(dict):
8
+ def __init__(self, *args: Any) -> None:
9
+ """This class simplifies the use of "."-separated
10
+ keys when defining a nested dictionary:::
11
+
12
+ >>> from nectar.profile import Profile
13
+ >>> keys = ['profile.url', 'profile.img']
14
+ >>> values = ["http:", "foobar"]
15
+ >>> p = Profile(keys, values)
16
+ >>> print(p["profile"]["url"])
17
+ http:
18
+
19
+ """
20
+ if len(args) == 2:
21
+ for i, item in enumerate(args[0]):
22
+ t = self
23
+ parts = item.split(".")
24
+ for j, part in enumerate(parts):
25
+ if j < len(parts) - 1:
26
+ t = t.setdefault(part, {})
27
+ else:
28
+ t[part] = args[1][i]
29
+ elif len(args) == 1 and isinstance(args[0], dict):
30
+ for k, v in args[0].items():
31
+ self[k] = v
32
+ elif len(args) == 1 and isinstance(args[0], str):
33
+ for k, v in json.loads(args[0]).items():
34
+ self[k] = v
35
+
36
+
37
+ class Profile(DotDict):
38
+ """This class is a template to model a user's on-chain
39
+ profile according to Hive profile metadata conventions.
40
+ """
41
+
42
+ def __init__(self, *args: Any) -> None:
43
+ """
44
+ Initialize a Profile by delegating to the DotDict initializer.
45
+
46
+ This constructor accepts the same arguments as DotDict.
47
+ """
48
+ super().__init__(*args)
49
+
50
+ def __str__(self) -> str:
51
+ return json.dumps(self)
52
+
53
+ def update(self, *args: Any, **kwargs: Any) -> None:
54
+ """
55
+ Update the profile with mapping/iterable semantics while preserving nested-merge behavior for mappings.
56
+ """
57
+ if args:
58
+ mapping = args[0]
59
+ if isinstance(mapping, Mapping):
60
+ for k, v in mapping.items():
61
+ if isinstance(v, Mapping):
62
+ self.setdefault(k, {}).update(v)
63
+ else:
64
+ self[k] = v
65
+ return
66
+ # Fallback to dict.update behavior
67
+ super().update(*args, **kwargs)
68
+
69
+ def remove(self, key: str) -> None:
70
+ parts = key.split(".")
71
+ if len(parts) > 1:
72
+ self[parts[0]].pop(".".join(parts[1:]))
73
+ else:
74
+ super().pop(parts[0], None)
nectar/py.typed ADDED
File without changes