|
9 | 9 | from queue import Empty
|
10 | 10 | from textwrap import dedent
|
11 | 11 | from time import monotonic
|
| 12 | +from typing import Optional |
12 | 13 |
|
13 | 14 | from jupyter_client import KernelManager
|
14 | 15 | from jupyter_client.client import KernelClient
|
|
28 | 29 | from .util import ensure_async, run_sync
|
29 | 30 |
|
30 | 31 |
|
31 |
| -def timestamp() -> str: |
| 32 | +def timestamp(msg: Optional[Dict] = None) -> str: |
| 33 | + if msg and 'header' in msg: # The test mocks don't provide a header, so tolerate that |
| 34 | + msg_header = msg['header'] |
| 35 | + if 'date' in msg_header and isinstance(msg_header['date'], datetime.datetime): |
| 36 | + try: |
| 37 | + # reformat datetime into expected format |
| 38 | + formatted_time = datetime.datetime.strftime( |
| 39 | + msg_header['date'], '%Y-%m-%dT%H:%M:%S.%fZ' |
| 40 | + ) |
| 41 | + if ( |
| 42 | + formatted_time |
| 43 | + ): # docs indicate strftime may return empty string, so let's catch that too |
| 44 | + return formatted_time |
| 45 | + except Exception: |
| 46 | + pass # fallback to a local time |
| 47 | + |
32 | 48 | return datetime.datetime.utcnow().isoformat() + 'Z'
|
33 | 49 |
|
34 | 50 |
|
@@ -618,7 +634,7 @@ async def _async_poll_for_reply(
|
618 | 634 | msg = await ensure_async(self.kc.shell_channel.get_msg(timeout=new_timeout))
|
619 | 635 | if msg['parent_header'].get('msg_id') == msg_id:
|
620 | 636 | if self.record_timing:
|
621 |
| - cell['metadata']['execution']['shell.execute_reply'] = timestamp() |
| 637 | + cell['metadata']['execution']['shell.execute_reply'] = timestamp(msg) |
622 | 638 | try:
|
623 | 639 | await asyncio.wait_for(task_poll_output_msg, self.iopub_timeout)
|
624 | 640 | except (asyncio.TimeoutError, Empty):
|
@@ -796,7 +812,7 @@ async def async_execute_cell(
|
796 | 812 | self.log.debug("Skipping tagged cell %s", cell_index)
|
797 | 813 | return cell
|
798 | 814 |
|
799 |
| - if self.record_timing and 'execution' not in cell['metadata']: |
| 815 | + if self.record_timing: # clear execution metadata prior to execution |
800 | 816 | cell['metadata']['execution'] = {}
|
801 | 817 |
|
802 | 818 | self.log.debug("Executing cell:\n%s", cell.source)
|
@@ -894,11 +910,11 @@ def process_message(
|
894 | 910 | if self.record_timing:
|
895 | 911 | if msg_type == 'status':
|
896 | 912 | if content['execution_state'] == 'idle':
|
897 |
| - cell['metadata']['execution']['iopub.status.idle'] = timestamp() |
| 913 | + cell['metadata']['execution']['iopub.status.idle'] = timestamp(msg) |
898 | 914 | elif content['execution_state'] == 'busy':
|
899 |
| - cell['metadata']['execution']['iopub.status.busy'] = timestamp() |
| 915 | + cell['metadata']['execution']['iopub.status.busy'] = timestamp(msg) |
900 | 916 | elif msg_type == 'execute_input':
|
901 |
| - cell['metadata']['execution']['iopub.execute_input'] = timestamp() |
| 917 | + cell['metadata']['execution']['iopub.execute_input'] = timestamp(msg) |
902 | 918 |
|
903 | 919 | if msg_type == 'status':
|
904 | 920 | if content['execution_state'] == 'idle':
|
|
0 commit comments