Skip to content

convert

convert

Data Conversion Utilities

This module provides comprehensive utilities for converting between different data formats, types, and structures. Includes datetime conversions, string formatting, data validation, and dataframe operations.

Functions:

Name Description
print_md

Display markdown in Jupyter notebooks

convert_epoch_millisecond_to_datetime

Convert epoch milliseconds to datetime

convert_datetime_to_epoch_millisecond

Convert datetime to epoch milliseconds

convert_string_to_datetime

Parse string to datetime object

convert_utc_to_timezone

Convert a UTC datetime to another IANA timezone

convert_python_to_ast_module

Parse Python code to AST module

extract_ast_functions

Extract function definitions from AST

convert_programming_text_to_title_case

Convert code names to title case

convert_snake_to_pascal

Convert snake_case to camelCase

convert_str_to_snake_case

Convert strings to snake_case

is_valid_email

Validate email format

convert_string_to_bool

Convert string to boolean

concat_list_dataframe

Concatenate list of DataFrames

merge_dict

Deep merge dictionaries

Exception Classes

InvalidEmail: Raised when email validation fails ConcatDataframeInvalidElementError: Raised when dataframe concat fails

Example

Datetime conversions

epoch = convert_datetime_to_epoch_millisecond(datetime.now()) dt = convert_epoch_millisecond_to_datetime(epoch)

String formatting

title = convert_programming_text_to_title_case("get_user_data")

Returns: "Get User Data"

Email validation

try: ... is_valid_email("user@example.com") # Returns True ... except InvalidEmail: ... print("Invalid email")

ConcatDataframeError

ConcatDataframeError(
    element: Any, operation: str = "concatenation"
)

Bases: UtilityError

Raised when dataframe concatenation operations fail.

This exception is raised when attempting to concatenate objects that are not pandas DataFrames or when concatenation operations fail.

Parameters:

Name Type Description Default
element Any

The invalid element that caused the error

required
operation str

The operation being performed

'concatenation'
Example

try: ... concat_dataframes([df1, "not_a_dataframe", df2]) ... except ConcatDataframeError as e: ... print(f"Error: {e}") Error: Invalid element type for concatenation:

Source code in src/crew_dcs/utils/exceptions.py
100
101
102
103
104
105
def __init__(self, element: Any, operation: str = "concatenation"):
    element_type = type(element).__name__
    message = f"Invalid element type for {operation}: {element_type}"
    super().__init__(message, {"element": element, "type": element_type})
    self.element = element
    self.operation = operation

InvalidEmailError

InvalidEmailError(email: str)

Bases: UtilityError

Raised when email validation fails.

This exception is raised when a provided email address does not match the expected email format pattern.

Parameters:

Name Type Description Default
email str

The invalid email address that caused the error

required
Example

try: ... validate_email("invalid-email") ... except InvalidEmailError as e: ... print(f"Error: {e}") Error: Invalid email format: "invalid-email"

Source code in src/crew_dcs/utils/exceptions.py
75
76
77
78
def __init__(self, email: str):
    message = f'Invalid email format: "{email}"'
    super().__init__(message, {"email": email})
    self.email = email

concat_list_dataframe

concat_list_dataframe(df_ls: list[object]) -> object

Take a list of DataFrames and concatenate them into one DataFrame.

Parameters:

Name Type Description Default
df_ls list[Any]

list of pandas DataFrames to concatenate

required

Returns:

Name Type Description
Any object

Concatenated DataFrame (returns Any due to optional pandas dependency)

Raises:

Type Description
ImportError

If pandas is not available

ConcatDataframe_InvalidElement

If any element is not a DataFrame

Example

df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]}) df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]}) result = concat_list_dataframe([df1, df2]) print(len(result)) # 4 rows

Note

Requires pandas to be installed. Uses inner join for concatenation and resets the index.

