I think I stumbled across a Jupyter Magic bug. ```...
# hamilton-help
s
I think I stumbled across a Jupyter Magic bug.
Copy code
# cell 1
display_config = {"orient": "TD", "deduplicate_inputs": True}
config = {"last_run": "run-id"}
The following works.
Copy code
# cell 2 - v1
%%cell_to_module adjustments
The following does not work
Copy code
# cell 2 - v2
%%cell_to_module adjustments --display display_config --config config
The full error detail is in the thread.
*TypeError*: 'ABCMeta' object is not iterable
I did not have any issues, until I added a config.
Copy code
@config.when_not(last_run="run-id")
def last_run_results__default() -> pd.DataFrame: ...

@config.when(last_run="run-id")
def last_run_results__runid() -> pd.DataFrame: ...
👀 1
Full Stack Trace
Copy code
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[22], line 1
----> 1 get_ipython().run_cell_magic('cell_to_module', 'adjustments --display display_config --config config', 'import stat\nimport pandas as pd\nfrom datetime import datetime\nfrom pathlib import Path\nfrom typing import Dict, List, Optional, Tuple\nfrom duco_api.duco_api import DucoCube\n\nfrom hamilton.htypes import Parallelizable, Collect\nfrom duco_api.util import unzip_bytes\nfrom hamilton.function_modifiers import pipe_output, pipe_input, step, extract_fields, config\nfrom pandas.tseries.offsets import BMonthBegin\n\n\ndef _most_recent_business_month_start(run_date: Optional[datetime] = None) -> pd.Timestamp:\n    """Return the most recent business month start date in the past with time zeroed out."""\n    \n    today = pd.Timestamp.today() if run_date is None else run_date\n    \n    # Calculate the first business day of the current month\n    first_business_day_this_month = (today.replace(day=1) + BMonthBegin()).normalize()\n    \n    if today >= first_business_day_this_month:\n        # If today is on or after the first business day of the current month\n        return first_business_day_this_month\n    else:\n        # Otherwise, return the first business day of the previous month\n        first_business_day_last_month = (today.replace(day=1) - pd.DateOffset(months=1) + BMonthBegin()).normalize()\n        return first_business_day_last_month\n    \n\ndef _filter_run_latest(df: pd.DataFrame) -> pd.DataFrame:\n    """Return last run id for process."""\n    return df[df["id"].idxmax()]\n\n\ndef _filter_run_bom(df: pd.DataFrame, run_date: Optional[datetime] = None) -> pd.DataFrame:\n    """Return last run id for process finishing \n    on the business day first of month.\n    """\n\n    df["completed_at"] = pd.to_datetime(df["completed_at"]).dt.normalize()\n\n    biz_bom = _most_recent_business_month_start().strftime("%Y-%m-%d")\n    filtered_df = df.loc[df["completed_at"] == biz_bom]\n    if filtered_df.empty:\n        print("NO DATA")\n    \n    return filtered_df.head(1)\n\n\ndef _filter_run_id(df: pd.DataFrame) -> pd.DataFrame:\n    """Fetch extract given run id."""\n    # TODO\n\n\ndef child_rec_process_code(child_rec_process_codes: list) -> Parallelizable[str]:\n    for process_code in child_rec_process_codes:\n        yield process_code\n\n\ndef process_code_runs_endpoint(child_rec_process_code: str) -> str:\n    return f"/processes/{child_rec_process_code}/runs"\n\n\ndef child_process_runs(\n    process_code_runs_endpoint: str, child_rec_process_code: str, duco: DucoCube\n) -> pd.DataFrame:\n    # TODO - get latest runs\n    data = duco.fetch(process_code_runs_endpoint, "get")\n    _df = pd.json_normalize(data)\n    _df.insert(0, "process_code", child_rec_process_code)\n    return _df\n\n\n@extract_fields({\n    "child_process_run_id": int,\n    "child_process_completed_at": pd.Timestamp,\n})\n@pipe_input(\n    step(_filter_run_bom).when(last_run="bom"),\n    step(_filter_run_latest).when(last_run="last"),\n)\ndef child_process_run_info(child_process_runs: pd.DataFrame) -> Dict[int, pd.Timestamp]:\n    """Return run id for process from `last_run` logic."""\n    return {\n        "child_process_run_id": child_process_runs["id"].iloc[0],\n        "child_process_completed_at": pd.to_datetime(child_process_runs["completed_at"].iloc[0]),\n    }\n\n\ndef last_run_result_endpoint(\n    child_rec_process_code: str, child_process_run_id: int, breaks_only: bool = True\n) -> str:\n    query_string = "?breaks_only=true" if breaks_only else ""\n    return f"/processes/{child_rec_process_code}/runs/{child_process_run_id}/xlsx_results{query_string}"\n\n\ndef last_run_result_inputs(\n    last_run_result_endpoint: str, \n    child_process_completed_at: pd.Timestamp\n) -> Tuple[str, pd.Timestamp]:\n    return last_run_result_endpoint, child_process_completed_at\n\n\ndef all_last_run_result_inputs(\n    last_run_result_inputs: Collect[Tuple[str, pd.Timestamp]]\n) -> List[Tuple[str, pd.Timestamp]]:\n    return last_run_result_inputs\n\n\n@config.when_not(last_run="run-id")\ndef last_run_results__default(\n    all_last_run_result_inputs: List[Tuple[str, pd.Timestamp]], \n    duco: DucoCube,\n    save_location: str,\n) -> List[pd.DataFrame]:\n    result_dfs = []\n    for endpoint, completed_at in all_last_run_result_inputs:\n        response = duco.fetch(endpoint, "get", json_response=False)\n        unzipped_results = unzip_bytes(response.content, ".", prefix=f"{completed_at:%Y%m%d}")\n        if unzipped_results:\n            file_path = unzipped_results[0]\n            _df = pd.read_excel(file_path)\n            result_dfs.append(_df)\n        \n    return result_dfs\n\n\n@config.when(last_run="run-id")\ndef last_run_results__runid(\n    process_code: str,\n    run_id: str,\n    duco: DucoCube,\n    save_location: str,\n    breaks_only: bool = True\n) -> List[pd.DataFrame]:\n    """Download run results given `process_code` and `run_id`"""\n    endpoint = f"/processes/{process_code}/runs/{run_id}"\n    resp = duco.fetch(endpoint, "get", json_response=True)\n    completed_at = pd.to_datetime(resp["completed_at"])\n    \n    query_string = "?breaks_only=true" if breaks_only else ""\n    response = duco.fetch(f"{endpoint}/xlsx_results{query_string}", "get", json_response=False)\n    results_dir = Path(save_location) / f"{completed_at:%Y-%m-%d}"\n    results_dir.mkdir(parents=True, exist_ok=True)\n    unzipped_results = unzip_bytes(response.content, save_location, prefix=f"{completed_at:%Y%m%d}")\n\n    if unzipped_results:\n        file_path = unzipped_results[0]\n        _df = pd.read_excel(file_path)\n\n    return _df\n')

File c:\codebase\api-duco\duco-venv\Lib\site-packages\IPython\core\interactiveshell.py:2541, in InteractiveShell.run_cell_magic(self, magic_name, line, cell)
   2539 with self.builtin_trap:
   2540     args = (magic_arg_s, cell)
-> 2541     result = fn(*args, **kwargs)
   2543 # The code below prevents the output from being displayed
   2544 # when using magics with decorator @output_can_be_silenced
   2545 # when the last Python token in the expression is a ';'.
   2546 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File c:\codebase\api-duco\duco-venv\Lib\site-packages\hamilton\plugins\jupyter_magic.py:355, in HamiltonMagics.cell_to_module(self, line, cell)
    353 # build the Driver. the Builder is copied to avoid conflict with the user namespace
    354 builder = base_builder.copy()
--> 355 dr = builder.with_config(config).with_modules(cell_module).build()
    357 # determine final vars
    358 if args.execute not in [True, None]:

File c:\codebase\api-duco\duco-venv\Lib\site-packages\hamilton\driver.py:1907, in Builder.with_config(self, config)
   1900 def with_config(self, config: Dict[str, Any]) -> "Builder":
   1901     """Adds the specified configuration to the config.
   1902     This can be called multilple times -- later calls will take precedence.
   1903 
   1904     :param config: Config to use.
   1905     :return: self
   1906     """
-> 1907     self.config.update(config)
   1908     return self

TypeError: 'ABCMeta' object is not iterable
t
I believe the issue is that you didn't execute cell 1, i.e., the variable
config
that you passed to
--config config
doesn't actually exist in the notebook before you execute cell 2
We could improve the exception message nonetheless
s
Yeah so despite running both cells, the variable
config
did not actually get assigned to the dict. It seems like a namespace issue possibly since the variable type was
hamilton.function_modifiers.config
. Renaming to
config_
fixed the issue.
👍 1
t
ah, yes that too. When you execute the cell with the magic
%%cell_to_module
its content is still evaluated in the notebook before any magic is applied. Catching such error is difficult