-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathutil.py
More file actions
111 lines (86 loc) · 3.4 KB
/
Copy pathutil.py
File metadata and controls
111 lines (86 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""Utility functions for Annif"""
from __future__ import annotations
import glob
import logging
import os
import os.path
import tempfile
from typing import Any, Callable
from annif import logger
class DuplicateFilter(logging.Filter):
"""Filter out log messages that have already been displayed."""
def __init__(self) -> None:
super().__init__()
self.logged = set()
def filter(self, record: logging.LogRecord) -> bool:
current_log = hash((record.module, record.levelno, record.msg, record.args))
if current_log not in self.logged:
self.logged.add(current_log)
return True
return False
def atomic_save(
obj: Any, dirname: str, filename: str, method: Callable | None = None
) -> None:
"""Save the given object (which must have a .save() method, unless the
method parameter is given) into the given directory with the given
filename, using a temporary file and renaming the temporary file to the
final name."""
prefix, suffix = os.path.splitext(filename)
prefix = "tmp-" + prefix
tempfd, tempfilename = tempfile.mkstemp(prefix=prefix, suffix=suffix, dir=dirname)
os.close(tempfd)
logger.debug("saving %s to temporary file %s", str(obj)[:90], tempfilename)
if method is not None:
method(obj, tempfilename)
else:
obj.save(tempfilename)
for fn in glob.glob(tempfilename + "*"):
newname = fn.replace(tempfilename, os.path.join(dirname, filename))
logger.debug("renaming temporary file %s to %s", fn, newname)
os.rename(fn, newname)
def cleanup_uri(uri: str) -> str:
"""remove angle brackets from a URI, if any"""
if uri.startswith("<") and uri.endswith(">"):
return uri[1:-1]
return uri
def parse_sources(sourcedef: str) -> list[tuple[str, float]]:
"""parse a source definition such as 'src1:1.0,src2' into a sequence of
tuples (src_id, weight)"""
sources = []
totalweight = 0.0
for srcdef in sourcedef.strip().split(","):
srcval = srcdef.strip().split(":")
src_id = srcval[0]
if len(srcval) > 1:
weight = float(srcval[1])
else:
weight = 1.0
sources.append((src_id, weight))
totalweight += weight
return [(srcid, weight / totalweight) for srcid, weight in sources]
def parse_args(param_string: str) -> tuple[list, dict]:
"""Parse a string of comma separated arguments such as '42,43,key=abc' into
a list of positional args [42, 43] and a dict of keyword args {key: abc}"""
if not param_string:
return [], {}
posargs = []
kwargs = {}
param_strings = param_string.split(",")
for p_string in param_strings:
parts = p_string.split("=")
if len(parts) == 1:
posargs.append(p_string)
elif len(parts) == 2:
kwargs[parts[0]] = parts[1]
return posargs, kwargs
def boolean(val: Any) -> bool:
"""Convert the given value to a boolean True/False value, if it isn't already.
True values are '1', 'yes', 'true', and 'on' (case insensitive), everything
else is False."""
return str(val).lower() in ("1", "yes", "true", "on")
def identity(x: Any) -> Any:
"""Identity function: return the given argument unchanged"""
return x
def metric_code(metric):
"""Convert a human-readable metric name into an alphanumeric string"""
return metric.translate(metric.maketrans(" ", "_", "()"))