Source code in src/crew_dcs/utils/convert.py
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
def concat_list_dataframe(df_ls: list[object]) -> object:
    """
    Take a list of DataFrames and concatenate them into one DataFrame.

    Args:
        df_ls (list[Any]): list of pandas DataFrames to concatenate

    Returns:
        Any: Concatenated DataFrame (returns Any due to optional pandas dependency)

    Raises:
        ImportError: If pandas is not available
        ConcatDataframe_InvalidElement: If any element is not a DataFrame

    Example:
        >>> df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
        >>> df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})
        >>> result = concat_list_dataframe([df1, df2])
        >>> print(len(result))  # 4 rows

    Note:
        Requires pandas to be installed. Uses inner join for concatenation
        and resets the index.
    """
    df = None
    for elem in df_ls:
        if not isinstance(elem, pd.DataFrame):
            raise ConcatDataframeError(elem)

        if len(elem.index) == 0:
            continue

        if df is None:
            df = elem
        else:
            df = pd.concat([df, elem], join="inner").reset_index(drop=True)

    return df

convert_datetime_to_epoch_millisecond

convert_datetime_to_epoch_millisecond(
    datetime: datetime | None,
) -> int | None

Convert datetime object to Epoch time with milliseconds.

Parameters:

Name Type Description Default
datetime datetime

Datetime object to convert. If None, returns None.

required

Returns:

Type Description
int | None

int, optional: Epoch time in milliseconds, or None if input is None

Example

import datetime as dt dt_obj = dt.datetime(2021, 1, 1) epoch = convert_datetime_to_epoch_millisecond(dt_obj) print(epoch) # 1609459200000

Source code in src/crew_dcs/utils/convert.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def convert_datetime_to_epoch_millisecond(
    datetime: dt.datetime | None,
) -> int | None:
    """
    Convert datetime object to Epoch time with milliseconds.

    Args:
        datetime (datetime, optional): Datetime object to convert. If None, returns None.

    Returns:
        int, optional: Epoch time in milliseconds, or None if input is None

    Example:
        >>> import datetime as dt
        >>> dt_obj = dt.datetime(2021, 1, 1)
        >>> epoch = convert_datetime_to_epoch_millisecond(dt_obj)
        >>> print(epoch)  # 1609459200000
    """
    return int(datetime.timestamp() * 1000) if datetime else None

convert_epoch_millisecond_to_datetime

convert_epoch_millisecond_to_datetime(
    epoch: int | None,
) -> datetime | None

Convert Epoch time with milliseconds to a timezone-aware UTC datetime object.

Domo epochs are UTC. This always attaches tzinfo=UTC rather than datetime.fromtimestamp()'s default of interpreting the epoch in the host's local timezone (which returns a naive datetime and, on any host not running TZ=UTC, silently produces the wrong instant). Compare or subtract the result against another aware datetime — a naive one raises TypeError: can't compare offset-naive and offset-aware datetimes, which surfaces this class of bug immediately instead of shifting output by the host's offset.

Parameters:

Name Type Description Default
epoch int

Epoch time in milliseconds. If None or 0, returns None (0 -- 1970-01-01T00:00:00Z -- is falsy and hits the same guard as None; this mirrors the pre-existing contract and is not changed here).

required

Returns:

Type Description
datetime | None

datetime, optional: Timezone-aware UTC datetime object, or None if input is None/0

Example

epoch_time = 1609459200000 # 2021-01-01 00:00:00 UTC dt_obj = convert_epoch_millisecond_to_datetime(epoch_time) print(dt_obj) 2021-01-01 00:00:00+00:00

