Skip to content

Commit 3e8ffff

Browse files
authored
Merge branch 'staging' into tests/zyzniewski/proper_mock
2 parents e696b5e + 02ea729 commit 3e8ffff

File tree

5 files changed

+102
-10
lines changed

5 files changed

+102
-10
lines changed

bittensor/core/async_subtensor.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import copy
3+
from datetime import datetime, timezone
34
import ssl
45
from functools import partial
56
from typing import Optional, Any, Union, Iterable, TYPE_CHECKING
@@ -27,6 +28,7 @@
2728
decode_account_id,
2829
DynamicInfo,
2930
)
31+
from bittensor.core.chain_data.delegate_info import DelegatedInfo
3032
from bittensor.core.chain_data.utils import decode_metadata
3133
from bittensor.core.config import Config
3234
from bittensor.core.errors import SubstrateRequestException
@@ -1219,7 +1221,7 @@ async def get_delegated(
12191221
if not result:
12201222
return []
12211223

1222-
return DelegateInfo.delegated_list_from_dicts(result)
1224+
return DelegatedInfo.list_from_dicts(result)
12231225

12241226
async def get_delegates(
12251227
self,
@@ -2744,6 +2746,36 @@ async def weights_rate_limit(
27442746
)
27452747
return None if call is None else int(call)
27462748

2749+
async def get_timestamp(
2750+
self,
2751+
block: Optional[int] = None,
2752+
block_hash: Optional[str] = None,
2753+
reuse_block: bool = False,
2754+
) -> datetime:
2755+
"""
2756+
Retrieves the datetime timestamp for a given block
2757+
2758+
Arguments:
2759+
block: The blockchain block number for the query. Do not specify if specifying block_hash or reuse_block.
2760+
block_hash: The blockchain block_hash representation of the block id. Do not specify if specifying block
2761+
or reuse_block.
2762+
reuse_block: Whether to reuse the last-used blockchain block hash. Do not specify if specifying block or
2763+
block_hash.
2764+
2765+
Returns:
2766+
datetime object for the timestamp of the block
2767+
"""
2768+
unix = (
2769+
await self.query_module(
2770+
"Timestamp",
2771+
"Now",
2772+
block=block,
2773+
block_hash=block_hash,
2774+
reuse_block=reuse_block,
2775+
)
2776+
).value
2777+
return datetime.fromtimestamp(unix / 1000, tz=timezone.utc)
2778+
27472779
# Extrinsics helper ================================================================================================
27482780

27492781
async def sign_and_send_extrinsic(

bittensor/core/subtensor.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import copy
2+
from datetime import datetime, timezone
3+
24
from functools import lru_cache
35
from typing import TYPE_CHECKING, Any, Iterable, Optional, Union, cast
46

@@ -2070,6 +2072,19 @@ def weights_rate_limit(
20702072
)
20712073
return None if call is None else int(call)
20722074

2075+
def get_timestamp(self, block: Optional[int] = None) -> datetime:
2076+
"""
2077+
Retrieves the datetime timestamp for a given block
2078+
2079+
Arguments:
2080+
block: The blockchain block number for the query.
2081+
2082+
Returns:
2083+
datetime object for the timestamp of the block
2084+
"""
2085+
unix = cast(ScaleObj, self.query_module("Timestamp", "Now", block=block)).value
2086+
return datetime.fromtimestamp(unix / 1000, tz=timezone.utc)
2087+
20732088
# Extrinsics helper ================================================================================================
20742089

20752090
def sign_and_send_extrinsic(
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from datetime import datetime
2+
import pytest
3+
4+
5+
@pytest.mark.asyncio
6+
async def test_get_timestamp(subtensor, async_subtensor, local_chain):
7+
with subtensor:
8+
block_number = subtensor.get_current_block()
9+
assert isinstance(
10+
subtensor.get_timestamp(), datetime
11+
) # verify it works with no block number specified
12+
sync_result = subtensor.get_timestamp(
13+
block=block_number
14+
) # verify it works with block number specified
15+
async with async_subtensor:
16+
assert isinstance(await async_subtensor.get_timestamp(), datetime)
17+
async_result = await async_subtensor.get_timestamp(block=block_number)
18+
assert sync_result == async_result

tests/unit_tests/test_async_subtensor.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import datetime
12
import unittest.mock as mock
23

34
import pytest
5+
from async_substrate_interface.types import ScaleObj
46
from bittensor_wallet import Wallet
57

68
from bittensor import u64_normalized_float
@@ -1231,9 +1233,9 @@ async def test_get_delegated_no_block_hash_no_reuse(subtensor, mocker):
12311233
# Preps
12321234
fake_coldkey_ss58 = "fake_ss58_address"
12331235

1234-
mocked_delegated_list_from_dicts = mocker.Mock()
1235-
async_subtensor.DelegateInfo.delegated_list_from_dicts = (
1236-
mocked_delegated_list_from_dicts
1236+
mocked_delegated_list_from_dicts = mocker.patch.object(
1237+
async_subtensor.DelegatedInfo,
1238+
"list_from_dicts",
12371239
)
12381240

12391241
# Call
@@ -1259,9 +1261,9 @@ async def test_get_delegated_with_block_hash(subtensor, mocker):
12591261
fake_coldkey_ss58 = "fake_ss58_address"
12601262
fake_block_hash = "fake_block_hash"
12611263

1262-
mocked_delegated_list_from_dicts = mocker.Mock()
1263-
async_subtensor.DelegateInfo.delegated_list_from_dicts = (
1264-
mocked_delegated_list_from_dicts
1264+
mocked_delegated_list_from_dicts = mocker.patch.object(
1265+
async_subtensor.DelegatedInfo,
1266+
"list_from_dicts",
12651267
)
12661268

12671269
# Call
@@ -1289,9 +1291,9 @@ async def test_get_delegated_with_reuse_block(subtensor, mocker):
12891291
fake_coldkey_ss58 = "fake_ss58_address"
12901292
reuse_block = True
12911293

1292-
mocked_delegated_list_from_dicts = mocker.Mock()
1293-
async_subtensor.DelegateInfo.delegated_list_from_dicts = (
1294-
mocked_delegated_list_from_dicts
1294+
mocked_delegated_list_from_dicts = mocker.patch.object(
1295+
async_subtensor.DelegatedInfo,
1296+
"list_from_dicts",
12951297
)
12961298

12971299
# Call
@@ -2714,3 +2716,15 @@ async def test_get_all_neuron_certificates(mocker, subtensor):
27142716
block_hash=None,
27152717
reuse_block_hash=False,
27162718
)
2719+
2720+
2721+
@pytest.mark.asyncio
2722+
async def test_get_timestamp(mocker, subtensor):
2723+
fake_block = 1000
2724+
mocked_query = mocker.AsyncMock(return_value=ScaleObj(1740586018 * 1000))
2725+
mocker.patch.object(subtensor.substrate, "query", mocked_query)
2726+
expected_result = datetime.datetime(
2727+
2025, 2, 26, 16, 6, 58, tzinfo=datetime.timezone.utc
2728+
)
2729+
actual_result = await subtensor.get_timestamp(block=fake_block)
2730+
assert expected_result == actual_result

tests/unit_tests/test_subtensor.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
import argparse
1919
import unittest.mock as mock
20+
import datetime
2021
from unittest.mock import MagicMock
2122

2223
import pytest
2324
from bittensor_wallet import Wallet
2425
from async_substrate_interface import sync_substrate
26+
from async_substrate_interface.types import ScaleObj
2527
import websockets
2628

2729
from bittensor import StakeInfo
@@ -3095,3 +3097,14 @@ def test_get_all_neuron_certificates(mocker, subtensor):
30953097
params=[fake_netuid],
30963098
block_hash=None,
30973099
)
3100+
3101+
3102+
def test_get_timestamp(mocker, subtensor):
3103+
fake_block = 1000
3104+
mocked_query = mocker.MagicMock(return_value=ScaleObj(1740586018 * 1000))
3105+
mocker.patch.object(subtensor.substrate, "query", mocked_query)
3106+
expected_result = datetime.datetime(
3107+
2025, 2, 26, 16, 6, 58, tzinfo=datetime.timezone.utc
3108+
)
3109+
actual_result = subtensor.get_timestamp(block=fake_block)
3110+
assert expected_result == actual_result

0 commit comments

Comments
 (0)