respodns/respodns/util.py

127 lines
3.5 KiB
Python
Raw Normal View History

2020-08-29 05:54:07 -07:00
rot13_mapping = {}
for a, b, c, d in zip("anAN05", "mzMZ49", "naNA50", "zmZM94"):
2021-06-27 08:06:48 -07:00
rot13_mapping.update({chr(k): chr(v)
for k, v in zip(range(ord(a), ord(b) + 1),
range(ord(c), ord(d) + 1))})
2020-08-29 05:54:07 -07:00
2020-08-29 06:34:46 -07:00
2020-08-30 03:19:42 -07:00
def rot13(s):
return "".join(rot13_mapping.get(c, c) for c in s)
2020-08-29 01:16:06 -07:00
def right_now():
from datetime import datetime, timezone
return datetime.now(timezone.utc)
2020-08-29 06:34:46 -07:00
def nonsense_consistent(domain, weekly=True):
from datetime import datetime, timezone
2020-08-29 05:54:07 -07:00
from random import Random
from string import ascii_lowercase
from zlib import crc32
if weekly:
week = datetime.now(timezone.utc).isocalendar().week
week_bytes = bytes((week, week, week, week))
seed = crc32(week_bytes + domain.encode("utf-8"))
else:
seed = crc32(domain.encode("utf-8"))
rng = Random(seed)
2020-08-29 05:54:07 -07:00
length = rng.choices((9, 10, 11, 12), (4, 5, 3, 2))[0]
return "".join(rng.choice(ascii_lowercase) for i in range(length))
2020-08-29 06:34:46 -07:00
def concat_nonsense(domain, weekly=True):
return nonsense_consistent(domain, weekly=weekly) + "." + domain
2020-08-29 05:54:07 -07:00
2020-08-29 06:34:46 -07:00
2020-08-29 05:54:07 -07:00
def head(n, it):
2020-08-30 03:19:42 -07:00
# TODO: maybe just do return [a for _, a in zip(range(n), it)]
2020-08-29 05:54:07 -07:00
res = []
try:
while len(res) < n:
res.append(next(it))
except StopIteration:
pass
return res
2020-08-29 06:34:46 -07:00
2020-08-29 05:54:07 -07:00
def taskize(item):
from types import CoroutineType
from asyncio import Task, create_task
if isinstance(item, CoroutineType):
assert not isinstance(item, Task) # TODO: paranoid?
item = create_task(item)
return item
2020-08-29 06:34:46 -07:00
2020-08-29 05:54:07 -07:00
def make_pooler(pool_size, finisher=None):
2020-08-29 06:34:46 -07:00
# TODO: write a less confusing interface
# that allows the code to be written more flatly.
# maybe like: async for done in apply(doit, [tuple_of_args]):
2020-08-29 05:54:07 -07:00
from asyncio import wait, FIRST_COMPLETED
pending = set()
2020-08-29 06:34:46 -07:00
2020-08-29 05:54:07 -07:00
async def pooler(item=None):
nonlocal pending
finish = item is None
if not finish:
pending.add(taskize(item))
desired_size = 0 if finish else pool_size - 1
while len(pending) > desired_size:
done, pending = await wait(pending, return_when=FIRST_COMPLETED)
2020-08-31 23:22:05 -07:00
if finisher is not None:
finisher(done, pending)
2020-08-29 06:34:46 -07:00
2020-08-29 05:54:07 -07:00
return pooler
2020-08-29 06:34:46 -07:00
2020-08-29 05:54:07 -07:00
class AttrCheck:
"""
Inheriting AttrCheck prevents accidentally setting attributes
that don't already exist.
"""
def __setattr__(self, name, value):
# NOTE: hasattr doesn't do what we want here. dir does.
if name.startswith("_") or name in dir(self):
super().__setattr__(name, value)
else:
raise AttributeError(name)
async def _release_later(sem, time=1):
from asyncio import sleep
await sleep(time)
sem.release()
class LimitPerSecond:
def __init__(self, limit):
from asyncio import BoundedSemaphore
if type(limit) is not int:
raise ValueError("limit must be int")
assert limit > 0, limit
self.limit = limit
self.tasks = []
self.sem = BoundedSemaphore(limit)
async def __aenter__(self):
#if self.sem.locked:
# from sys import stderr
# print("THROTTLING", file=stderr)
await self.sem.acquire()
async def __aexit__(self, exc_type, exc_value, traceback):
from asyncio import create_task
task = create_task(_release_later(self.sem))
self.tasks.append(task)
async def finish(self):
for task in self.tasks:
await task