Source code in src/crew_dcs/utils/convert.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def convert_epoch_millisecond_to_datetime(
    epoch: int | None,
) -> dt.datetime | None:
    """
    Convert Epoch time with milliseconds to a timezone-aware UTC datetime object.

    Domo epochs are UTC. This always attaches `tzinfo=UTC` rather than
    `datetime.fromtimestamp()`'s default of interpreting the epoch in the
    host's local timezone (which returns a naive datetime and, on any host
    not running `TZ=UTC`, silently produces the wrong instant). Compare or
    subtract the result against another aware datetime — a naive one raises
    `TypeError: can't compare offset-naive and offset-aware datetimes`,
    which surfaces this class of bug immediately instead of shifting output
    by the host's offset.

    Args:
        epoch (int, optional): Epoch time in milliseconds. If None or 0,
            returns None (0 -- 1970-01-01T00:00:00Z -- is falsy and hits the
            same guard as None; this mirrors the pre-existing contract and
            is not changed here).

    Returns:
        datetime, optional: Timezone-aware UTC datetime object, or None if
            input is None/0

    Example:
        >>> epoch_time = 1609459200000  # 2021-01-01 00:00:00 UTC
        >>> dt_obj = convert_epoch_millisecond_to_datetime(epoch_time)
        >>> print(dt_obj)
        2021-01-01 00:00:00+00:00
    """
    return dt.datetime.fromtimestamp(epoch / 1000.0, tz=dt.UTC) if epoch else None

convert_programming_text_to_title_case

convert_programming_text_to_title_case(
    clean_str: str,
) -> str

Convert function names from programming conventions to human-readable display format.

Transforms snake_case and camelCase function names into Title Case format suitable for user interfaces. Preserves leading underscores as spaces to maintain private function indicators.

Parameters:

Name Type Description Default
clean_str str

The original function name in snake_case or camelCase

required

Returns:

Name Type Description
str str

The formatted display name in Title Case

Examples:

>>> convert_programming_text_to_title_case('getUserData')
'Get User Data'
>>> convert_programming_text_to_title_case('calculate_total_sum')
'Calculate Total Sum'
>>> convert_programming_text_to_title_case('_private_method')
' Private Method'
Note

Leading underscores are converted to spaces to preserve the indication that these are private/internal methods.

Source code in src/crew_dcs/utils/convert.py
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def convert_programming_text_to_title_case(clean_str: str) -> str:
    """
    Convert function names from programming conventions to human-readable display format.

    Transforms snake_case and camelCase function names into Title Case format suitable
    for user interfaces. Preserves leading underscores as spaces to maintain private
    function indicators.

    Args:
        clean_str (str): The original function name in snake_case or camelCase

    Returns:
        str: The formatted display name in Title Case

    Examples:
        >>> convert_programming_text_to_title_case('getUserData')
        'Get User Data'
        >>> convert_programming_text_to_title_case('calculate_total_sum')
        'Calculate Total Sum'
        >>> convert_programming_text_to_title_case('_private_method')
        ' Private Method'

    Note:
        Leading underscores are converted to spaces to preserve the indication
        that these are private/internal methods.
    """
    leading_underscores = ""
    working_str = clean_str

    # Extract leading underscores and convert to spaces
    while working_str.startswith("_"):
        leading_underscores += " "
        working_str = working_str[1:]

    # Convert camelCase to spaced format
    spaced_name = re.sub(r"([a-z])([A-Z])", r"\1 \2", working_str)
    # Convert snake_case to spaced format
    spaced_name = spaced_name.replace("_", " ")

    # Capitalize each word and join
    return leading_underscores + " ".join(
        word.capitalize() for word in spaced_name.split()
    )

convert_python_to_ast_module

convert_python_to_ast_module(
    python_str: str | None = None,
    python_file_path: str | None = None,
    return_str: bool = False,
) -> Module | str

Parse Python code string or file and return its AST module.

Parameters:

Name Type Description Default
python_str str

Python code string to parse

None
python_file_path str

Path to Python file to parse

None
return_str bool

If True, return the source string instead of AST

False

Returns:

Type Description
Module | str

ast.Module or str: AST module of the parsed code, or source string if return_str=True

Raises:

Type Description
ValueError

If neither python_str nor python_file_path is provided

FileNotFoundError

If python_file_path doesn't exist

SyntaxError

If the Python code has syntax errors

Example

code = "def hello(): return 'world'" ast_module = convert_python_to_ast_module(python_str=code) functions = extract_ast_functions(ast_module)

