util.utils module

Miscellaneous utility functions.

Created on Oct 14, 2024

Code author: Rob Kremer <robkremer@me.com>


util.utils.makeParserFromFunc(fn: ~typing.Callable, ignoreParam: ~typing.Callable[[str], bool] = <function <lambda>>, parser: ~argparse.ArgumentParser | None = None, typeHandlers: dict[str, ~typing.Callable[[str], ~typing.Any]] = {}) → ArgumentParser
Parameters:
  • fn (Callable) – The function to make a parser for.

  • ignoreParam (Callable[[str], bool]) – A filter that, given the name of a parameter return True iff the param SHOULD be ignored. By default, all params will be used.

  • parser (argparse.ArgumentParser) – If specified, this parser will have the params of fn added. If parser is absent, a new argparse.ArgumentParser will be created.

  • typeHandlers (dict[str, Callable[[str], Any]])

Returns:

The parser.

Return type:

argparse.ArgumentParser

Limitations to be aware of:

  • Complex annotations (list[int], Optional[str], unions) need extra handling

  • *args / **kwargs don’t map cleanly to argparse

But for straightforward typed functions it works well.

util.utils.resolveAliases(x: str, table: dict[str, str], raiseOnError: bool = True) → str
Parameters:
  • x – The string to analyze.

  • table – The dict from which to lookup keys in.

  • raiseOnError – If True a syntax error or a failed lookup in table will raise an exception, otherwise the $ expression will be left intact.

From a string, substitute any contained <key> expressions “${<key>}” for dict[“key”]. Eg: x == “a${b}c” and table == {“b”:”BB”} returns “aBBc”.

Returns:

Return type:

str

util.utils.splitOnSeparator(s: str, separator: str = '.') → List[str]

Split a string on ‘.’ while respecting single- and double-quoted substrings.

Dots inside quoted regions are treated as literal characters and do not trigger a split. Quotes are stripped from the output tokens. A quoted region must span an entire token — a quote may only open at the start of a token and must close at the end of a token (immediately before a ‘.’ or end of string). Nested quotes of the opposite kind are allowed inside a quoted region.

Parameters:
  • s (str) – The input string to split.

  • separator (str)

Returns:

A list of substrings split on unquoted dots, with quotes removed.

Return type:

List[str]

Raises:

ValueError – If a quote appears mid-token, a quoted block is not properly terminated, or characters follow a closing quote before the next ‘.’ or end of string.

util.utils.quoteIfNecessary(s: str) → str

If the string contains spaces return surrounded with quotes, otherwise just return the string.

Parameters:

s (str)

Returns:

Return type:

str

util.utils._CMD_MASK = 8

The bit of the key shortcuts are made with: Command (Mod1) on macOS, Control elsewhere (see util.platformSupport.SHORTCUT). (Not Mod1 or Mod2 off the Mac: on X11, Mod1 is Alt and Mod2 is usually Num Lock; on Windows, 0x8 is Num Lock.)

util.utils._ALT_MASK = 16

The Option (Mod2) bit on macOS; Alt’s on Windows (0x20000) and on X11 (Mod1).

util.utils.s_CMD(s: int) → bool
Returns:

True iff s has the Command key bit set (Control, off the Mac: the key shortcuts are made with there).

Parameters:

s (int)

util.utils.s_SHIFT(s: int) → bool
Returns:

True iff s has the Shift key bit set.

Parameters:

s (int)

util.utils.s_CTL(s: int) → bool
Returns:

True iff s has the Control key bit set.

Parameters:

s (int)

util.utils.s_ALT(s: int) → bool
Returns:

True iff s has the Alt or Option key bits set.

Parameters:

s (int)

util.utils.s_OPT(s: int) → bool
Returns:

True iff s has the Alt or Option key bits set.

Parameters:

s (int)

util.utils.strToBool(b: str)
Parameters:

b (str)

Returns:

Return type:

util.utils.WINDOWS = False

Whether this is Windows, where paths look like “C:folderfile” (see urlPath(), pathURL()).

util.utils.isWindowsPath(s: str, windows: bool = False) → bool
Parameters:
  • s – A path or URL.

  • windows – Whether this is Windows (by default: whether it is). Elsewhere, there are no such paths: “x:/…” is a URL.

Returns:

Whether s is a Windows path with a drive letter (eg: “C:Usersme”, “C:/Users/me”) or a network one (”\servershare...”): a path, not a URL whose scheme is one letter (as urlparse() takes it to be).

