|
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