Source code in src/crew_dcs/utils/convert.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def convert_python_to_ast_module(
    python_str: str | None = None,
    python_file_path: str | None = None,
    return_str: bool = False,
) -> ast.Module | str:
    """
    Parse Python code string or file and return its AST module.

    Args:
        python_str (str, optional): Python code string to parse
        python_file_path (str, optional): Path to Python file to parse
        return_str (bool): If True, return the source string instead of AST

    Returns:
        ast.Module or str: AST module of the parsed code, or source string if return_str=True

    Raises:
        ValueError: If neither python_str nor python_file_path is provided
        FileNotFoundError: If python_file_path doesn't exist
        SyntaxError: If the Python code has syntax errors

    Example:
        >>> code = "def hello(): return 'world'"
        >>> ast_module = convert_python_to_ast_module(python_str=code)
        >>> functions = extract_ast_functions(ast_module)
    """
    if not python_str and python_file_path:
        try:
            with open(python_file_path, encoding="utf-8") as source:
                python_str = source.read()
        except FileNotFoundError:
            raise FileNotFoundError(f"Python file not found: {python_file_path}")  # noqa: B904

    if not python_str:
        raise ValueError("Must provide either python_str or python_file_path")

    if return_str:
        return python_str

    return ast.parse(python_str)

convert_snake_to_pascal

convert_snake_to_pascal(clean_str: str) -> str

Convert snake_case string to camelCase (pascal case with lowercase first letter).

Parameters:

Name Type Description Default
clean_str str

Snake case string to convert

required

Returns:

Name Type Description
str str

String in camelCase format

Example

convert_snake_to_pascal('user_name_field') 'userNameField' convert_snake_to_pascal('api_key') 'apiKey'

Source code in src/crew_dcs/utils/convert.py
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
def convert_snake_to_pascal(clean_str: str) -> str:
    """
    Convert snake_case string to camelCase (pascal case with lowercase first letter).

    Args:
        clean_str (str): Snake case string to convert

    Returns:
        str: String in camelCase format

    Example:
        >>> convert_snake_to_pascal('user_name_field')
        'userNameField'
        >>> convert_snake_to_pascal('api_key')
        'apiKey'
    """
    clean_str = clean_str.replace("_", " ").title().replace(" ", "")
    return clean_str[0].lower() + clean_str[1:] if clean_str else ""

convert_str_to_snake_case

convert_str_to_snake_case(
    text_str: str,
    is_only_alphanumeric: bool = False,
    is_pascal: bool = False,
) -> str

Convert various string formats to snake_case.

Can handle conversion from PascalCase/camelCase to snake_case, and optionally filter to only alphanumeric characters.

Parameters:

Name Type Description Default
text_str str

String to convert to snake_case

required
is_only_alphanumeric bool

If True, remove non-alphanumeric characters

False
is_pascal bool

If True, treat input as PascalCase/camelCase

False

Returns:

Name Type Description
str str

String converted to snake_case format

Example

convert_str_to_snake_case('UserNameField', is_pascal=True) 'user_name_field' convert_str_to_snake_case('User Name Field') 'user_name_field' convert_str_to_snake_case('User-Name!Field', is_only_alphanumeric=True) 'usernamefield'

Source code in src/crew_dcs/utils/convert.py
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
def convert_str_to_snake_case(
    text_str: str, is_only_alphanumeric: bool = False, is_pascal: bool = False
) -> str:
    """
    Convert various string formats to snake_case.

    Can handle conversion from PascalCase/camelCase to snake_case, and optionally
    filter to only alphanumeric characters.

    Args:
        text_str (str): String to convert to snake_case
        is_only_alphanumeric (bool): If True, remove non-alphanumeric characters
        is_pascal (bool): If True, treat input as PascalCase/camelCase

    Returns:
        str: String converted to snake_case format

    Example:
        >>> convert_str_to_snake_case('UserNameField', is_pascal=True)
        'user_name_field'
        >>> convert_str_to_snake_case('User Name Field')
        'user_name_field'
        >>> convert_str_to_snake_case('User-Name!Field', is_only_alphanumeric=True)
        'usernamefield'
    """
    if is_pascal:
        # Convert PascalCase/camelCase to snake_case
        text_str = re.sub(r"(?<!^)(?=[A-Z])", "_", text_str)

    # Replace spaces with underscores and convert to lowercase
    text_str = text_str.replace(" ", "_").lower()

    if is_only_alphanumeric:
        # Remove all non-alphanumeric characters
        text_str = re.sub(r"\W+", "", text_str)

    return text_str