util.utils.openFile(path: str, mode: str = 'r', **kwargs) → Any

Like open(), but a path that can’t be a file’s (eg, one with a ‘”’ in it, on Windows, which raises an OSError “Invalid argument”) raises FileNotFoundError, as it does elsewhere: there’s no such file.

util.utils.urlPath(url: str, windows: bool = False) → str | None
Parameters:
Returns:

The path of the file url is of, on this computer (“%20” is a space, …): eg: “/Users/me/family.snm”, or “C:Usersmefamily.snm” on Windows. A path is as it is (unquoted). None if url is some other URL (eg: “https:”, “sn:”).

util.utils.pathURL(path: str, quoted: bool = True, windows: bool = False) → str
Parameters:
  • path – An absolute path.

  • quoted – Whether characters not allowed in URLs are quoted (eg: a space as “%20”), as Path.as_uri() does; else they’re as they are (as in “file://” + path).

  • windows – Whether path is a Windows path (by default: on Windows).

Returns:

The “file:” URL of path: eg: “file:///Users/me/family.snm”, or “file:///C:/Users/me/family.snm” for “C:Usersmefamily.snm”.

util.utils.isValidURL(url: str) → bool
Parameters:

url – A string to determine if it’s a proper URL

Returns:

True iff the str appears to be well-formed URL (a Windows path, eg: “C:Usersme”, isn’t one: see isWindowsPath())

util.utils.REMOTE_TIMEOUT = 30

How long (seconds) to wait for a web server (see fetchURL(), urlExists()).

util.utils.webCacheFolder: str | None = None

Where copies of the files read from the web are kept, if anywhere (see fetchURL()): set by an application (eg: Semantic Nets: ~/.sn/cache).

util.utils.cachePath(url: str) → str | None
Parameters:

url (str) – A web URL (“http:” or “https:”).

Returns:

Where its copy is (or would be) in webCacheFolder – by its host, then its path (a query string, if it has one, is part of the file’s name) – or None if there’s no cache, or it isn’t a web URL.

Return type:

str | None

util.utils.cachedCopy(url: str) → str | None
Returns:

The path of url’s copy in webCacheFolder (see cachePath()), if there is one.

class util.utils.OfflineWarning

Bases: object

fetchURL()’s warnings that it’s using a cached copy: at most once per URL.

warned: set[str] = {}
util.utils.fetchURL(url: str) → bytes
Parameters:

url (str) – A web URL (“http:” or “https:”).

Returns:

What’s there. It’s copied into webCacheFolder (if there is one); and if the server can’t be reached (eg: offline), that copy is used instead (with a warning).

Return type:

bytes

Raises:
  • FileNotFoundError – If there’s nothing there (HTTP 404 or 410).

  • urllib.error.URLError – If the server can’t be reached, and there’s no copy.

util.utils.urlExists(url: str) → bool
Parameters:

url – A string to determine if it’s an existant URL

Returns:

True iff the str is a URL that references an existant place – for a web URL whose server can’t be reached, if there’s a copy of it (see fetchURL()).

util.utils.canWriteUrl(url: str) → bool
Parameters:

url (str) – A url in text format.

Returns:

True iff you can write to that location (best guess).

Return type:

bool

The file: check is reliable; the HTTP check is best-effort only.

util.utils.fixURL(url: str, extension: str | None = None, replaceExt: bool | None = None, mode: str = 'w') → tuple[str, ParseResult]
Parameters:
  • url (str) – the URL to examine.

  • replaceExt (Optional[bool] (default: None)) – If True, any extension will be replace with the default extension; if False, the default extension will be appended to the path even there’s already another extension; if None (the default), the extension will only be added it the file does not already have an extension.

  • mode (str) – ‘r’ will check if the file or URL exists; ‘w’ will try to determine if the file is writable (not a URL other than a file’s, eg: “https:” – that can’t be told without writing it: it may be only for reading, so writing it fails then); anything else will make no checks.

  • extension (str) – The file extension to use for a replacement or appendment; if missing or None it defaults “.txt”. Ignored if replaceExt is None.

Returns:

Return type:

tuple[str, urllib.parse.ParseResult]

Raises:
  • FileNotFoundError – if mode=”r” and the url/file does not exist for a read operation

  • PermissionError – if mode=”w” and the url/file doesn’t appear to be writeable.

  • ValueError – if the URL is empty.

Sanitize the URL by making a filename a proper “file:///” URL, dealing with the path file extension, and verifying the file is accessible for read or write.

util.utils.fileToUrl(filename: str) → str
Parameters:

filename – A string representing a file path (may be relative)

Returns:

A proper URL referencing filename; it may or may not actually exist.

util.utils.urlToFile(url: str) → str | None
Parameters:

url – A string representing a URL

Returns:

A path string equivalent to url; it may or may not actually exist.

util.utils.openUrl(url: str) → bytes

Fetch the contents of a URL and return them as bytes for XML parsing.

Parses the given URL to detect its scheme. For file: URLs, the path is resolved to an absolute local path and read from the filesystem. All other schemes (e.g. http:, https:, ftp:) are fetched via urllib.request.urlopen().

The raw bytes are returned without decoding so that the XML parser can read the encoding declaration from the document itself and decode accordingly.

Parameters:

url (str) – The URL to fetch. May use any scheme supported by urllib.request, plus the file: protocol for local filesystem paths.

Returns:

Raw byte content of the resource, suitable for passing directly to an XML parser (e.g. xml.etree.ElementTree.fromstring() or lxml.etree.XMLParser).

Return type:

bytes

Raises:
  • ValueError – If url is an empty string.

  • FileNotFoundError – If a file: URL points to a path that does not exist.

  • urllib.error.URLError – If a remote URL cannot be reached.

Usage:

>>> xmlBytes = openUrl("https://example.com/feed.xml")
>>> import xml.etree.ElementTree as ET
>>> root = ET.fromstring(xmlBytes)

>>> xmlBytes = openUrl("file:///home/user/data.xml")
>>> root = ET.fromstring(xmlBytes)

<adapted from Claude>

util.utils.addDefaultExtToURL(url: str, ext: str = '.xml')
Parameters:
  • url – a legal url.

  • ext – an extension to be added to <url> iff <url>’s path does not already have an extension.

Returns:

<url> or a version of <url> with the extension added iff it did not already have an extension.

A dot is added in front of <ext> if it is not already there.

util.utils.getNameAndExtFromURL(url: str, extension: str | None = None)
Parameters:
  • url – a legal url, or if that fails will try to parse as a path.

  • extension – (default:None) if the url does not have a file extension, use this one if it’s given. If there is an extension on the URL already, extension is ignored.

Returns:

Just the filename (no path) and extension.

util.utils.changeUrlExtension(url, new_extension)

Changes the file extension of a URL.

Args:

url (str): The original URL string. new_extension (str): The new extension (e.g., “.jpg”, “.html”).

Returns:

str: The URL with the modified extension.

Parameters:
  • url

  • new_extension

Returns:

Return type:

util.utils.makeConcrete(abstract_cls: type[T], implementations: dict[str, Callable]) → type[T]

Dynamically create a concrete subclass from an ABC.

Parameters:
  • abstract_cls (type[T]) – The abstract base class to concretize.

  • implementations (dict[str, Callable]) – A mapping of abstract method names to callables.

Returns:

A new concrete subclass with all abstract methods implemented.

Return type:

type[T]

Raises:

TypeError – If you left out one or more abstract methods in implementations.

Usage:
class Shapes(ABC):
  @abstractmethod
  def area(self) -> float: ...

  @abstractmethod
  def perimeter(self) -> float: ...

circle = make_concrete(Shape, {
  "area":      lambda self: 3.14159 * 5 ** 2,
  "perimeter": lambda self: 2 * 3.14159 * 5,
})()

print(circle.area())       # 78.53975
print(circle.perimeter())  # 31.4159
util.utils.getFullClassName(cls: type) → str

Unlike obj.__class__.__name__ this returns the (period-deliminated) full class path of subjectClass

Returns:

Return type:

str

util.utils.getClass(className: str) → type | None

Searches the modules in the system for the first class that matches the simple class name. Use getFullyqualifiedClass() if you have a class path.

Parameters:

className (str)

Returns:

Return type:

Optional[type]

util.utils._classesByPath: dict[str, type] = {}

The classes getFullyQualifiedClass() has found, by their paths.

util.utils.getFullyQualifiedClass(classPath: str) → type | None
Parameters:

classPath – A string representing the name of a class

Returns:

Either a Class object or None

Follows the class path down the modules in the system to return the matching class object. User getClass() if you only have a simple class name and want to search for it. A class found is remembered (a class not found isn’t: its module may be loaded later): reading a file asks for the same few classes thousands of times, and each search looks through every module loaded.

util.utils._findFullyQualifiedClass(classPath: str) → type | None

getFullyQualifiedClass()’s search.

util.utils.getClassFromFullyQualifiedName(classPath: str) → type | None
Parameters:

className – A string representing the fully qualified name of a class

Returns:

Either a Class object or None

Uses the FULLY QUALIFIED class name to retrieve the actual class.

Parameters:

classPath (str)

util.utils.findSubclasses(cls: Type) → set[Type]
Parameters:

cls – a class object to find all its loaded (imported subclasses)

Returns:

A set of all subclasses of subjectClass

util.utils._originOf(cls: type) → type

Extract the bare origin type from any generic alias form.

Handles legacy Generic[T], PEP 585 list[int]-style aliases, PEP 695 class Foo[T] aliases, and plain classes.

Parameters:

cls (type) – A class or generic alias.

Returns:

The unwrapped origin type.

Return type:

type

util.utils.findConcreteSubclasses(abstractClass: Type) → list[Type]

Return all concrete (non-abstract) subclasses of an abstract class.

Recursively walks the subclass tree, collecting any class that is not itself abstract. Handles PEP 695 (class Foo[T]), legacy Generic[T], and plain classes.

Parameters:

abstractClass (Type[T]) – The abstract base class to inspect, optionally parameterized.

Returns:

A list of concrete subclass types.

Return type:

list[Type[T]]

util.utils.importSubmodules(packageName: str, filtr: Callable[[str], bool] | None = None) → None

Import all modules in a package to ensure subclasses are registered.

Parameters:
  • packageName (str) – The dotted package name, e.g. 'myapp.strategies'.

  • filtr (Optional[Callable[[str], bool]]=None) – If given, the module will only be loaded if filter() called with a scanned module name returns True. If absent, all scanned modules will be imported.

util.utils.getCallerIdInfoStr(stack: int = 1, includeArgs=True) → str
Parameters:
  • stack – An integer indicating how far back in the call stack to go. The default is 1, which indicates the caller of this method’s caller.

  • includeArgs (bool)

Returns:

A string of the form “[<class>.]<method>([self=<self>]) [line <lineno>]”.

util.utils.extendsDoc(base: type)

A decorator to the decorated method’s docstring its namesake’s in base, followed by its own. Works on plain methods, classmethods and staticmethods, whichever order the decorators are in. Usage:

class Sub(Base):
  @extendsDoc(Base)
  def place(self, node):
    """
    In addition: relation labels are placed only if "place labels" is on.
    """
    ...  
util.utils.delOne(lst: list[T], val: T, comparator: ~typing.Callable[[T, T], bool] = <function <lambda>>, throw: bool = True) → bool

Deletes exactly one item for the list, specifically, the first one found.

Parameters:
  • lst (list[T]) – The target list.

  • val (T) – the value to remove from the list.

  • comparator (Callable[[T,T],bool]) – A function to use to compare the values in the list to val. By default, is is used as the comparator.

  • throw (bool) – If not set to False, will raise a KeyError exception if no values that satisfy comparator(<item>, val) are found in the list. By default, this function will raise an exception if val is not found.

Returns:

True iff item val is successfully removed from the list.

Return type:

bool

Raises:

KeyError – iff no item in the list matches val.

util.utils.grouped(iterable: Iterable, n: int) → Iterable[tuple[T, ...]]

Return an iterable of n-tuples which is a n-partition the parameter iterable:

s -> (s0,s1,s2,…sn-1), (sn,sn+1,sn+2,…s2n-1), (s2n,s2n+1,s2n+2,…s3n-1), …

Thus, list(grouped([1,2,3,4], 2)) -> [(1,2),(3,4)]

Parameters:
  • iterable (Iterable[T]) – The target iterable.

  • n (int) – the size of the partitions, ie: the size of the tuples.

Returns:

a iterable of n-Tuples.

Return type:

Iterable[tuple[T,…]]

util.utils.removeDuplicates(items: list, asTree: bool = False) → list

Return a copy of the items list with any duplicates (determine by the “==” operator) removed. The first of any duplicates are the ones that are kept.

Parameters:
  • items (list)

  • asTree (bool)

Returns:

Return type:

list

util.utils.removeDuplicatesInPlace(items: list, removeLater=True) → None
Parameters:
  • list – The list to remove duplicates from.

  • removeLater – [bool, default: True] Iff True, keep the first of any duplicates, otherwise keep the last.

Removes any duplicates from the items list (determine by the “==” operator) in place.

Parameters:

items (list)

util.utils.flattenPairs(pairs)

From a vector of pairs, return a simple vector. [(1,2),(3,4)] -> [1,2,3,4]

Parameters:

pairs

Returns:

Return type:

util.utils.treeFlatten(tree: list, _omit: list = []) → list

Flatten a tree formed by nested lists into a single flat list eliminating duplicates.

eg: [1 [2 [6], 3 [4 [6], 5 [6]]]] –> [1, 2, 6, 3, 4, 5]

Parameters:
  • tree (list)

  • _omit (list)

Returns:

Return type:

list

util.utils.treeSplit(tree: list, _omit: list = [])

Split a tree list into roots and the children

eg: [1 [2 [6], 3 [4 [6], 5 [6]]]] –> [1], [2 [6], 3 [4 [6], 5 [6]]] and

[2 [6], 3 [4 [6], 5 [6]] –> [2, 3], [6, 4, 5], [6, 6] (note that [6, 4, 5] are the

immediate parents “1 in the example above)

Parameters:
  • tree (list)

  • _omit (list)

Returns:

Return type:

class util.utils.ExtendedEnumMeta(cls, bases, classdict, *, boundary=None, _simple=False, **kwds)

Bases: EnumType

A metaclass for ExtendedFlag: looking up a name with “|” in it (eg: Flags["A|B"]) gives the combined flags.

__getitem__(name)
Parameters:

name

Returns:

Return type:

class util.utils.ExtendedFlag(new_class_name, /, names, *, module=None, qualname=None, type=None, start=1, boundary=None)

Bases: Flag

A Flag whose members can be looked up by combined names (see ExtendedEnumMeta).

static fromStr(spec: str)

This cannot be right…

Parameters:

spec (str)

Returns:

Return type:

__str__() → str
Returns:

Return type:

str

static _generate_next_value_(name, start, count, last_values)

Generate the next value when not given.

name: the name of the member start: the initial start value or None count: the number of existing members last_values: the last value assigned or None

_boundary_ = 'strict'
_flag_mask_ = 0
_singles_mask_ = 0
_all_bits_ = 0
_inverted_ = None
util.utils.play(soundFile: str | None = None) → None

Plays a sound file: with afplay on macOS, winsound on Windows (a .wav file), and paplay or aplay on Linux (if there’s one).

Parameters:

soundFile –

May have the following values:

  • None: plays an error beep (on macOS, “/System/Library/Sounds/Sosumi.aiff”).

  • Just a file name: (not containing any slashes (“/”)) on macOS, looks in

    /System/Library/Sounds/ for the file.

  • A path: (containing slashes (“/”)) just tries to play that file.

util.utils.xmlEscape(strXML: str) → str

Replaces the “&”, “<”, “>”, and single and double quote characters with xml escape codes.

Parameters:

strXML (str)

Returns:

Return type:

str

util.utils.xmlUnescape(strXML: str) → str

Replaces XML escape codes with “&”, “<”, “>”, and single and double quote characters.

Parameters:

strXML (str)

Returns:

Return type:

str

util.utils.xmlToStringIndented(elem: Element, **kwargs)

Emulate an ElementTree.tostring() function, but with indents similar to what you get with the ElementTree.indent() method only for Elements.

Parameters:
  • elem (Element)

  • kwargs

Returns:

Return type:

util.utils.PointIndici

Use this globally to choose if Points and Rects store indices as int or float values.

class util.utils.Point(x: float, y: float)
class util.utils.Point(pair: Iterable[float])

Bases: Sequence

Implementation of an (x,y) point in a flexible but constrained way. Whether indices are int or float depends on the value of PointIndici.

__init__(x: float, y: float) → None
__init__(pair: Iterable[float]) → None

Overloaded constructor that will take either to PointInidici arguments as x and y, or any Iterator of two PointInidicis.

Parameters:

args

__getitem__(index: int) → float
__getitem__(index: slice[Any, Any, Any], /) → Sequence[float]

Implementation of indexing ([]). Works exactly like tuple*[index] where index may be an int or a slice.

Parameters:

index (Union[int, slice[Any, Any, Any]])

Returns:

Return type:

Union[PointIndici, Sequence[PointIndici]]

asTuple() → tuple

Returns self as a tuple.

Returns:

Return type:

tuple

asList() → list

Returns self as a list.

Returns:

Return type:

list

moveBy(x: float | None = None, y: float | None = None)

Mutating version of adding the corresponding values of x and y

Parameters:
__str__() → str

Return the in “(<x>,<y>)” form.

Returns:

Return type:

str

_abc_impl = <_abc._abc_data object>
util.utils.pointInRect(pt: Iterable[float], rect: Iterable[float]) → bool
Parameters:
  • pt – (x,y)

  • rect – (x,y,x1,y1)

Returns:

True iff the give pt lies inside rect.

util.utils.shiftRectToPoint(rect: Iterable[float], point: Iterable[float]) → list[float]
Parameters:
  • pt – (x,y)

  • rect – (x,y,x1,y1)

  • point (Iterable[PointIndici])

Returns:

A new rectangle if the same size and aspect ratio as rect, but with the upper left corner at pt

util.utils.overlaps(rect1: Iterable[float], rect2: Iterable[float]) → bool
Parameters:
  • rect1 – (x,y,x1,y1)

  • rect2 – (x,y,x1,y1)

Returns:

True iff the two rectangles overlap.

util.utils.pointInPoly(pt: Iterable[float], poly: Iterable[float]) → bool
Parameters:
  • pt – (x,y)

  • poly – (x,y [,x1,y1]…)

Returns:

True iff the give pt lies inside poly.

util.utils.midpoint(*pts: Iterable[float]) → Tuple[float, float]
Parameters:

pts – A list of point pairs

Returns:

The point midway between all the pts

util.utils.expandRect(rect: Iterable[float], spacing: Iterable[float] | float) → list[float]
Parameters:
  • rect – The rectangle to expand.

  • spacing – The vector of points to expand rect as [left, top, right, bottom]. May be of length 1, 2, or 4 or an int or a float, with the obvious interpretations.

Returns:

A new rectangle representing the expansion of rect

util.utils.normalizeRect(rect: Iterable[float]) → list[float]

Fix up a rect (x,y,x1,y1) such that x<x1 and y<y1, in conformance to tk rules.

Param:

rect:

Returns:

A new rectangle same as rect, but s/t x<x1 and y<y1.

Parameters:

rect (Iterable[PointIndici])

util.utils.iterableToList(it: Iterable) → list
Parameters:

it (Iterable)

Returns:

Return type:

list

util.utils.getSubMenu(menu: Menu, name: str, makeIfNone: int | str | None = 'end') → Menu | None

Find a submenu in menu with label name .

Parameters:
  • menu – The menu to search

  • name – The label of the item we are searching for

  • makeIfNone – [default: tk.END (insert at end)] if not None and menu does not contain any menu items named name then insert a new menu named name at position makeIfNone – either an int index or one of Tkinter’s symbolic index constants (e.g. tk.END, which is itself just the str “end”; this is what insert_cascade() itself accepts, so we just pass it straight through).

Returns:

Return a tk.Menu if it is found or created; if it is not found and makeIfNone is None, return None .

util.utils.getToplevelWindows(root: Tk)

root’s top-level windows.

util.utils.getMenuBar() → Menu | None

Resolve the app’s menu bar – the configured menu of tk._default_root.

Returns:

The resolved tk.Menu, or None if there’s no default root, or it has no menu bar configured.

util.utils._findEntry(menu: Menu, label: str) → tuple[int | None, str | None]
Parameters:
  • menu – The menu to search.

  • label – The label of the item we are searching for.

Returns:

(index, type) of the entry in menu labelled label, or (None, None) if there is no such entry.

util.utils._separatedItems: set[tuple[str, str]] = {}

(menu, label) of the items added with a separator just above them (see addToMenuBar()’s separatorBefore).

util.utils._separate(menu: Menu, label: str, separatorBefore: bool) → None

If separatorBefore: make sure the item labelled label in menu has a separator just above it (unless it’s the first), and note that it’s the item’s, to go with it (see removeFromMenuBar()).

Parameters:
  • menu – The menu.

  • label – The item’s label.

  • separatorBefore – Whether it has a separator above it.

util.utils._lastItems: set[tuple[str, str]] = {}

(menu, label) of the items kept at the end of their menus (see keepLast()).

util.utils.keepLast(menu: Menu, *labels: str) → None

Keep the items labelled labels in menu at its end: items addToMenuBar() and addCheckToMenuBar() add later go above them (and above the separator just above them, if there is one) – eg: “Quit” at the end of a File menu.

Parameters:
  • menu – The menu.

  • labels – Its items’ labels.

util.utils._endIndex(menu: Menu) → int | str
Returns:

Where a new item goes at the end of menu: tk.END, or above the items kept last (see keepLast()).

util.utils.addToMenuBar(path: str, command: Callable | None = None, *, menu: Menu | None = None, accelerator: str | None = None, cascadePositions: dict[str, int] | None = None, separatorBefore: bool = False) → None

Add – or, if already present, replace – an entry in the app’s menu bar.

Pass exactly one of command or menu: command adds/replaces a plain command entry that invokes command when chosen; menu adds/replaces a cascade entry whose submenu is menu. The top-level menu bar itself is always the app’s own – resolved from tk._default_root’s configured menu (see getMenuBar()) – there’s no need to look it up and pass it in.

Parameters:
  • path – A "|"-separated path of cascade labels ending in the item’s own label, e.g. "File|Save view". All but the last segment name cascade submenus, created via getSubMenu() if they don’t already exist; the last segment is the item’s own label.

  • command – The callable to invoke when the item is chosen. Give this to add/replace a plain command item; mutually exclusive with menu.

  • menu – The submenu to show as a cascade. Give this to add/replace a cascade item; mutually exclusive with command.

  • accelerator – Optional accelerator string to display (e.g. “Cmd+S”).

  • cascadePositions – Where to put any cascade that has to be made, by label, eg: {"Edit": 1}; by default, at the end (as for addCheckToMenuBar()).

  • separatorBefore – Put a separator just above the item (unless it’s the first, or there’s one there already): it starts a group of items. removeFromMenuBar() removes the separator with the item.

Note:

Safe to call repeatedly with the same path – e.g. every time a window gains focus – since an existing entry with the same final label is reconfigured in place rather than duplicated (and, if it was previously the other kind of entry – command vs. cascade – it’s replaced in place, at the same position). So a window can add its items unconditionally whenever it gains focus.

util.utils.addCheckToMenuBar(path: str, variable: Variable, command: Callable | None = None, *, cascadePositions: dict[str, int] | None = None, separatorBefore: bool = False) → None

Add – or, if already present, replace – a check item in the app’s menu bar: it shows (and sets) the boolean variable, and calls command when chosen. Like addToMenuBar(), safe to call repeatedly with the same path (eg: each time a window gains focus, rebinding it to that window’s variable), and removed with removeFromMenuBar().

Parameters:
  • path – A "|"-separated path of cascade labels ending in the item’s own label, e.g. "View|Animation toolbar".

  • variable – The (boolean) variable the item shows and sets.

  • command – Called (after variable is set) when the item is chosen.

  • cascadePositions – Where to put any cascade that has to be made, by label, eg: {"View": 1} (just after “File”); by default, at the end.

  • separatorBefore – As for addToMenuBar().

util.utils.removeFromMenuBar(path: str) → None

Remove an entry (as added by addToMenuBar()) from the app’s menu bar – and the separator above it, if it was added with one (separatorBefore).

Safe to call even when the item (or an intervening cascade) isn’t currently present – this is simply a no-op in that case, so callers (eg: a window’s focus-out handler) don’t need to track whether they actually added the item first.

Parameters:

path – Same "|"-separated path as passed to addToMenuBar().

util.utils.isGuiAvailable() → bool

Determine whether a graphical display environment appears to be available.

Returns:

True if a GUI environment is likely usable, False otherwise.

Return type:

bool

util.utils.askViaTk(prompt: str, title: str = 'Input required', check: ~typing.Callable[[str], bool] = <function <lambda>>) → str | None

Prompt the user for input using a Tkinter dialog: in the running Tk application, if there is one (over its focused window); otherwise in a Tk interpreter of its own, made for it and destroyed afterwards.

Parameters:
  • prompt (str) – The message to display to the user.

  • title (str) – The dialog window title.

Returns:

The user’s input, or None if the dialog could not be created or was cancelled.

Return type:

Optional[str]

util.utils.askViaTerminal(prompt: str, check: ~typing.Callable[[str], bool] = <function <lambda>>) → str | None

Prompt the user for input via stdin/stdout.

Parameters:

prompt (str) – The message to display to the user.

Returns:

The user’s input, or None if stdin is not interactive.

Return type:

Optional[str]

util.utils.askYesNo(question: str, title: str) → bool

Ask the user a yes-or-no question: in a Yes/No dialog over the focused window, if the GUI is running; otherwise (eg: the terminal interface, or once the windows are gone at exit) as text (see util.utils.askUser()).

Parameters:
  • question (str)

  • title (str) – The dialog’s title.

Returns:

True iff the answer is yes.

Return type:

bool

util.utils.askUser(prompt: str, title: str = 'Input required', check: ~typing.Callable[[str], bool] = <function <lambda>>) → str | None

Ask the user a question, using a GUI dialog if available and falling back to the terminal.

Parameters:
  • prompt (str) – The message to display to the user.

  • title (str) – The dialog window title, used only in GUI mode.

  • check (check:Callable[[str],bool]) – A function to validate user input.

Returns:

The user’s input, or None if no input method succeeded.

Return type:

Optional[str]

class util.utils.VerticalScrolledFrame(parent, *args, **kw)

Bases: Frame

A pure Tkinter scrollable frame that actually works! * Use the ‘interior’ attribute to place widgets inside the scrollable frame. * Construct and pack/place/grid normally. * This frame only allows vertical scrolling.

__init__(parent, *args, **kw)

A frame that scrolls vertically: put widgets in its interior.

util.utils.wheelSteps(event: Event) → int
Parameters:

event – A mouse wheel event (see wheelEvents()).

Returns:

How far it goes, in the wheel’s steps: positive up (away from the user), negative down. On macOS, Tk’s delta (a trackpad’s small steps); on Windows and X11, delta / 120 (a notch), at least 1 either way; for X11’s buttons 4 and 5 (Tk before 8.7), 1 and -1.

util.utils.wheelEvents(shift: bool = False) → list[str]
Parameters:

shift – The events with Shift held (on most systems, a sideways scroll).

Returns:

The event sequences a mouse wheel makes, to bind: “<MouseWheel>”; and with Tk before 8.7 on X11, where the wheel is buttons 4 and 5, “<Button-4>” and “<Button-5>” too. (See wheelSteps().)

util.utils.bindRightMouse(window, func)

Bind func to a right click in window: also Control-click, on macOS.

util.utils.tag_bindRightMouse(window, id_, func)

Bind func to a right click on canvas item id_ (see bindRightMouse()).

util.utils.tag_unbindRightMouse(window, id_)

Unbind a right click on canvas item id_ (see tag_bindRightMouse()).

util.utils.eventEqual(e1, e2)

Whether e1 and e2 are the same event: of the same type, at the same place on the screen.

util.utils.dialogOwner(parent: Any) → Misc | None
Parameters:

parent (tk.Misc | tk.Wm | None) – What a dialog is opened from (any widget), if anything.

Returns:

The window the dialog belongs over: parent’s own window, if it’s on screen; else the app’s window that has the focus, or failing that one that’s full screen, or any that’s on screen – eg: when parent is an app’s hidden root window; or None if none is.

Return type:

tk.Misc | None

util.utils.ownDialog(dialog: Toplevel, parent: Any) → None

Make dialog transient for the window it belongs over (see dialogOwner()): it’s then shown over that window, at its own size. Call it before dialog is first shown: on macOS, a window that isn’t transient for another opens full screen – in a space of its own – when the app’s in full screen, and making it transient once it’s been shown doesn’t undo that. (Never transient for a window that isn’t on screen: on macOS/Aqua that can leave the dialog blank, or never shown: see showExceptionDialog().)

Parameters:
  • dialog (tk.Toplevel) – A dialog: a new window, not shown yet.

  • parent (tk.Misc | tk.Wm | None) – What it’s opened from, if anything.

util.utils.showExceptionDialog(parent: Toplevel | Tk | None = None, title: str = 'Error', text: str | None = None) → None

Display the currently-handled exception’s traceback (as returned by traceback.format_exc()) in a scrollable, resizable dialog. Call this from within an except: block, the same place you’d call traceback.print_exc().

Parameters:
  • parent (tk.Misc | None) – The parent widget/window this dialog is transient to.

  • title (str) – The dialog’s window title.

  • text (str) – The full text of the traceback (presumably form traceback.format_exc()).

Return type:

None

util.utils.allSubclasses(cls) → set

Returns the set of all descendants of subjectClass

Returns:

Return type:

set

util.utils.allConcreteSubclasses(cls) → set

Returns the set of all non-abstract descendants of subjectClass

Returns:

Return type:

set

class util.utils.Categories

Bases: Generic

Named categories of objects, each defined by a test: isCategory() says whether an object is in any of the named categories.

__init__()
categories: dict[str, Callable[[T], bool]]
addCategory(name: str, test: Callable[[T], bool])
Parameters:
  • name (str)

  • test (Callable[[T], bool])

deleteCategory(name: str)
Parameters:

name (str)

isCategory(obj: T, categories: Iterable[str] | str)
Parameters:
  • obj (T)

  • categories (Union[Iterable[str], str])

Returns:

Return type:

keys()
Returns:

Return type: