77Defines common utilities objects that don't fall in any specific category.
88"""
99
10+ from __future__ import annotations
11+
1012import datetime
1113import functools
1214import logging
1517import subprocess
1618import unicodedata
1719from collections import defaultdict
20+ from collections .abc import Callable , Iterable
1821from html .parser import HTMLParser
1922from itertools import chain
2023from pathlib import Path
2124from pprint import PrettyPrinter
2225from textwrap import TextWrapper
23- from typing import TypeVar
26+ from typing import Any , TypeVar
2427
2528import requests
2629
6467
6568# Monkey-patching the "PrettyPrinter" mapping to handle the "TypeError"
6669# exception raised with "instancemethod": https://bugs.python.org/issue33395
67- class _dispatch (dict ):
68- def get (self , key , default = None ):
70+ class _dispatch (dict [ Any , Any ] ):
71+ def get (self , key : Any , default : Any = None ) -> Any :
6972 try :
7073 return self .__get__ (key , default )
7174 except Exception as error : # noqa: F841, S110
@@ -74,22 +77,22 @@ def get(self, key, default=None):
7477
7578PrettyPrinter ._dispatch = _dispatch () # pyright: ignore
7679
77- ROOT_BUILD_DEFAULT = (Path (__file__ ) / ".." / ".." / ".." / "build" ).resolve ()
80+ ROOT_BUILD_DEFAULT : Path = (Path (__file__ ) / ".." / ".." / ".." / "build" ).resolve ()
7881"""
7982Default build root directory.
8083
8184ROOT_BUILD_DEFAULT : str
8285"""
8386
8487
85- class DocstringDict (dict ):
88+ class DocstringDict (dict [ str , Any ] ):
8689 """
8790 A :class:`dict` sub-class that allows settings a docstring to :class:`dict`
8891 instances.
8992 """
9093
9194
92- def first_item (iterable , default = None ):
95+ def first_item (iterable : Iterable [ Any ] , default : Any = None ) -> Any :
9396 """
9497 Return the first item of given iterable.
9598
@@ -115,7 +118,7 @@ def first_item(iterable, default=None):
115118 return None
116119
117120
118- def common_ancestor (* args ) :
121+ def common_ancestor (* args : Any ) -> Any :
119122 """
120123 Return the common ancestor of given iterables.
121124
@@ -148,7 +151,7 @@ def common_ancestor(*args):
148151 return ancestor
149152
150153
151- def paths_common_ancestor (* args ) :
154+ def paths_common_ancestor (* args : str ) -> str :
152155 """
153156 Return the common ancestor path from given paths.
154157
@@ -174,7 +177,7 @@ def paths_common_ancestor(*args):
174177 return path_ancestor
175178
176179
177- def vivification ():
180+ def vivification () -> defaultdict [ Any , Any ] :
178181 """
179182 Implement supports for vivification of the underlying dict like
180183 data-structure, magical!
@@ -196,7 +199,9 @@ def vivification():
196199 return defaultdict (vivification )
197200
198201
199- def vivified_to_dict (vivified ):
202+ def vivified_to_dict (
203+ vivified : defaultdict [Any , Any ] | dict [Any , Any ],
204+ ) -> dict [Any , Any ]:
200205 """
201206 Convert given vivified data-structure to dictionary.
202207
@@ -222,7 +227,12 @@ def vivified_to_dict(vivified):
222227 return vivified
223228
224229
225- def message_box (message , width = 79 , padding = 3 , print_callable = print ):
230+ def message_box (
231+ message : str ,
232+ width : int = 79 ,
233+ padding : int = 3 ,
234+ print_callable : Callable [[str ], None ] = print ,
235+ ) -> bool :
226236 """
227237 Print a message inside a box.
228238
@@ -278,7 +288,7 @@ def message_box(message, width=79, padding=3, print_callable=print):
278288
279289 ideal_width = width - padding * 2 - 2
280290
281- def inner (text ) :
291+ def inner (text : str ) -> str :
282292 """Format and pads inner text for the message box."""
283293
284294 return "*{0}{1}{2}{0}*" .format (
@@ -303,7 +313,7 @@ def inner(text):
303313 return True
304314
305315
306- def is_colour_installed (raise_exception = False ):
316+ def is_colour_installed (raise_exception : bool = False ) -> bool :
307317 """
308318 Return if *Colour* is installed and available.
309319
@@ -335,7 +345,7 @@ def is_colour_installed(raise_exception=False):
335345 return False
336346
337347
338- def is_jsonpickle_installed (raise_exception = False ):
348+ def is_jsonpickle_installed (raise_exception : bool = False ) -> bool :
339349 """
340350 Return if *jsonpickle* is installed and available.
341351
@@ -368,7 +378,7 @@ def is_jsonpickle_installed(raise_exception=False):
368378 return False
369379
370380
371- def is_networkx_installed (raise_exception = False ):
381+ def is_networkx_installed (raise_exception : bool = False ) -> bool :
372382 """
373383 Return if *NetworkX* is installed and available.
374384
@@ -400,7 +410,7 @@ def is_networkx_installed(raise_exception=False):
400410 return False
401411
402412
403- REQUIREMENTS_TO_CALLABLE = DocstringDict (
413+ REQUIREMENTS_TO_CALLABLE : DocstringDict = DocstringDict (
404414 {
405415 "Colour" : is_colour_installed ,
406416 "jsonpickle" : is_jsonpickle_installed ,
@@ -415,7 +425,7 @@ def is_networkx_installed(raise_exception=False):
415425"""
416426
417427
418- def required (* requirements ) :
428+ def required (* requirements : str ) -> Callable [[ Callable [..., Any ]], Callable [..., Any ]] :
419429 """
420430 Decorate a function to check whether various ancillary package requirements
421431 are satisfied.
@@ -430,11 +440,11 @@ def required(*requirements):
430440 object
431441 """
432442
433- def wrapper (function ) :
443+ def wrapper (function : Callable [..., Any ]) -> Callable [..., Any ] :
434444 """Wrap given function wrapper."""
435445
436446 @functools .wraps (function )
437- def wrapped (* args , ** kwargs ) :
447+ def wrapped (* args : Any , ** kwargs : Any ) -> Any :
438448 """Wrap given function."""
439449
440450 for requirement in requirements :
@@ -447,7 +457,7 @@ def wrapped(*args, **kwargs):
447457 return wrapper
448458
449459
450- def is_string (a ) :
460+ def is_string (a : Any ) -> bool :
451461 """
452462 Return if given :math:`a` variable is a *string* like variable.
453463
@@ -472,7 +482,7 @@ def is_string(a):
472482 return isinstance (a , str )
473483
474484
475- def is_iterable (a ) :
485+ def is_iterable (a : Any ) -> bool :
476486 """
477487 Return if given :math:`a` variable is iterable.
478488
@@ -497,7 +507,7 @@ def is_iterable(a):
497507 return is_string (a ) or (bool (getattr (a , "__iter__" , False )))
498508
499509
500- def git_describe ():
510+ def git_describe () -> str :
501511 """
502512 Describe the current *OpenColorIO Configuration for ACES* *git* version.
503513
@@ -512,7 +522,7 @@ def git_describe():
512522 try : # pragma: no cover
513523 version = subprocess .check_output (
514524 ["git" , "describe" ], # noqa: S603, S607
515- cwd = opencolorio_config_aces .__path__ [0 ],
525+ cwd = opencolorio_config_aces .__path__ [0 ], # pyright: ignore
516526 stderr = subprocess .STDOUT ,
517527 ).strip ()
518528 version = version .decode ("utf-8" )
@@ -525,7 +535,7 @@ def git_describe():
525535# TODO: Numpy currently comes via "Colour", we might want that to be an
526536# explicit dependency in the future.
527537@required ("Colour" )
528- def matrix_3x3_to_4x4 (M ) :
538+ def matrix_3x3_to_4x4 (M : Any ) -> list [ float ] :
529539 """
530540 Convert given 3x3 matrix :math:`M` to a raveled 4x4 matrix.
531541
@@ -548,7 +558,7 @@ def matrix_3x3_to_4x4(M):
548558 return np .ravel (M_I ).tolist ()
549559
550560
551- def multi_replace (name , patterns ) :
561+ def multi_replace (name : str , patterns : dict [ str , str ]) -> str :
552562 """
553563 Update given name by applying in succession the given patterns and
554564 substitutions.
@@ -581,10 +591,10 @@ def multi_replace(name, patterns):
581591
582592
583593def validate_method (
584- method ,
585- valid_methods ,
586- message = '"{0}" method is invalid, it must be one of {1}!' ,
587- ):
594+ method : str ,
595+ valid_methods : list [ str ] ,
596+ message : str = '"{0}" method is invalid, it must be one of {1}!' ,
597+ ) -> str :
588598 """
589599 Validate whether given method exists in the given valid methods and
590600 returns the method lower cased.
@@ -623,7 +633,7 @@ def validate_method(
623633 return method_lower
624634
625635
626- def google_sheet_title (url ) :
636+ def google_sheet_title (url : str ) -> str :
627637 """
628638 Return the title from given *Google Sheet* url.
629639
@@ -649,20 +659,24 @@ def google_sheet_title(url):
649659 """
650660
651661 class Parser (HTMLParser ):
652- def __init__ (self ):
662+ def __init__ (self ) -> None :
653663 HTMLParser .__init__ (self )
654- self .in_title = []
655- self .title = None
656-
657- def handle_starttag (self , tag , attrs ): # noqa: ARG002
664+ self .in_title : list [str ] = []
665+ self .title : str | None = None
666+
667+ def handle_starttag (
668+ self ,
669+ tag : str ,
670+ attrs : list [tuple [str , str | None ]], # noqa: ARG002
671+ ) -> None :
658672 if tag == "title" :
659673 self .in_title .append (tag )
660674
661- def handle_endtag (self , tag ) :
675+ def handle_endtag (self , tag : str ) -> None :
662676 if tag == "title" :
663677 self .in_title .pop ()
664678
665- def handle_data (self , data ) :
679+ def handle_data (self , data : str ) -> None :
666680 if self .in_title :
667681 self .title = data
668682
@@ -672,10 +686,13 @@ def handle_data(self, data):
672686 parser = Parser ()
673687 parser .feed (requests .get (url , timeout = 60 ).text )
674688
689+ if parser .title is None :
690+ raise ValueError (f"Could not extract title from URL: { url } " )
691+
675692 return parser .title .rsplit ("-" , 1 )[0 ].strip ()
676693
677694
678- def slugify (object_ , allow_unicode = False ):
695+ def slugify (object_ : Any , allow_unicode : bool = False ) -> str :
679696 """
680697 Generate a *SEO* friendly and human-readable slug from given object.
681698
@@ -725,7 +742,7 @@ def slugify(object_, allow_unicode=False):
725742 return re .sub (r"[-\s]+" , "-" , value ).strip ("-_" )
726743
727744
728- def attest (condition , message = "" ):
745+ def attest (condition : bool , message : str = "" ) -> None :
729746 """
730747 Provide the `assert` statement functionality without being disabled by
731748 optimised Python execution.
@@ -742,7 +759,7 @@ def attest(condition, message=""):
742759 raise AssertionError (message )
743760
744761
745- def timestamp ():
762+ def timestamp () -> str :
746763 """
747764 Return a timestamp description.
748765
0 commit comments