convert_string_to_bool

convert_string_to_bool(v: str | bool) -> bool

Convert string representation to boolean value.

Recognizes common string representations of boolean values and converts them to actual boolean type.

Parameters:

Name Type Description Default
v str or bool

Value to convert to boolean

required

Returns:

Name Type Description
bool bool

Converted boolean value

Example

convert_string_to_bool("yes") True convert_string_to_bool("false") False convert_string_to_bool("1") True convert_string_to_bool(True) True

Source code in src/crew_dcs/utils/convert.py
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
def convert_string_to_bool(v: str | bool) -> bool:
    """
    Convert string representation to boolean value.

    Recognizes common string representations of boolean values and converts
    them to actual boolean type.

    Args:
        v (str or bool): Value to convert to boolean

    Returns:
        bool: Converted boolean value

    Example:
        >>> convert_string_to_bool("yes")
        True
        >>> convert_string_to_bool("false")
        False
        >>> convert_string_to_bool("1")
        True
        >>> convert_string_to_bool(True)
        True
    """
    if isinstance(v, bool):
        return v
    return str(v).lower() in ("yes", "true", "t", "1")

convert_string_to_datetime

convert_string_to_datetime(
    datestr: str | None,
) -> datetime | None

Convert a date string to datetime object using flexible parsing.

Parameters:

Name Type Description Default
datestr str

Date string to parse. If None or empty, returns None.

required

Returns:

Type Description
datetime | None

datetime, optional: Parsed datetime object, or None if input is None/empty

Raises:

Type Description
ImportError

If dateutil is not available

ValueError

If the date string cannot be parsed

Example

dt_obj = convert_string_to_datetime("2021-01-01 12:30:00") dt_obj = convert_string_to_datetime("Jan 1, 2021") dt_obj = convert_string_to_datetime("2021/01/01")

Source code in src/crew_dcs/utils/convert.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def convert_string_to_datetime(datestr: str | None) -> dt.datetime | None:
    """
    Convert a date string to datetime object using flexible parsing.

    Args:
        datestr (str, optional): Date string to parse. If None or empty, returns None.

    Returns:
        datetime, optional: Parsed datetime object, or None if input is None/empty

    Raises:
        ImportError: If dateutil is not available
        ValueError: If the date string cannot be parsed

    Example:
        >>> dt_obj = convert_string_to_datetime("2021-01-01 12:30:00")
        >>> dt_obj = convert_string_to_datetime("Jan 1, 2021")
        >>> dt_obj = convert_string_to_datetime("2021/01/01")
    """
    if not datestr:
        return None

    return date_parser.parse(datestr)

convert_utc_to_timezone

convert_utc_to_timezone(
    utc_datetime: datetime, target_timezone: str
) -> datetime

Convert a UTC datetime to the target timezone using stdlib zoneinfo.

A naive utc_datetime (no tzinfo) is treated as already being in UTC — this is the shape returned by most Domo API epoch-millisecond fields once parsed. An aware utc_datetime is converted to UTC first (regardless of its original offset) before being re-expressed in target_timezone, so the conversion is correct even if the caller passes a non-UTC aware value.

Unlike a naive "attach UTC and hope", an unknown/misspelled IANA timezone name fails loudly instead of silently falling back to UTC.

Parameters:

Name Type Description Default
utc_datetime datetime

A UTC datetime (naive, or aware in any timezone).

