whatamithinking-hostutil 5.0.2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
from ipaddress import ip_address, ip_network
|
|
2
|
+
from typing import Optional, NamedTuple
|
|
3
|
+
import socket
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
import psutil
|
|
7
|
+
from netifaces import AF_INET, AF_INET6, gateways
|
|
8
|
+
from python_hosts import Hosts, HostsEntry
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"HOSTNAME_REGEX",
|
|
12
|
+
"normalize",
|
|
13
|
+
"is_like_ipv4_address",
|
|
14
|
+
"is_like_ipv6_address",
|
|
15
|
+
"is_like_address",
|
|
16
|
+
"is_like_hostname",
|
|
17
|
+
"is_like_host",
|
|
18
|
+
"get_likely_type",
|
|
19
|
+
"is_valid_address",
|
|
20
|
+
"is_valid_hostname",
|
|
21
|
+
"is_valid_host",
|
|
22
|
+
"get_valid_type",
|
|
23
|
+
"normalize_mac_address",
|
|
24
|
+
"get_hostname",
|
|
25
|
+
"get_addresses",
|
|
26
|
+
"get_address",
|
|
27
|
+
"is_localhost",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__version__ = "5.0.2"
|
|
32
|
+
|
|
33
|
+
# src: https://stackoverflow.com/questions/106179/regular-expression-to-fullmatch-dns-hostname-or-ip-address
|
|
34
|
+
# updated to inclue underscore, which is allowed on windows
|
|
35
|
+
HOSTNAME_REGEX = re.compile(
|
|
36
|
+
r"(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9_\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9_\-]*[A-Za-z0-9])"
|
|
37
|
+
)
|
|
38
|
+
# this does not guarantee a valid ipv4 address. it just indicates that the user probably entered an address
|
|
39
|
+
# but perhaps messed up the formatting
|
|
40
|
+
_IPV4_ADDRESS_LIKE_REGEX = re.compile(r"\d*\.\d*\.\d*\.\d*")
|
|
41
|
+
# hostnames cannot contain ":" (src: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names)
|
|
42
|
+
# and ipv4 does not use this char either, so if included it likely means the user was trying to provide ipv6
|
|
43
|
+
_IPV6_ADDRESS_LIKE_REGEX = re.compile(r".*:.*")
|
|
44
|
+
# cache of the hostname for the local machine for performance reasons
|
|
45
|
+
# saved the first time it is requested
|
|
46
|
+
_cached_local_hostname: str = None
|
|
47
|
+
_MAC_REPLACE_REGEX = re.compile(r"[^0123456789ABCDEF]")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_like_ipv4_address(host: str) -> bool:
|
|
51
|
+
"""Return True if the given string looks like an ipv4 address, even
|
|
52
|
+
if it is not necessarily a valid one."""
|
|
53
|
+
return _IPV4_ADDRESS_LIKE_REGEX.match(host) is not None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_like_ipv6_address(host: str) -> bool:
|
|
57
|
+
"""Return True if the given string looks like an ipv6 address, even
|
|
58
|
+
if it is not necessarily a valid one."""
|
|
59
|
+
return _IPV6_ADDRESS_LIKE_REGEX.match(host) is not None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_like_address(host: str) -> bool:
|
|
63
|
+
"""Return True if the given string looks like an address, even
|
|
64
|
+
if it is not necessarily a valid one."""
|
|
65
|
+
if is_like_ipv4_address(host):
|
|
66
|
+
return True
|
|
67
|
+
if is_like_ipv6_address(host):
|
|
68
|
+
return True
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def is_like_hostname(host: str) -> bool:
|
|
73
|
+
"""Return True if the given host looks like a hostname, even if
|
|
74
|
+
it is not necessarily a valid one."""
|
|
75
|
+
# ipv4 format can be used as a valid hostname on some systems so have
|
|
76
|
+
# to check that first to avoid calling address a hostname
|
|
77
|
+
if is_like_address(host):
|
|
78
|
+
return False
|
|
79
|
+
if not HOSTNAME_REGEX.match(host):
|
|
80
|
+
return False
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def is_like_host(host: str) -> bool:
|
|
85
|
+
"""Return True if the given host looks like an address or hostname
|
|
86
|
+
and False otherwise.
|
|
87
|
+
|
|
88
|
+
True does not necessarily mean the host is a valid address or hostname,
|
|
89
|
+
only that it looks like one of those.
|
|
90
|
+
"""
|
|
91
|
+
if is_like_address(host):
|
|
92
|
+
return True
|
|
93
|
+
if HOSTNAME_REGEX.match(host):
|
|
94
|
+
return True
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_likely_type(host: str) -> str:
|
|
99
|
+
"""Returns 'address' if given host is likely an IP address, 'hostname'
|
|
100
|
+
if it is likely a hostname and raises an exception, ValueError, if neither.
|
|
101
|
+
|
|
102
|
+
This can be used for when the user input could be either an address
|
|
103
|
+
or a hostname, but there is a chance the user messed up the formatting.
|
|
104
|
+
"""
|
|
105
|
+
if is_like_address(host):
|
|
106
|
+
return "address"
|
|
107
|
+
if HOSTNAME_REGEX.match(host):
|
|
108
|
+
return "hostname"
|
|
109
|
+
raise ValueError(f"Host is not likely a IPv4/IPv6 address or hostname: {host}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def is_valid_address(host: str) -> bool:
|
|
113
|
+
"""Return True if the given host is in a valid address format;
|
|
114
|
+
False otherwise.
|
|
115
|
+
|
|
116
|
+
This does not check whether the host exists, only if it appears to
|
|
117
|
+
be in the right format for an address.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
ip_address(host)
|
|
121
|
+
except ValueError:
|
|
122
|
+
return False
|
|
123
|
+
else:
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def is_valid_hostname(host: str) -> bool:
|
|
128
|
+
"""Return True if the given host is in a valid hostname format
|
|
129
|
+
and is not in a valid address format.
|
|
130
|
+
|
|
131
|
+
This assumes that if an address was given, it is in the right format.
|
|
132
|
+
If an address was given but it is in the wrong format, it may be considered
|
|
133
|
+
a hostname.
|
|
134
|
+
"""
|
|
135
|
+
if is_valid_address(host):
|
|
136
|
+
return False
|
|
137
|
+
if not HOSTNAME_REGEX.fullmatch(host):
|
|
138
|
+
return False
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def is_valid_host(host: str) -> bool:
|
|
143
|
+
"""Return True if the given host is a valid address or hostname
|
|
144
|
+
and False otherwise.
|
|
145
|
+
|
|
146
|
+
This assumes valid formatting will be used for the address or
|
|
147
|
+
for the hostname. If this is a user-input and they may make a mistake
|
|
148
|
+
use `is_host_like` instead.
|
|
149
|
+
"""
|
|
150
|
+
if is_valid_address(host):
|
|
151
|
+
return True
|
|
152
|
+
if HOSTNAME_REGEX.fullmatch(host):
|
|
153
|
+
return True
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_valid_type(host: str) -> str:
|
|
158
|
+
"""Returns 'address' if given host is a valid IP address, 'hostname'
|
|
159
|
+
if it is a valid hostname and raises an exception, ValueError, if neither.
|
|
160
|
+
|
|
161
|
+
This assumes valid formatting will be used for the address or
|
|
162
|
+
for the hostname. If this is a user-input and they may make a mistake
|
|
163
|
+
use `get_likely_type` instead.
|
|
164
|
+
"""
|
|
165
|
+
if is_valid_address(host):
|
|
166
|
+
return "address"
|
|
167
|
+
if HOSTNAME_REGEX.fullmatch(host):
|
|
168
|
+
return "hostname"
|
|
169
|
+
raise ValueError(f"Host is not a valid IPv4/IPv6 address or hostname: {host}")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def normalize(host: Optional[str]) -> Optional[str]:
|
|
173
|
+
"""Normalize the given host (hostname or address) so it can be compared
|
|
174
|
+
against others to avoid duplicates.
|
|
175
|
+
|
|
176
|
+
Simple operation, but defined here to avoid replicating all over the place.
|
|
177
|
+
This works with likely types, so imperfect inputs are supported.
|
|
178
|
+
"""
|
|
179
|
+
if not host:
|
|
180
|
+
return
|
|
181
|
+
htype = get_likely_type(host)
|
|
182
|
+
if htype == "hostname":
|
|
183
|
+
# the second strip removes the domain info which is sometimes
|
|
184
|
+
# included in the hostname but for a lan not relevant in most cases
|
|
185
|
+
return host.strip().split(".", 1)[0].casefold()
|
|
186
|
+
else:
|
|
187
|
+
return str(ip_address(host))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def get_hostname(host: Optional[str] = None) -> str:
|
|
191
|
+
"""Blocking. Return the hostname from either an address or a
|
|
192
|
+
hostname.
|
|
193
|
+
|
|
194
|
+
The result is normalized so hostnames can be compared.
|
|
195
|
+
|
|
196
|
+
This takes 1-5ms for the local machine since it is statically set.
|
|
197
|
+
The address may take longer to lookup.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
host: Optional. address or hostname to lookup.
|
|
201
|
+
Defaults to returning local hostname.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
The hostname for the given host
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
socket.gaierror if lookup failed
|
|
208
|
+
"""
|
|
209
|
+
if host is None:
|
|
210
|
+
global _cached_local_hostname
|
|
211
|
+
if _cached_local_hostname is None:
|
|
212
|
+
_cached_local_hostname = socket.gethostname()
|
|
213
|
+
hostname = _cached_local_hostname
|
|
214
|
+
else:
|
|
215
|
+
if get_likely_type(host) == "hostname":
|
|
216
|
+
hostname = host
|
|
217
|
+
else:
|
|
218
|
+
# strip off the dns suffix/domain info which should always be separated
|
|
219
|
+
# out from the hostname of the machine with dots
|
|
220
|
+
# this function supports both ipv4 and ipv6
|
|
221
|
+
hostname = socket.gethostbyaddr(host)[0]
|
|
222
|
+
return normalize(hostname)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _fast_is_local_address(address: str) -> bool:
|
|
226
|
+
try:
|
|
227
|
+
htype = get_likely_type(address)
|
|
228
|
+
except ValueError:
|
|
229
|
+
return False
|
|
230
|
+
if htype != "address":
|
|
231
|
+
return False
|
|
232
|
+
# when you bind to this, the app binds to all addresses for the localhost
|
|
233
|
+
# assumed that when the user gives this, they mean the local machine
|
|
234
|
+
if address in ("0.0.0.0", "::1"):
|
|
235
|
+
return True
|
|
236
|
+
try:
|
|
237
|
+
ip_addr = ip_address(address)
|
|
238
|
+
except ValueError:
|
|
239
|
+
return False
|
|
240
|
+
else:
|
|
241
|
+
return ip_addr.is_loopback
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _get_hosts_file_local_hostnames() -> set[str]:
|
|
245
|
+
"""Return a set of loopback hostnames defined in the local
|
|
246
|
+
hosts file.
|
|
247
|
+
|
|
248
|
+
Takes ~1ms
|
|
249
|
+
|
|
250
|
+
WARNING: Cached after the first call for performance reasons.
|
|
251
|
+
"""
|
|
252
|
+
hosts = Hosts()
|
|
253
|
+
host_entries: HostsEntry = hosts.entries
|
|
254
|
+
loopback_hostnames = set(["localhost"]) # built in whether present in file or not
|
|
255
|
+
for _ in host_entries:
|
|
256
|
+
if _.entry_type in ("blank", "comment"):
|
|
257
|
+
continue
|
|
258
|
+
if not _fast_is_local_address(_.address):
|
|
259
|
+
continue
|
|
260
|
+
loopback_hostnames |= set(_.names)
|
|
261
|
+
return loopback_hostnames
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _fast_is_local_hostname(hostname: str) -> bool:
|
|
265
|
+
try:
|
|
266
|
+
htype = get_likely_type(hostname)
|
|
267
|
+
except ValueError:
|
|
268
|
+
return False
|
|
269
|
+
if htype != "hostname":
|
|
270
|
+
return False
|
|
271
|
+
hostname = normalize(hostname)
|
|
272
|
+
if hostname == "localhost":
|
|
273
|
+
return True
|
|
274
|
+
if hostname in _get_hosts_file_local_hostnames():
|
|
275
|
+
return True
|
|
276
|
+
# this requires io, but is still pretty fast, taking <5ms
|
|
277
|
+
if hostname == get_hostname():
|
|
278
|
+
return True
|
|
279
|
+
return False
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def normalize_mac_address(mac: str, sep: str = ":") -> str:
|
|
283
|
+
"""Normalize the given mac address using the the given separator.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
mac: The mac address to normalize
|
|
287
|
+
sep: Optional. The separator to use between the blocks of the address.
|
|
288
|
+
Defaults to ":".
|
|
289
|
+
"""
|
|
290
|
+
pure = re.sub(_MAC_REPLACE_REGEX, "", mac.upper())
|
|
291
|
+
if len(pure) != 12:
|
|
292
|
+
raise ValueError("Invalid MAC address length. Cannot normalize.")
|
|
293
|
+
return sep.join([pure[i : i + 2] for i in range(0, len(pure), 2)])
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class AddressInfo(NamedTuple):
|
|
297
|
+
connection_name: str
|
|
298
|
+
family: socket.AddressFamily
|
|
299
|
+
address: str
|
|
300
|
+
netmask: str
|
|
301
|
+
gateway: str
|
|
302
|
+
broadcast: str
|
|
303
|
+
mac_address: str
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def get_addresses() -> list[AddressInfo]:
|
|
307
|
+
"""Return a connection name sorted list of AddressInfo objects for
|
|
308
|
+
all physical connections for this machine.
|
|
309
|
+
|
|
310
|
+
socket.gethostbyname(socket.gethostname()) is normally used but it
|
|
311
|
+
does not always work when other network adapters are installed on a
|
|
312
|
+
machine. For example, when WSL is installed on windows the socket lib
|
|
313
|
+
can sometimes return the address of the WSL adapter which is not accessible
|
|
314
|
+
from external machines.
|
|
315
|
+
This function filters out non-active network adapters, so if you are switching
|
|
316
|
+
from wifi to ethernet, the addresses returned will be different before and after.
|
|
317
|
+
|
|
318
|
+
WARNING: In some cases on windows it seems the address of an adapter such as wifi
|
|
319
|
+
may change when not in use to some internal address for the machine in which case
|
|
320
|
+
it will be filtered out instead of returned with an invalid address.
|
|
321
|
+
|
|
322
|
+
Takes ~30-60ms.
|
|
323
|
+
|
|
324
|
+
Note this is on the network and not on the internet.
|
|
325
|
+
"""
|
|
326
|
+
# HACK: use the physical gateways we know about to filter out virtual adapters
|
|
327
|
+
# which should not have the right ip addr/netmask to use the physical gateways
|
|
328
|
+
gaddrs = frozenset(
|
|
329
|
+
addr[0]
|
|
330
|
+
for ift, addrs in gateways().items() # ~5ms
|
|
331
|
+
for addr in addrs
|
|
332
|
+
if ift in (AF_INET, AF_INET6)
|
|
333
|
+
)
|
|
334
|
+
addrinfos = []
|
|
335
|
+
for name, addrs in psutil.net_if_addrs().items(): # ~25ms
|
|
336
|
+
# print(name)
|
|
337
|
+
# skip connection if not for one of the known gateways to filter out virtual stuff
|
|
338
|
+
gfound = False
|
|
339
|
+
for addr in addrs:
|
|
340
|
+
if not addr.family in (socket.AF_INET, socket.AF_INET6):
|
|
341
|
+
continue
|
|
342
|
+
if addr.netmask is None:
|
|
343
|
+
continue
|
|
344
|
+
gwaddr = str(ip_network(f"{addr.address}/{addr.netmask}", strict=False)[1])
|
|
345
|
+
if not gwaddr in gaddrs:
|
|
346
|
+
continue
|
|
347
|
+
gfound = True
|
|
348
|
+
if not gfound:
|
|
349
|
+
continue
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
mac_address = next(
|
|
353
|
+
addr.address for addr in addrs if addr.family == psutil.AF_LINK
|
|
354
|
+
)
|
|
355
|
+
except StopIteration:
|
|
356
|
+
raise RuntimeError(f"No mac address found for connection, {name}")
|
|
357
|
+
|
|
358
|
+
for addr in addrs:
|
|
359
|
+
if not addr.family in (socket.AF_INET, socket.AF_INET6):
|
|
360
|
+
continue
|
|
361
|
+
if ip_address(addr.address).is_loopback:
|
|
362
|
+
continue
|
|
363
|
+
if addr.netmask is None:
|
|
364
|
+
continue
|
|
365
|
+
ipnet = ip_network(f"{addr.address}/{addr.netmask}", strict=False)
|
|
366
|
+
addrinfos.append(
|
|
367
|
+
AddressInfo(
|
|
368
|
+
connection_name=name.casefold().strip(),
|
|
369
|
+
family=addr.family,
|
|
370
|
+
address=normalize(addr.address),
|
|
371
|
+
gateway=normalize(gwaddr),
|
|
372
|
+
netmask=normalize(addr.netmask),
|
|
373
|
+
broadcast=normalize(addr.broadcast or str(ipnet.broadcast_address)),
|
|
374
|
+
mac_address=normalize_mac_address(mac_address),
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
addrinfos.sort(key=lambda _: _.connection_name)
|
|
379
|
+
return addrinfos
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def get_address(host: Optional[str] = None) -> str:
|
|
383
|
+
"""Blocking. Get the default address of the machine.
|
|
384
|
+
|
|
385
|
+
Takes ~60-80ms
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
host: Optional. address or hostname to lookup the address
|
|
389
|
+
for. Defaults to returning the public address for the local machine.
|
|
390
|
+
|
|
391
|
+
Raises:
|
|
392
|
+
ConnectionError if computer has no network adapter in use and address
|
|
393
|
+
cannot be determined.
|
|
394
|
+
"""
|
|
395
|
+
htype = None if host is None else get_likely_type(host)
|
|
396
|
+
address = None
|
|
397
|
+
if htype == "address":
|
|
398
|
+
address = host
|
|
399
|
+
else:
|
|
400
|
+
# HACK: if fast_is_local_hostname fails to pickup on a host value which is local
|
|
401
|
+
# this block may return the wrong address when mulitple network adapters are installed
|
|
402
|
+
# should be fine the ip address is given since we just return that as-is
|
|
403
|
+
if host is None or _fast_is_local_hostname(host):
|
|
404
|
+
addrs = get_addresses()
|
|
405
|
+
if not addrs:
|
|
406
|
+
raise ConnectionError("No network adapters found for this machine.")
|
|
407
|
+
try:
|
|
408
|
+
# return first address found which is in active use
|
|
409
|
+
# or else fallback to just whatever the first one is in the event
|
|
410
|
+
# no adapters are currently in use for some reason, such as network
|
|
411
|
+
# disconnect or switching from one to the other
|
|
412
|
+
stats = dict(
|
|
413
|
+
(k.casefold().strip(), v) for k, v in psutil.net_if_stats().items()
|
|
414
|
+
)
|
|
415
|
+
address = next(
|
|
416
|
+
_ for _ in addrs if stats[_.connection_name].isup
|
|
417
|
+
).address
|
|
418
|
+
except StopIteration:
|
|
419
|
+
address = addrs[0].address
|
|
420
|
+
else:
|
|
421
|
+
address = socket.getaddrinfo(host, None)[0][4][0]
|
|
422
|
+
return normalize(address)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def is_localhost(host: str, dns: bool = True) -> bool:
|
|
426
|
+
"""Blocking. Return True if the given address or hostname is for the
|
|
427
|
+
localhost; return False otherwise.
|
|
428
|
+
|
|
429
|
+
Always check info which is quickest to pull, such as known loopback
|
|
430
|
+
and local hostnames as well as the local hosts file. After that,
|
|
431
|
+
it checks with dns and finally it checks all interfaces available
|
|
432
|
+
on the local machine, which can take a few hundred ms.
|
|
433
|
+
|
|
434
|
+
Usually takes ~<50ms with default settings.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
host: the address or hostname to check
|
|
438
|
+
dns: Optional. If True and a hostname is given, a final attempt
|
|
439
|
+
will be made to resolve it to an address and then check if that
|
|
440
|
+
address is for the local machine. This can take as long as typical
|
|
441
|
+
timeout for DNS queries of 4 seconds. Defaults to True.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
True if host is for the localhost/loopback; False otherwise
|
|
445
|
+
"""
|
|
446
|
+
host = normalize(host)
|
|
447
|
+
try:
|
|
448
|
+
htype = get_likely_type(host)
|
|
449
|
+
except ValueError:
|
|
450
|
+
return False
|
|
451
|
+
if htype == "address":
|
|
452
|
+
if _fast_is_local_address(host):
|
|
453
|
+
return True
|
|
454
|
+
if dns:
|
|
455
|
+
# this takes 30-40ms. slightly faster than checking all interfaces
|
|
456
|
+
if host in frozenset(
|
|
457
|
+
map(normalize, socket.gethostbyname_ex(get_hostname())[2])
|
|
458
|
+
):
|
|
459
|
+
return True
|
|
460
|
+
return False
|
|
461
|
+
else:
|
|
462
|
+
if _fast_is_local_hostname(host):
|
|
463
|
+
return True
|
|
464
|
+
if dns:
|
|
465
|
+
try:
|
|
466
|
+
addrinfos = socket.getaddrinfo(host, None)
|
|
467
|
+
except socket.gaierror:
|
|
468
|
+
return False
|
|
469
|
+
else:
|
|
470
|
+
for addrinfo in addrinfos:
|
|
471
|
+
if is_localhost(addrinfo[4][0], dns=dns):
|
|
472
|
+
return True
|
|
473
|
+
return False
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: whatamithinking-hostutil
|
|
3
|
+
Version: 5.0.2
|
|
4
|
+
Dynamic: Description
|
|
5
|
+
Dynamic: Description-Content-Type
|
|
6
|
+
Summary: hostname/address parsers/validators/utils for handling user inputs which could be either.
|
|
7
|
+
Author-email: connormaynes@gmail.com
|
|
8
|
+
Keywords: address,host,hostname,ip,ipv4,ipv6,networking
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: System :: Networking
|
|
12
|
+
Requires-Dist: netifaces
|
|
13
|
+
Requires-Dist: psutil
|
|
14
|
+
Requires-Dist: python-hosts
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: black; extra == 'dev'
|
|
17
|
+
Provides-Extra: test
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
whatamithinking/hostutil/__init__.py,sha256=GXcssV1S1vRSDm3Mw-61o9o3mDdyhqwrBFmRzCGCRqo,16439
|
|
2
|
+
whatamithinking_hostutil-5.0.2.dist-info/METADATA,sha256=Xc31Qrr4vWx7STH7omp33_wXiqGGEyUPICiEg9egl4o,605
|
|
3
|
+
whatamithinking_hostutil-5.0.2.dist-info/WHEEL,sha256=aha0VrrYvgDJ3Xxl3db_g_MDIW-ZexDdrc_m-Hk8YY4,105
|
|
4
|
+
whatamithinking_hostutil-5.0.2.dist-info/RECORD,,
|