required
target_timezone str

IANA timezone name, e.g. "America/Denver".

required

Returns:

Type Description
datetime

A timezone-aware datetime expressed in target_timezone.

Raises:

Type Description
ValueError

If target_timezone is not a recognized IANA timezone.

Example

import datetime as dt convert_utc_to_timezone( ... dt.datetime(2024, 1, 1, 12, 0), "America/Denver" ... ) datetime.datetime(2024, 1, 1, 5, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Denver'))

Source code in src/crew_dcs/utils/convert.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def convert_utc_to_timezone(
    utc_datetime: dt.datetime,
    target_timezone: str,
) -> dt.datetime:
    """Convert a UTC datetime to the target timezone using stdlib `zoneinfo`.

    A naive `utc_datetime` (no `tzinfo`) is treated as already being in UTC —
    this is the shape returned by most Domo API epoch-millisecond fields once
    parsed. An aware `utc_datetime` is converted to UTC first (regardless of
    its original offset) before being re-expressed in `target_timezone`, so
    the conversion is correct even if the caller passes a non-UTC aware value.

    Unlike a naive "attach UTC and hope", an unknown/misspelled IANA timezone
    name fails loudly instead of silently falling back to UTC.

    Args:
        utc_datetime: A UTC datetime (naive, or aware in any timezone).
        target_timezone: IANA timezone name, e.g. "America/Denver".

    Returns:
        A timezone-aware datetime expressed in `target_timezone`.

    Raises:
        ValueError: If `target_timezone` is not a recognized IANA timezone.

    Example:
        >>> import datetime as dt
        >>> convert_utc_to_timezone(
        ...     dt.datetime(2024, 1, 1, 12, 0), "America/Denver"
        ... )
        datetime.datetime(2024, 1, 1, 5, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Denver'))
    """
    if utc_datetime.tzinfo is None:
        utc_datetime = utc_datetime.replace(tzinfo=dt.UTC)
    else:
        utc_datetime = utc_datetime.astimezone(dt.UTC)

    try:
        target_zone = ZoneInfo(target_timezone)
    except (ZoneInfoNotFoundError, ValueError) as exc:
        raise ValueError(
            f"Unknown IANA timezone: {target_timezone!r}. "
            "Expected a value like 'UTC' or 'America/Denver'."
        ) from exc

    return utc_datetime.astimezone(target_zone)

extract_ast_functions

extract_ast_functions(
    ast_module: Module,
) -> list[FunctionDef]

Extract all function definitions from an AST module.

Parameters:

Name Type Description Default
ast_module Module

AST module to extract functions from

required

Returns:

Type Description
list[FunctionDef]

list[ast.FunctionDef]: list of function definition nodes

Example

code = ''' ... def func1(): ... pass ... ... class MyClass: ... def method1(self): ... pass ... ... def func2(): ... pass ... ''' ast_module = convert_python_to_ast_module(python_str=code) functions = extract_ast_functions(ast_module) print(len(functions)) # 2 (only top-level functions)

Note

This only extracts top-level function definitions, not methods inside classes.

Source code in src/crew_dcs/utils/convert.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
def extract_ast_functions(ast_module: ast.Module) -> list[ast.FunctionDef]:
    """
    Extract all function definitions from an AST module.

    Args:
        ast_module (ast.Module): AST module to extract functions from

    Returns:
        list[ast.FunctionDef]: list of function definition nodes

    Example:
        >>> code = '''
        ... def func1():
        ...     pass
        ...
        ... class MyClass:
        ...     def method1(self):
        ...         pass
        ...
        ... def func2():
        ...     pass
        ... '''
        >>> ast_module = convert_python_to_ast_module(python_str=code)
        >>> functions = extract_ast_functions(ast_module)
        >>> print(len(functions))  # 2 (only top-level functions)

    Note:
        This only extracts top-level function definitions, not methods
        inside classes.
    """
    return [node for node in ast_module.body if isinstance(node, ast.FunctionDef)]

is_valid_email

is_valid_email(email: str) -> bool

Test if provided string is a valid email format.

Uses regex pattern matching to validate email format according to standard email format requirements.

Parameters:

Name Type Description Default
email str

Email string to validate

required

Returns:

Name Type Description
bool bool

True if email format is valid

Raises:

Type Description
InvalidEmail

If email format is invalid

Example

is_valid_email("user@example.com") True is_valid_email("invalid-email") InvalidEmail: Invalid email format: "invalid-email"

Source code in src/crew_dcs/utils/convert.py
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
def is_valid_email(email: str) -> bool:
    """
    Test if provided string is a valid email format.

    Uses regex pattern matching to validate email format according to
    standard email format requirements.

    Args:
        email (str): Email string to validate

    Returns:
        bool: True if email format is valid

    Raises:
        InvalidEmail: If email format is invalid

    Example:
        >>> is_valid_email("user@example.com")
        True
        >>> is_valid_email("invalid-email")
        InvalidEmail: Invalid email format: "invalid-email"
    """
    pattern = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b"

    if re.fullmatch(pattern, email):
        return True
    raise InvalidEmailError(email=email)

merge_dict

merge_dict(
    source: dict[str, object],
    destination: dict[str, object],
) -> dict[str, object]

Deep merge source dictionary into destination dictionary.

Recursively merges nested dictionaries, with source values taking precedence over destination values for conflicts.

Parameters:

Name Type Description Default
source dict[str, Any]

Dictionary to merge from

required
destination dict[str, Any]

Dictionary to merge into (modified in place)

required

Returns:

Type Description
dict[str, object]

dict[str, Any]: The merged destination dictionary

Example

dest = {"a": 1, "b": {"c": 2, "d": 3}} src = {"b": {"d": 4, "e": 5}, "f": 6} result = merge_dict(src, dest) print(result)

{"a": 1, "b": {"c": 2, "d": 4, "e": 5}, "f": 6}

Note

The destination dictionary is modified in place. For nested dictionaries, the merge is recursive.

Source code in src/crew_dcs/utils/convert.py
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
def merge_dict(
    source: dict[str, object], destination: dict[str, object]
) -> dict[str, object]:
    """
    Deep merge source dictionary into destination dictionary.

    Recursively merges nested dictionaries, with source values taking precedence
    over destination values for conflicts.

    Args:
        source (dict[str, Any]): Dictionary to merge from
        destination (dict[str, Any]): Dictionary to merge into (modified in place)

    Returns:
        dict[str, Any]: The merged destination dictionary

    Example:
        >>> dest = {"a": 1, "b": {"c": 2, "d": 3}}
        >>> src = {"b": {"d": 4, "e": 5}, "f": 6}
        >>> result = merge_dict(src, dest)
        >>> print(result)
        # {"a": 1, "b": {"c": 2, "d": 4, "e": 5}, "f": 6}

    Note:
        The destination dictionary is modified in place. For nested dictionaries,
        the merge is recursive.
    """
    for key, value in source.items():
        if isinstance(value, dict):
            # Get existing node or create empty dict
            node = destination.setdefault(key, {})
            merge_dict(value, node)
        else:
            destination[key] = value

    return destination

print_md

print_md(md_str: str) -> None

Display markdown string in Jupyter notebook environment.

Parameters:

Name Type Description Default
md_str str

Markdown string to display

required

Raises:

Type Description
ImportError

If IPython is not available

Example

print_md("# Header\nBold text")

Note

This function only works in Jupyter notebook environments where IPython.display is available.

Source code in src/crew_dcs/utils/convert.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def print_md(md_str: str) -> None:
    """
    Display markdown string in Jupyter notebook environment.

    Args:
        md_str (str): Markdown string to display

    Raises:
        ImportError: If IPython is not available

    Example:
        >>> print_md("# Header\\n**Bold text**")

    Note:
        This function only works in Jupyter notebook environments where
        IPython.display is available.
    """
    display_markdown(md_str, raw=True)