PK!QfFmúúpupy/__init__.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from pupy.foreign import chunks from pupy.foreign import digits_list from pupy.foreign import dirs_gen from pupy.foreign import files_gen from pupy.foreign import int_from_digits from pupy.foreign import is_permutation from pupy.foreign import iter_product from pupy.foreign import rotate from pupy.foreign import rotations_gen from pupy.savings_n_loads import ljson from pupy.savings_n_loads import load_jasm from pupy.savings_n_loads import lpak from pupy.savings_n_loads import lstr from pupy.savings_n_loads import lstring from pupy.savings_n_loads import safepath from pupy.savings_n_loads import save_jasm from pupy.savings_n_loads import savings from pupy.savings_n_loads import sjson from pupy.savings_n_loads import spak from pupy.savings_n_loads import sstr from pupy.savings_n_loads import sstring from pupy.utils import environ_dict from pupy.utils import fmt_bytes from pupy.utils import fmt_file_size from pupy.utils import fmt_seconds from pupy.utils import ls from pupy.utils import ls_dirs from pupy.utils import ls_files from pupy.utils import ls_files_dirs from pupy.utils import parent_dirpath from pupy.utils import path2name from pupy.utils import sync from pupy.utils import timestamp PK!<Ûšb››pupy/__main__.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python """ `python -mpupy` """ from pupy.cli import main if __name__ == "__main__": main() PK!&VÌmmpupy/_typing.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any from typing import Dict from typing import Iterable from typing import List from typing import Union Flint = Union[int, float] # float or int Paths = Iterable[str] # iterable of path-strings JASM = Union[None, bool, int, float, str, List[Any], Dict[str, Any]] # JSON obj PK!ü_tÛHHpupy/_version.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from pathlib import Path __version__ = ( [ l for l in open(str(Path(__file__).resolve().parents[1] / "pyproject.toml")) .read() .split("\n") if "version" in l ][0] .replace("version = ", "") .strip('"') ) PK!”$Èz@@ pupy/aio.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from pupy._typing import JASM try: import aiofiles except ImportError: raise ImportError( "'pip install aiofiles'..." " if ya wanna use this module!" ) try: from ujson import dumps from ujson import load from ujson import loads except: from json import dumps from json import load from json import loads try: from toml import dumps as toml_dumps from toml import load as toml_load from toml import loads as toml_loads except ImportError: pass try: from ruamel.yaml import YAML _yaml_saver = YAML() _yaml_loader = YAML(typ="safe") except ImportError: pass async def sabytes(filepath: str, bytes: bytes) -> None: """Read bytes from file path :param filepath: filepath as as string to read bites from :return: some bytes... """ async with aiofiles.open(filepath, mode="wb") as f: await f.write(bytes) async def labytes(filepath: str) -> bytes: """Read bytes from file path :param filepath: filepath as as string to read bites from :return: some bytes... """ async with aiofiles.open(filepath, mode="rb") as f: _bytes = await f.read() return _bytes async def sastring(filepath: str, string: str) -> None: try: async with aiofiles.open(filepath, mode="w", encoding="utf-8") as f: await f.write(string) # except NameError: # raise ImportError("'pip install aiofiles' if ya wanna use this!") except UnicodeEncodeError: async with aiofiles.open(filepath, mode="w", encoding="latin2") as f: await f.write(string) async def lastring(filepath: str) -> str: try: async with aiofiles.open(filepath, mode="r", encoding="utf-8") as f: _file_string = await f.read() return _file_string except UnicodeDecodeError: async with aiofiles.open(filepath, mode="r", encoding="latin2") as f: _file_string = await f.read() return _file_string async def sajson(filepath: str, data: JASM, min: bool = False) -> None: """Save json-serial-ize-able data to a specific filepath. :param filepath: destination filepath :param data: json cereal-izable dictionary/list/thing :param min: Bool flag -- minify the json file :return: None """ if type(data) == dict and any(type(val) == bytes for val in data.values()): data = {k: str(v, encoding="utf-8") for k, v in data.items()} if min: _json_str = dumps(data, ensure_ascii=False) else: _json_str = dumps(data, indent=4, sort_keys=True, ensure_ascii=False) await sastring(filepath, _json_str) async def lajson(filepath: str) -> JASM: """Load a json file given a filepath and return the file-data :param filepath: path to the jasm file you want to load :return: Loaded file contents """ _json_str = await lastring(filepath) return loads(filepath) async def latoml(filepath: str) -> JASM: try: _toml_str = await lastring(filepath) return toml_loads(_toml_str) except NameError: raise EnvironmentError("'pip install toml' if you wanna use this!") async def satoml(filepath: str, data: JASM) -> None: try: filepath = filepath if "." in filepath else "{}.toml".format(filepath) _toml_str = toml_dumps(data) await sastring(filepath, _toml_str) except NameError: raise EnvironmentError("'pip install toml' if you wanna use this!") if __name__ == "__main__": pass # from doctest import testmod # testmod() PK!n¦Åá pupy/aio.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from pupy._typing import JASM async def sabytes(filepath: str, bytes: bytes) -> None: ... async def labytes(filepath: str) -> bytes: ... async def sastring(filepath: str, string: str) -> None: ... async def lastring(filepath: str) -> str: ... async def sajson(filepath: str, data: JASM, min: bool = ...) -> None: ... async def lajson(filepath: str) -> JASM: ... async def latoml(filepath: str) -> JASM: ... async def satoml(filepath: str, data: JASM) -> None: ... PK!‘Ò“‹·%·%pupy/amazon_prime.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from bisect import bisect from bisect import bisect_right from collections.abc import MutableSequence from itertools import count from math import sqrt from typing import Any from typing import Iterable from typing import Iterator from typing import List from typing import Optional from typing import Union from pupy.decorations import cash_it from pupy.maths import divisors_gen def prime_gen( plim: int = 0, kprimes: Union[None, Iterable[int]] = None ) -> Iterator[int]: """Infinite (within reason) prime number generator My big modification is the pdiv_dictionary() function that recreats the dictionary of divisors so that you can continue to generate prime numbers from a (sorted) list of prime numbers. Based on: eratosthenes by David Eppstein, UC Irvine, 28 Feb 2002 http://code.activestate.com/recipes/117119/ and the thread at the url :param plim: prime_limit; default=0 makes for an infinite generator :type plim: int :param kprimes: known_primes as an iterable (Default value = None) :type kprimes: iter .. doctest:: >>> list(prime_gen(50)) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] >>> list(prime_gen(10)) [2, 3, 5, 7] """ if kprimes is None: kprimes = [2, 3, 5, 7, 11] def _dictionary(): """Recreates the prime divisors dictionary used by the generator""" div_dict = {} for pdiv in kprimes: multiple = kprimes[-1] // pdiv * pdiv if multiple % 2 == 0: multiple += pdiv else: multiple += 2 * pdiv while multiple in div_dict: multiple += pdiv * 2 div_dict[multiple] = pdiv return div_dict # [1] # See if the upper bound is greater than the known primes if 0 < plim <= kprimes[-1]: for p in kprimes: if p <= plim: yield p return # return bc we are done # [2] # Recreate the prime divisibility dictionary using kprimes; # Set start and yield first 4 primes divz = _dictionary() start = kprimes[-1] + 2 # max prime + 2 (make sure it is odd) if start == 13: yield 2 yield 3 yield 5 yield 7 yield 11 # use count or range depending on if generator is infinite it = count(start, 2) if plim == 0 else range(start, plim, 2) for num in it: prime_div = divz.pop(num, None) if prime_div: multiple = (2 * prime_div) + num while multiple in divz: multiple += 2 * prime_div divz[multiple] = prime_div else: divz[num * num] = num yield num def prime_factorization_gen(n: int) -> Iterator[int]: """generates all numbers in the prime factorization of n :param n: number to be factored :type n: int .. doctest:: >>> list(prime_factorization_gen(12)) [2, 2, 3] >>> list(prime_factorization_gen(16)) [2, 2, 2, 2] """ for factor in prime_factors_gen(n): if n <= 1: break while n % factor == 0: n //= factor yield factor def prime_factors_gen(n: int) -> Iterator[Any]: """prime factors generator :param n: number to be factorized :type n: int .. doctest:: python >>> list(prime_factors_gen(12)) [2, 3] >>> list(prime_factors_gen(16)) [2] """ return (p for p in divisors_gen(n) if is_prime(p)) @cash_it def is_prime(number: int) -> bool: """Checks if a number is prime :param number: number to check if is prime :type number: int :returns: -> True if number is prime :rtype: bool .. doctest:: python >>> is_prime(1) False >>> is_prime(2) True >>> is_prime(3) True >>> is_prime(4) False >>> is_prime(5) True >>> is_prime(6) False >>> is_prime(7) True >>> is_prime(100) False >>> is_prime(89) True """ if number == 2 or number == 3: return True if number < 2 or number % 2 == 0: return False if number < 9: return True if number % 3 == 0: return False for step in range(5, int(sqrt(number)) + 1, 6): if step >= number: break if number % step == 0: return False if number % (step + 2) == 0: return False return True class OctopusPrime(MutableSequence): """OctopusPrime, the 8-leg autobot, here to help you find PRIMES .. ───────────▄▄▄▄▄▄▄▄▄─────────── ────────▄█████████████▄──────── █████──█████████████████──█████ â–████▌─▀███▄───────▄███▀─â–████▌ ─█████▄──▀███▄───▄███▀──▄█████─ ─â–██▀███▄──▀███▄███▀──▄███▀██▌─ ──███▄▀███▄──▀███▀──▄███▀▄███── ──â–█▄▀█▄▀███─▄─▀─▄─███▀▄█▀▄█▌── ───███▄▀█▄██─██▄██─██▄█▀▄███─── ────▀███▄▀██─█████─██▀▄███▀──── ───█▄─▀█████─█████─█████▀─▄█─── ───███────────███────────███─── ───███▄────▄█─███─█▄────▄███─── ───█████─▄███─███─███▄─█████─── ───█████─████─███─████─█████─── ───█████─████─███─████─█████─── ───█████─████─███─████─█████─── ───█████─████▄▄▄▄▄████─█████─── ────▀███─█████████████─███▀──── ──────▀█─███─▄▄▄▄▄─███─█▀────── ─────────▀█▌â–█████▌â–█▀───────── ────────────███████──────────── """ def __init__(self, plim: int = 100) -> None: # list.__init__(self, list(prime_gen(plim=plim))) # super(OctopusPrime, self).__init__() p = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, ] if plim == 100: self._list = p[:] elif plim < 100: self._list = list(filter(lambda n: n <= plim, p)) else: self._list = list(prime_gen(plim, p)) self.max_loaded = self._list[-1] def _transform(self, n: Optional[int] = None) -> None: """TRANSFORM / grow the list :param n: (Default value = None) """ n = n if n is not None else self._list[-1] * 10 self._list.extend(list(prime_gen(plim=n, kprimes=self._list))) def primes_below(self, upper_bound): """Lists primes, p, such that p < upper_bound :param upper_bound: exclusive upper bound :type upper_bound: int :returns: -> primes less than upper_bound :rtype: list """ return self.primes_between(1, upper_bound) def primes_between(self, lower_bound: int, upper_bound: int) -> List[int]: """Lists primes, p, such that, lower_bound < p < upper_bound :param lower_bound: exclusive lower bound :type lower_bound: int :param upper_bound: exclusive upper bound :type upper_bound: int :returns: -> primes between lower_bound and upper_bound :rtype: list """ if upper_bound > self[-1]: self._transform(upper_bound) return self[bisect_right(self, lower_bound) : bisect(self, upper_bound)] def __len__(self) -> int: return len(self._list) def __getitem__(self, i: Union[int, slice]) -> Union[int, List[int]]: return self._list[i] def __delitem__(self, i): del self._list[i] def __setitem__(self, key, value): self._list[key] = value def insert(self, index, object): """ :param index: :param object: """ self._list.insert(index) def __str__(self): return str(self._list) def __repr__(self): return str(self._list) if __name__ == "__main__": from doctest import testmod testmod() PK!¦ ôüüpupy/amazon_prime.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from collections.abc import MutableSequence from typing import Any from typing import Iterable from typing import Iterator from typing import List from typing import Union def prime_gen( plim: int = ..., kprimes: Union[None, Iterable[int]] = ... ) -> Iterator[int]: ... def prime_factorization_gen(n: int) -> Iterator[int]: ... def prime_factors_gen(n: int) -> Iterator[Any]: ... def is_prime(number: int) -> bool: ... class OctopusPrime(MutableSequence): max_loaded: Any = ... def __init__(self, plim: int = ...) -> None: ... def primes_below(self, upper_bound: Any): ... def primes_between(self, lower_bound: int, upper_bound: int) -> List[int]: ... def __len__(self) -> int: ... def __getitem__(self, i: Union[int, slice]) -> Union[int, List[int]]: ... def __delitem__(self, i: Any) -> None: ... def __setitem__(self, key: Any, value: Any) -> None: ... def insert(self, index: Any, object: Any) -> None: ... PK!Ó!5ª¤¤ pupy/cli.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python """ ======== Pupy CLI ======== """ from argparse import ZERO_OR_MORE from argparse import ArgumentParser from codecs import decode from os import listdir from os import rename from pupy._version import __version__ def unescaped_str(arg_str): """ :param arg_str: :return: """ return decode(str(arg_str), "unicode_escape") parser = ArgumentParser(description="Command description.") parser.add_argument( "-r", "--replace", metavar="PAT", nargs=2, help="Rename all things in." ) parser.add_argument("-V", "--version", action="store_true", help="Print pupy version.") parser.add_argument( "--rm-pattern", metavar="PAT", type=unescaped_str, nargs=ZERO_OR_MORE, help="REPLACE pattern with a space.", ) def main(args=None): """ :param args: """ args = parser.parse_args(args=args) if args.version: print("Pupy version: {}".format(__version__)) if args.replace: frum, two = args.replace print("Replacing:", frum) print("With:", two) for f in listdir("."): print("For f/d:", f) rename(f, f.replace(frum, two)) if args.rm_pattern: for pattern in args.rm_pattern: print("Removing pattern:", pattern) for f in listdir("."): print("For f/d:", f) rename(f, f.replace(pattern, "")) PK!,ïÎÛÛ pupy/cli.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any from typing import Optional def unescaped_str(arg_str: Any): ... parser: Any def main(args: Optional[Any] = ...) -> None: ... PK!ȸªKÙÙpupy/decorations.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from cProfile import Profile from functools import wraps from inspect import getfile from logging import DEBUG from logging import config from logging import getLogger from os import chdir from os import getcwd from os import makedirs from os import mkdir from os import path from time import time from pupy.utils import fmt_seconds # "%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s" logging_config = dict( version=1, formatters={ "f": { "format": "[%(levelname)1.1s %(asctime)s %(filename)s:%(lineno)d] %(message)s" } }, handlers={ "h": {"class": "logging.StreamHandler", "formatter": "f", "level": DEBUG} }, root={"handlers": ["h"], "level": DEBUG}, ) config.dictConfig(logging_config) logger = getLogger() def in_n_out(funk): """Chdir in to the dir the test_function is in and change dirs out when done :type funk: Callable :param funk: docin.api logger functions logger.(debug/info/warn/error) :return: wrapped function """ @wraps(funk) def chin_n_chout(*args, **kwargs): """ :param args: :param kwargs: :return: """ # thing = os.path.split(os.path.realpath(__file__)) cd = getcwd() dirpath = args[0] if path.isdir(dirpath): chdir(dirpath) funk_results = funk(*args, **kwargs) chdir(cd) return funk_results return chin_n_chout def flog(funk=None, loglevel="debug", funk_call=True, tictoc=False): """ :param funk: :param loglevel: :param funk_call: :param tictoc: :return: """ _log_levels = { "debug": logger.debug, "info": logger.info, "warn": logger.warning, "error": logger.error, } def _decorate_flog_wrapper(_funk): def _fmt_args(*args): return ", ".join(str(arg) for arg in args) def _fmt_kwargs(**kwargs): return ", ".join("{}={}".format(str(k), str(v)) for k, v in kwargs.items()) def _fmt_call(*args, **kwargs): params_str = ", ".join( s for s in (_fmt_args(*args), _fmt_kwargs(**kwargs)) if s ) return "{}({})".format(_funk.__name__, params_str) @wraps(_funk) def _flog_wrapper(*args, **kwargs): ti = time() _ret = _funk(*args, **kwargs) tf = time() msg_parts = [ _fmt_call(*args, **kwargs) if funk_call else None, fmt_seconds(ti, tf) if tictoc else None, ] msg_str = " | ".join(part for part in msg_parts if part) if any(el for el in msg_parts): _log_levels[loglevel]("[FLOG] | {}".format(msg_str)) return _ret return _flog_wrapper return _decorate_flog_wrapper(funk) if funk else _decorate_flog_wrapper def dirdec(funk): """ :param funk: :return: """ @wraps(funk) def _wrapper(*args, **kwargs): result = funk(*args, **kwargs) try: mkdir(result) except (FileExistsError, OSError) as e: pass return result return _wrapper def mkdirs(funk): """ :param funk: :return: """ @wraps(funk) def _wrapper(*args, **kwargs): try: dirpath = path.split(args[0])[0] except IndexError: dirpath = path.split(kwargs["filepath"])[0] try: makedirs(dirpath, exist_ok=True) except OSError: pass except TypeError: pass return funk(*args, **kwargs) return _wrapper def cash_it(funk): """args-2-return value cache. This function is particularly useful for when you want that lru-cache, but you/one is working with python two. :param funk: function to be cached :type funk: function :returns: wrapped function :rtype: function """ cash_money = {} @wraps(funk) def cash_wrap(*argz): """ """ if argz not in cash_money: rv = funk(*argz) cash_money[argz] = rv return cash_money[argz] return cash_wrap def cprof(funk): """"cProfiling decorator src: https://zapier.com/engineering/profiling-python-boss/ :param funk: funktion to decorate and 'get tha c prof of' """ @wraps(funk) def profiled_funk(*args, **kwargs): """wrapper funk""" profile = Profile() try: profile.enable() ret_val = funk(*args, **kwargs) profile.disable() finally: print("__CPROFILE__") profile.print_stats() return ret_val return profiled_funk class tictoc(object): """Timing decorator object :param runs: # of runs to time over (defaults to 1) """ def __init__(self, runs=1): self.runs = runs def __str__(self, t_total, funk, args_string): _fmt_strs = ( "__TICTOC__", " file: {}".format(getfile(funk)), " funk: {}".format(funk.__name__), " args: {}".format(args_string), " time: {}".format(fmt_seconds(t_total)), " runs: {}".format(self.runs), ) return "\n".join(_fmt_strs) def __call__(self, time_funk, printing=True): @wraps(time_funk) def time_wrapper(*args, **kwargs): """ :param args: :param kwargs: :return: """ self.args = str(args) ts = time() for i in range(self.runs): result = time_funk(*args, **kwargs) te = time() t_total = (te - ts) / self.runs if printing: print(self.__str__(t_total, time_funk, self.args)) return result return time_wrapper def requires(package): def _requires(_funk): @wraps(_funk) def _wrapper(*args, **kwargs): try: return _funk(*args, **kwargs) except ImportError as e: raise eImportError("'pip install {}' to use this!".format(package)) return _wrapper return _requires PK!ðGKKpupy/decorations.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any from typing import Optional logging_config: Any logger: Any def in_n_out(funk: Any): ... def flog( funk: Optional[Any] = ..., loglevel: str = ..., funk_call: bool = ..., tictoc: bool = ..., ): ... def dirdec(funk: Any): ... def mkdirs(funk: Any): ... def cash_it(funk: Any): ... def cprof(funk: Any): ... class tictoc: runs: Any = ... def __init__(self, runs: int = ...) -> None: ... args: Any = ... def __call__(self, time_funk: Any, printing: bool = ...): ... PK!ø¶Ìpupy/foreign.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from collections import Counter from collections import deque from functools import reduce from operator import mul from os import path from os import sep from os import walk from typing import Any from typing import Iterable from typing import Iterator from typing import List from typing import Tuple from typing import Union from pupy._typing import Flint def files_gen(dirpath: str = ",", abs: bool = True) -> Iterator[Any]: """Yields paths beneath dirpath param; dirpath defaults to os.getcwd() :param dirpath: Directory path to walking down/through. :param abs: Yield the absolute path :return: Generator object that yields filepaths (absolute or relative) """ return ( fpath if abs else fpath.replace(dirpath, "").strip(sep) for fpath in ( path.join(pwd, file) for pwd, dirs, files in walk(dirpath) for file in files ) ) def dirs_gen(dirpath: str = ".", abs: bool = True) -> Iterator[Any]: """Yields paths beneath dirpath param; dirpath defaults to os.getcwd() :param dirpath: Directory path to walking down/through. :param abs: Yield the absolute path :return: Generator object that yields dir-paths (absolute or relative) """ return ( fpath if abs else fpath.replace(dirpath, "").strip(sep) for fpath in (pwd for pwd, dirs, files in walk(dirpath)) ) def exhaust(it: Iterable[Any]) -> None: """Exhaust an interable / use it up; useful for evaluating a map object. :param it: iterable object to run through :return: None .. docstring::python >>> a = [1, 2, 3, 4, 5, 6] >>> a_map = map(lambda x: x*2, a) >>> a_exhausted = exhaust(a_map) >>> a_exhausted == None # will be none after being exhausted True """ deque(it, maxlen=0) def chunks(it: Union[List[int], str], chunk_size: int) -> Iterator[Any]: """Yields chunks of something slicable with length <= chunk_size :param it: :param chunk_size: size of the chunks :type chunk_size: int .. docstring::python >>> list(chunks([1, 2, 3, 4, 5, 6], 3)) [[1, 2, 3], [4, 5, 6]] >>> list(chunks([1, 2, 3, 4, 5, 6], 2)) [[1, 2], [3, 4], [5, 6]] >>> list(chunks('abcdefghijklmnopqrstuvwxyz', 2)) ['ab', 'cd', 'ef', 'gh', 'ij', 'kl', 'mn', 'op', 'qr', 'st', 'uv', 'wx', 'yz'] >>> list(chunks('abcdefghijklmnopqrstuvwxyz', 13)) ['abcdefghijklm', 'nopqrstuvwxyz'] """ return (it[i : i + chunk_size] for i in range(0, len(it), chunk_size)) def is_permutation( a: Union[int, List[int], str], b: Union[int, List[int], str] ) -> bool: """Checks if two integers or lists are permutations lists are permutations :param a: possible perumtation of b :type a: int or list :param b: possible perumtation of a :type b: int or list :returns: True if a and b are permutations of one another; False otherwise :rtype: bool It works for list!!! .. doctest:: python >>> a = [1, 2, 3, 4] >>> b = [4, 3, 2, 1] >>> is_permutation(a, b) True >>> a = [1, 3, 2, 4] >>> b = [4, 3, 2, 1] >>> is_permutation(a, b) True >>> a = [1, 3, 2, 4] >>> b = [4, 3, 2, 1, 1] >>> is_permutation(a, b) # False cuz of the extra 'one' False It works for integers!?!?!? .. doctest:: python >>> a = 1234 >>> b = 4321 >>> is_permutation(a, b) True >>> a = 12344 >>> b = 43212 >>> is_permutation(a, b) False It also works for strings!!! .. doctest:: python >>> a = 'abcd' >>> b = 'dbca' >>> is_permutation(a, b) # False cuz of the extra 'one' True >>> a = 'pood' >>> b = 'doop' >>> is_permutation(a, b) # False cuz of the extra 'one' True >>> a = 'snorkel' >>> b = 'doop' >>> is_permutation(a, b) # False cuz of the extra 'one' False """ if isinstance(a, int): a = digits_list(a) if isinstance(b, int): b = digits_list(b) return len(a) == len(b) and Counter(a) == Counter(b) def rotate(rlist: List[int], rn: int = 1, left_rotate: bool = True) -> List[int]: """Rotate a list (rlist) by rn indices to the left or right :param rlist: list/toople or slicable to be rotated :type rlist: list or tuple :param rn: steps bywhich to rotate (Default value = 1) :type rn: int :param left_rotate: True (default) left rotates; False right rotates. :type left_rotate: bool :returns: rotated list :rtype: list .. doctest:: python >>> rotate([1, 2, 3, 4], left_rotate=True) [2, 3, 4, 1] >>> rotate([1, 2, 3, 4], left_rotate=False) [4, 1, 2, 3] >>> rotate([1, 2, 3, 4], rn=4, left_rotate=False) [1, 2, 3, 4] """ def _left_rotate(l, n=1): """ :param l: :param n: (Default value = 1) """ return l[n:] + l[:n] def _right_rotate(l, n=1): """ :param l: :param n: (Default value = 1) """ return l[-n:] + l[:-n] return _left_rotate(rlist, rn) if left_rotate else _right_rotate(rlist, rn) def rotations_gen(rlist: Tuple[int, int, int, int]) -> Iterator[Any]: """Yields all rotations of a list :param rlist: .. doctest::python >>> for rot in rotations_gen((1, 2, 3, 4)): ... print(rot) ... (1, 2, 3, 4) (4, 1, 2, 3) (3, 4, 1, 2) (2, 3, 4, 1) """ return ((rlist[-i:] + rlist[:-i]) for i in range(len(rlist))) def digits_list(number: int) -> List[int]: """Returns a list of the digits in num :param number: number w/ digits to be listsed :type number: int :returns: -> digits in a list :rtype: list .. doctest::python >>> digits_list(1111) [1, 1, 1, 1] >>> digits_list(982) [9, 8, 2] >>> digits_list(101) [1, 0, 1] >>> digits_list(123) [1, 2, 3] """ digits = deque() for _ in range(len(str(number))): number, r = divmod(number, 10) digits.appendleft(r) return list(digits) def int_from_digits(digits: Iterable[int]) -> int: """Converts an iterable of digits digits to a number The iteratble can be ints or strings/chars :rtype: int .. doctest::python >>> int_from_digits([3, 2, 1]) 321 >>> int_from_digits([1, 1, 1, 1, 2, 3]) 111123 >>> int_from_digits([1, 2, 3]) 123 """ return sum( digits[len(list(digits)) - i - 1] * 10 ** i for i in range(0, len(list(digits)), 1) ) def iter_product(l: Iterable[int]) -> Flint: """Product of all the elements in a list or tuple :param l: list with integer elements :returns: product of all the elements in a list :rtype: int .. doctest::python >>> iter_product([1, 2, 3, 4]) 24 >>> iter_product(tuple([1, 2, 3, 4])) 24 >>> iter_product([-1, -2, -3, 4]) -24 """ return reduce(mul, l) PK!Øî_ð££pupy/foreign.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any from typing import Iterable from typing import Iterator from typing import List from typing import Tuple from typing import Union from pupy._typing import Flint def files_gen(dirpath: str = ..., abs: bool = ...) -> Iterator[Any]: ... def dirs_gen(dirpath: str = ..., abs: bool = ...) -> Iterator[Any]: ... def exhaust(it: Iterable[Any]) -> None: ... def chunks(it: Union[List[int], str], chunk_size: int) -> Iterator[Any]: ... def is_permutation( a: Union[int, List[int], str], b: Union[int, List[int], str] ) -> bool: ... def rotate(rlist: List[int], rn: int = ..., left_rotate: bool = ...) -> List[int]: ... def rotations_gen(rlist: Tuple[int, int, int, int]) -> Iterator[Any]: ... def digits_list(number: int) -> List[int]: ... def int_from_digits(digits: Iterable[int]) -> int: ... def iter_product(l: Iterable[int]) -> Flint: ... PK!ú=¼¸  pupy/lin.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from os import symlink from os import unlink from subprocess import PIPE from subprocess import run def rsync(src: str, dest: str, delete: bool = False): """Sheldon rsync wrapper for syncing tdirs :param delete: :param dest: path to local tdir :param src: path to remote (raid) tdir :return: subprocess return code from rsync Rsync return codes: - 0 == Success - 1 == Syntax or usage error - 2 == Protocol incompatibility - 3 == Errors selecting input/output files, dirs - 4 == Requested action not supported: an attempt was made to manipulate 64-bit files on a platform that cannot support them; or an option was specified that is supported by the client and not the server. - 5 == Error starting client-server protocol - 6 == Daemon unable to append to log-file - 10 == Error in socket I/O - 11 == Error in file I/O - 12 == Error in rsync protocol data stream - 13 == Errors with program diagnostics - 14 == Error in IPC code - 20 == Received SIGUSR1 or SIGINT - 21 == Some error returned by waitpid() - 22 == Error allocating core memory buffers - 23 == Partial transfer due to error - 24 == Partial transfer due to vanished source files - 25 == The --max-delete limit stopped deletions - 30 == Timeout in data send/receive - 35 == Timeout waiting for daemon connection """ subproc = run(["mkdir", "-p", dest], stdout=PIPE, stderr=PIPE) if not dest.endswith("/"): dest = "{}/".format(dest) if not src.endswith("/"): src = "{}/".format(src) rsync_args = [ "rsync", "-a", "-O", "--no-o", "--no-g", "--no-p", "--delete" if delete else None, src, dest, ] subproc = run(args=list(filter(None, rsync_args)), stdout=PIPE, stderr=PIPE) return subproc def link_dir(linkpath, targetpath): symlink(targetpath, linkpath) def link_dirs(link_target_tuples): for link, target in link_target_tuples: link_dir(link, target) def link_file(linkpath: str, targetpath: str) -> None: symlink(targetpath, linkpath) def link_files(link_target_tuples): for link, target in link_target_tuples: link_file(link, target) def unlink_dir(link): unlink(link) def unlink_dirs(links): for link in links: unlink(link) def unlink_file(link): unlink(link) def unlink_files(links): for link in links: unlink(link) PK!é4Þá pupy/lin.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any def rsync(src: str, dest: str, delete: bool = ...) -> Any: ... def link_dir(linkpath: Any, targetpath: Any) -> None: ... def link_dirs(link_target_tuples: Any) -> None: ... def link_file(linkpath: str, targetpath: str) -> None: ... def link_files(link_target_tuples: Any) -> None: ... def unlink_dir(link: Any) -> None: ... def unlink_dirs(links: Any) -> None: ... def unlink_file(link: Any) -> None: ... def unlink_files(links: Any) -> None: ... PK!o>¹ˆÕBÕB pupy/maths.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from collections import Counter from math import acos from math import factorial from math import pi from math import sqrt from operator import add from operator import floordiv from operator import methodcaller from operator import sub from operator import truediv from typing import Any from typing import Iterator from typing import List from typing import Optional from typing import Set from typing import Tuple from typing import Type from typing import Union from pupy.decorations import cash_it from pupy.foreign import iter_product def partitions_gen(numero: int, min_p: int = 1, max_p: Optional[int] = None): """Partitions generator Adapted from: code.activestate.com/recipes/218332-generator-for-integer-partitions/min_p :param numero: number for which to yield partiton tuples :type numero: int :param min_p: smallest part size (Default value = 1) :type min_p: int :param max_p: largest part size (Default value = None) :type max_p: int .. docstring::python >>> list(partitions_gen(4)) [(4,), (1, 3), (1, 1, 2), (1, 1, 1, 1), (2, 2)] >>> list(partitions_gen(4, min_p=1, max_p=2)) [(1, 1, 2), (1, 1, 1, 1), (2, 2)] """ if max_p is None or max_p >= numero: yield (numero,) for i in range(min_p, numero // 2 + 1): for p in partitions_gen(numero - i, i, max_p): yield (i,) + p @cash_it def rfactorial(n: int) -> int: """Recursive factorial function :param n: .. docstring::python >>> from math import factorial >>> rfactorial(1) == factorial(1) True >>> rfactorial(2) == factorial(2) True >>> rfactorial(3) == factorial(3) True >>> rfactorial(4) == factorial(4) True >>> rfactorial(5) == factorial(5) True >>> rfactorial(6) == factorial(6) True >>> rfactorial(7) == factorial(7) True >>> rfactorial(8) == factorial(8) True >>> rfactorial(9) == factorial(9) True """ if n == 1: return 1 else: return rfactorial(n - 1) * n def radians_2_degrees(rads: float) -> float: """Converts radians to degrees :param rads: .. doctest:: python >>> from math import pi >>> radians_2_degrees(2*pi) == 360.0 True >>> radians_2_degrees(2*pi) 360.0 """ return 180 * rads / pi def degrees_2_radians(degs: float) -> float: """Converts degrees to radians :param degs: .. doctest:: python >>> from math import pi >>> degrees_2_radians(360.0) == 2*pi True """ return degs * pi / 180 def power_mod(number: int, exponent: int, mod: int) -> int: """ :param number: :param exponent: :param mod: .. doctest:: python >>> power_mod(2, 4, 3) 2 >>> power_mod(12, 3, 4) 144 >>> power_mod(123, 2, 3) 123 >>> power_mod(120, 6, 10) 14400 >>> power_mod(120, 6, 1) 14400 """ if exponent > 0: if exponent % 2 == 0: return power_mod(number, floordiv(exponent, 2), mod) return power_mod(number, floordiv(exponent, 2), mod) * number else: return 1 def divisors_gen(n: int) -> Iterator[int]: """Divisors generator :param n: number w/ divisors to be generated :type n: int .. doctest:: python >>> list(divisors_gen(1)) [1] >>> list(divisors_gen(4)) [1, 2, 4] >>> list(divisors_gen(16)) [1, 2, 4, 8, 16] """ large_divisors = [] for i in range(1, int(sqrt(n) + 1)): if n % i == 0: yield i if i * i != n: large_divisors.append(n // i) for divisor in reversed(large_divisors): yield divisor def gcd_it(a: int, b: int) -> int: """iterative gcd :param a: :param b: >>> from pupy.maths import gcd_it >>> from pupy.maths import gcd_r >>> gcd_it(1, 4) == gcd_r(1, 4) True >>> gcd_it(2, 6) == gcd_r(2, 6) True >>> gcd_it(3, 14) == gcd_r(3, 14) True >>> gcd_it(4, 300) == gcd_r(4, 300) True """ while a: a, b = b % a, a return b @cash_it def gcd_r(a: int, b: int) -> int: """recursive greatest common divisor :param a: :param b: .. doctest:: python >>> from pupy.maths import gcd_it >>> from pupy.maths import gcd_r >>> gcd_it(1, 4) == gcd_r(1, 4) True >>> gcd_it(2, 6) == gcd_r(2, 6) True >>> gcd_it(3, 14) == gcd_r(3, 14) True >>> gcd_it(4, 300) == gcd_r(4, 300) True """ if b > a: return gcd_r(b, a) r = a % b if r == 0: return b return gcd_r(r, b) def reverse(n: int) -> int: """Reverses a number :param n: number to be reversed :type n: int :returns: reversed of a number :rtype: int .. doctest:: python >>> reverse(1) 1 >>> reverse(12345) 54321 >>> reverse(54321) 12345 >>> reverse(54321000) 12345 """ reversed = 0 while n > 0: reversed *= 10 reversed += n % 10 n //= 10 return reversed @cash_it def fib_r(n: int) -> int: """Recursively the nth fibonacci number :param n: nth fibonacci sequence number :type n: int :returns: the nth fibonacci number :rtype: int .. doctest:: python >>> fib_r(1) 1 >>> fib_r(2) 2 >>> fib_r(6) 13 """ return n if n < 3 else fib_r(n - 1) + fib_r(n - 2) def expo(d: int, n: int) -> int: """greatest exponent for a divisor of n :param d: divisor :type d: int :param n: number be divided :type n: int :returns: number of times a divisor divides n :rtype: int .. doctest:: python >>> expo(100, 2) 2 >>> expo(12, 5) 0 >>> expo(12, 2) 2 >>> expo(160, 4) 2 >>> expo(1000, 4) 1 """ if n < d: # flip d, n = n, d c = n divs = 0 while c % d == 0: c //= d divs += 1 return divs def pytriple_gen(max_c: int) -> Iterator[Tuple[int, int, int]]: """primative pythagorean triples generator special thanks to 3Blue1Brown's video on pythagorean triples https://www.youtube.com/watch?v=QJYmyhnaaek&t=300s :param max_c: max value of c to yeild triples up to :type max_c: int """ for real_pts in range(2, int(sqrt(max_c)) + 1, 1): for imag_pts in range(real_pts % 2 + 1, real_pts, 2): comp = complex(real_pts, imag_pts) sqrd = comp * comp real = int(sqrd.real) imag = int(sqrd.imag) if abs(real - imag) % 2 == 1 and gcd_it(imag, real) == 1: sea = int((comp * comp.conjugate()).real) if sea > max_c: break else: yield (imag, real, sea) if real > imag else (real, imag, sea) def n_permutations_with_replacements( it: Union[ Tuple[int, int, int, int], Tuple[int, int, int, int, int, int, int], Tuple[int, int, int, int, int], Tuple[int, int, int, int, int, int], ] ) -> int: """ .. doctest:: python >>> n_permutations_with_replacements((1, 2, 3, 4)) 24 >>> n_permutations_with_replacements((1, 2, 3, 4, 3)) 60 >>> n_permutations_with_replacements((1, 2, 3, 4, 3, 3)) 120 >>> n_permutations_with_replacements((1, 2, 3, 4, 3, 4, 4)) 420 """ c = Counter(n for n in it) a = list(factorial(nc) for nc in c.values()) return factorial(len(it)) // iter_product(a) def disjoint( a: Union[List[int], List[Union[str, int]]], b: Union[List[int], List[Union[str, int]]], ) -> bool: """ :param a: :param b: .. doctest:: python >>> a = [1, 2, 3, 4] >>> b = [2, 3, 4, 5] >>> disjoint(a, b) False >>> a = [1, 2, 3, 4] >>> b = [5, 6, 7, 8] >>> disjoint(a, b) True >>> a = ['joe', 'frank', 3, 4] >>> b = [5, 6, 7, 'frank'] >>> disjoint(a, b) False """ return not any(ae in b for ae in a) def set_cmp( a: Union[Set[int], List[int]], b: Union[Set[int], List[int]] ) -> Tuple[Set[int], Set[int], Set[int]]: """Compare the elements of two iterables (a and b) :param a: first iterable to compare elements of :param b: second iterable to compare elements of :return: tuple of sets of the form (common elements, in a only, in b only) .. doctest:: >>> a = [n for n in range(6)] >>> a [0, 1, 2, 3, 4, 5] >>> b = list(range(3, 9)) >>> b [3, 4, 5, 6, 7, 8] >>> set_cmp(a, b) ({3, 4, 5}, {0, 1, 2}, {8, 6, 7}) """ if not isinstance(a, set) or not isinstance(b, set): return set_cmp(set(a), set(b)) return a & b, a - b, b - a def n_choose_r(n, r): """ :param n: :param r: """ return factorial(n) // factorial(r) // factorial(n - r) class Trigon(object): """Trigon object composed of three points connected by lines.""" def __init__( self, pt1: Union[Tuple[int, int]], pt2: Union[Tuple[int, int]], pt3: Union[Tuple[int, int]], ) -> None: self.pt1 = Vuple(pt1) self.pt2 = Vuple(pt2) self.pt3 = Vuple(pt3) @classmethod def from_points(cls, pts: List[Tuple[int, int]]): """ :param pts: return: """ if len(pts) == 3: return Trigon(*pts) if len(pts) == 6: it = iter(pts) return Trigon(*zip(it, it)) def __str__(self): return "<< {}, {}, {} >>".format(self.pt1, self.pt2, self.pt3) def __contains__(self, point: Union[Tuple[int, int]]) -> bool: if type(point) is not Vuple: point = Vuple(point) return self.area() == sum( map(methodcaller("area"), self.inner_triangles(point)) ) def inner_triangles(self, point): """Triangle funk that returns the three triangles w/ a point The point (p) is connected to each point of a triangle. with points, a, b, and c. The three triangles are t1=(a, b, p), t2=(a, c, p), and t3 = (b, c, p). :param point: point to connect to Triangle Vertices :type point: tuple or Vuple :returns: t1, t2, t3-> Three triangles """ t1 = Trigon(point, self.pt2, self.pt3) t2 = Trigon(self.pt1, point, self.pt3) t3 = Trigon(self.pt1, self.pt2, point) return t1, t2, t3 def is_perimeter_point(self, point: Tuple[int, int]) -> bool: """ :param point: """ if type(point) is not Vuple: point = Vuple(point) return any( tri_area == 0 for tri_area in map(methodcaller("area"), self.inner_triangles(point)) ) def points(self): """ """ return self.pt1, self.pt2, self.pt3 def contains_origin(self) -> bool: """True if the origin (0,0) lies within the Triangle""" return (0, 0) in self def area(self) -> float: """ """ return abs(truediv(Vuple.cross(self.pt1 - self.pt2, self.pt3 - self.pt2), 2)) @staticmethod def area_from_points(pt1, pt2, pt3): """ :param pt1: :param pt2: :param pt3: """ return abs(truediv(Vuple.cross(pt1 - pt2, pt3 - pt2), 2)) class Vuple(tuple): """VUPLE == Vector+Tuple""" def __new__(cls: Type[Any], *args) -> Any: """ :param args: :return: """ return super(Vuple, cls).__new__(cls, tuple(*args)) def __gt__(self, other: Any) -> bool: return Vuple.mag_sqrd(self) > Vuple.mag_sqrd(other) def __eq__(self, other: Union[Tuple[float, float], Tuple[int, int], Any]) -> bool: return all(a == b for a, b in zip(self, other)) def __add__(self, k: Union[int, Any]) -> Any: """ .. docstring::python >>> va = Vuple((8, 13)) >>> vb = Vuple((26, 7)) >>> va + vb (34, 20) """ if type(k) is int or type(k) is float: return Vuple((k + el for el in self)) elif type(k) is Vuple and len(self) == len(k): return Vuple(map(add, self, k)) raise ValueError("huh idk") def __iadd__(self, k): return self.__add__(k) def __sub__(self, k: Any) -> Any: return Vuple(map(sub, self, k)) def __isub__(self, k): return self.__sub__(k) def __mul__(self, k: Union[int, Any]) -> Union[int, Any]: """Multiply by a scalar for each element or cross product if also iterable of same length :param k: scalar ot other iterable to do the cross producting with :return: A Vuple or a sum as a cost product .. docstring::python >>> a = Vuple((1, 2, 3)) >>> a * a 14 >>> a = Vuple((1, 1, 1)) >>> a * 4 (4, 4, 4) """ if type(k) is int or type(k) is float: return self._mul_scalar(k) elif type(k) is Vuple: if len(k) != len(self): raise ValueError("Sizes do not match!") return Vuple.dot(self, k) def __imul__(self, k: int) -> Any: return self.__mul__(k) def _mul_scalar(self, k: int) -> Any: """ :param k: """ return Vuple((k * el for el in self)) def __truediv__(self, k: Union[int, float]) -> Any: if type(k) is int or type(k) is float: return self._truediv_scalar(k) def _truediv_scalar(self, k: Union[int, float]) -> Any: """ :param k: """ return Vuple((el / k for el in self)) def __itruediv__(self, k: int) -> Any: return self.__truediv__(k) def __floordiv__(self, k): if type(k) is int or type(k) is float: return self._floordiv_scalar_int(k) def __ifloordiv__(self, k): return self.__floordiv__(k) def _floordiv_scalar_int(self, k): """ :param k: """ return Vuple((el // k for el in self)) def normalize(self) -> Any: """Normalizes the Vuple ST self.magnitude == 1 :return: Unit Vuple """ return Vuple.unit_vuple(self) @staticmethod def unit_vuple(voop: Any) -> Any: """ :param voop: """ return Vuple(voop) / Vuple.mag(voop) def get_mag_sqrd(self): """ """ return Vuple.mag_sqrd(self) @staticmethod def mag_sqrd(voop: Union[Tuple[int, int], Any]) -> int: """ :param voop: """ return sum(el * el for el in voop) def get_mag(self) -> float: """ """ return Vuple.mag(self) @staticmethod def mag(voop: Union[Tuple[int, int], Any]) -> float: """ :param voop: .. docstring::python >>> v = Vuple((3, 4, 5)) """ return sqrt(Vuple.mag_sqrd(voop)) @staticmethod def dot(a: Any, b: Any) -> Union[int, float]: """ :param a: :param b: """ return sum(va * vb for va, vb in zip(a, b)) @staticmethod def cross(v1: Any, v2: Any) -> int: """Cross product of two 2d vectors :param v1: first vector :param v2: second vector :returns: cross product of v1 and v2 """ if len(v1) == 2 and len(v2) == 2: return (v1[0] * v2[1]) - (v1[1] * v2[0]) else: raise ValueError("cross product gt 2d not implemented") @staticmethod def angle(v1: Any, v2: Any, radians: bool = False) -> float: """ :param v1: :param v2: :param radians: (Default value = False) """ # return acos(Vuple.dproduct(v1, v2)/(Vuple.mag(v1)*Vuple.mag(v2))) q = 1 if radians else 180 / pi return q * acos(Vuple.dot(Vuple.unit_vuple(v1), Vuple.unit_vuple(v2))) def is_disjoint(self, them): """ :param them: """ return disjoint(self, them) def product(self) -> int: """Multiplies all elements in the Vuple :return: .. doctest:: pythonm >>> v = Vuple((1, 2, 3, 4)) >>> v.product() 24 >>> v = Vuple((100, 1, -1, 2)) >>> v.product() -200 >>> v = Vuple((100, -1, -1, 2)) >>> v.product() 200 """ return iter_product(self) if __name__ == "__main__": import doctest doctest.testmod() PK!”°Uú6 6 pupy/maths.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any from typing import Iterator from typing import List from typing import Optional from typing import Set from typing import Tuple from typing import Type from typing import Union def partitions_gen( numero: int, min_p: int = ..., max_p: Optional[int] = ... ) -> Any: ... def rfactorial(n: int) -> int: ... def radians_2_degrees(rads: float) -> float: ... def degrees_2_radians(degs: float) -> float: ... def power_mod(number: int, exponent: int, mod: int) -> int: ... def divisors_gen(n: int) -> Iterator[int]: ... def gcd_it(a: int, b: int) -> int: ... def gcd_r(a: int, b: int) -> int: ... def reverse(n: int) -> int: ... def fib_r(n: int) -> int: ... def expo(d: int, n: int) -> int: ... def pytriple_gen(max_c: int) -> Iterator[Tuple[int, int, int]]: ... def n_permutations_with_replacements( it: Union[ Tuple[int, int, int, int], Tuple[int, int, int, int, int, int, int], Tuple[int, int, int, int, int], Tuple[int, int, int, int, int, int], ] ) -> int: ... def disjoint( a: Union[List[int], List[Union[str, int]]], b: Union[List[int], List[Union[str, int]]], ) -> bool: ... def set_cmp( a: Union[Set[int], List[int]], b: Union[Set[int], List[int]] ) -> Tuple[Set[int], Set[int], Set[int]]: ... def n_choose_r(n: Any, r: Any): ... class Trigon: pt1: Any = ... pt2: Any = ... pt3: Any = ... def __init__( self, pt1: Union[Tuple[int, int]], pt2: Union[Tuple[int, int]], pt3: Union[Tuple[int, int]], ) -> None: ... @classmethod def from_points(cls: Any, pts: List[Tuple[int, int]]) -> Any: ... def __contains__(self, point: Union[Tuple[int, int]]) -> bool: ... def inner_triangles(self, point: Any): ... def is_perimeter_point(self, point: Tuple[int, int]) -> bool: ... def points(self): ... def contains_origin(self) -> bool: ... def area(self) -> float: ... @staticmethod def area_from_points(pt1: Any, pt2: Any, pt3: Any): ... class Vuple(tuple): def __new__(cls: Type[Any], *args: Any) -> Any: ... def __gt__(self, other: Any) -> bool: ... def __eq__( self, other: Union[Tuple[float, float], Tuple[int, int], Any] ) -> bool: ... def __add__(self, k: Union[int, Any]) -> Any: ... def __iadd__(self, k: Any): ... def __sub__(self, k: Any) -> Any: ... def __isub__(self, k: Any): ... def __mul__(self, k: Union[int, Any]) -> Union[int, Any]: ... def __imul__(self, k: int) -> Any: ... def __truediv__(self, k: Union[int, float]) -> Any: ... def __itruediv__(self, k: int) -> Any: ... def __floordiv__(self, k: Any): ... def __ifloordiv__(self, k: Any): ... def normalize(self) -> Any: ... @staticmethod def unit_vuple(voop: Any) -> Any: ... def get_mag_sqrd(self): ... @staticmethod def mag_sqrd(voop: Union[Tuple[int, int], Any]) -> int: ... def get_mag(self) -> float: ... @staticmethod def mag(voop: Union[Tuple[int, int], Any]) -> float: ... @staticmethod def dot(a: Any, b: Any) -> Union[int, float]: ... @staticmethod def cross(v1: Any, v2: Any) -> int: ... @staticmethod def angle(v1: Any, v2: Any, radians: bool = ...) -> float: ... def is_disjoint(self, them: Any): ... def product(self) -> int: ... PK!Ëo  pupy/savings_n_loads.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from codecs import getwriter from io import open from itertools import count from os import path from os import utime from typing import Union from pupy._typing import JASM from pupy.decorations import mkdirs try: from ujson import dump from ujson import load from ujson import loads except: from json import dump from json import load from json import loads try: from toml import dumps as toml_dumps from toml import load as toml_load from toml import loads as toml_loads except ModuleNotFoundError: pass try: from msgpack import pack from msgpack import unpack except ModuleNotFoundError: pass try: from ruamel.yaml import YAML _yaml_saver = YAML() _yaml_loader = YAML(typ="safe") except ModuleNotFoundError: pass def safepath(path_str: str) -> str: """Checks if a file/dir path is save/unused; returns an unused path. :param path_str: file or dir path :return: A file/dir path that does not exist and contains the given path """ if path.exists(path_str): f_bn, f_ext = path.splitext(path_str) for n in count(1): safe_save_path = f_bn + "_({}).".format(str(n)) + f_ext if not path.exists(safe_save_path): return safe_save_path return path_str def lbytes(filepath: str) -> None: """Read bytes from file path :param filepath: filepath as as string to read bites from :return: some bytes... """ with open(filepath, "rb") as file: return file.read() @mkdirs def sstring(filepath: str, string: str) -> None: """Writes a string to filepath :param filepath: Filepath save location :param string: File as a string to be saved :return: None? what do you want? confirmation? .. note:: Decorated w/ @mkdirs Function is decorated with @mkdirs decorator; @mkdirs creates parent directories for the given filepath if they do not already exist. """ with open(filepath, "wb") as file: file.write(string.encode("utf-8")) def savings(filepath: str, string: str) -> None: """Alias for sstring""" return sstring(filepath, string) def sstr(filepath: str, string: str) -> None: """Alias for sstring""" return sstring(filepath, string) def lstring(filepath: str) -> str: """(lstring) Read and return the file-contents as a string given a filepath :param filepath: Path to a file to read :return: Content of the file read as a string """ try: with open(filepath, "r", encoding="utf-8") as f: return f.read() except UnicodeDecodeError: with open(filepath, "r", encoding="latin2") as f: return f.read() def lstr(filepath: str) -> str: """Alias for lstring""" return lstring(filepath) @mkdirs def sjson(filepath: str, data: JASM, min: bool = False) -> None: """Save json-serial-ize-able data to a specific filepath. :param filepath: destination filepath :param data: json cereal-izable dictionary/list/thing :param min: Bool flag -- minify the json file :return: None """ if type(data) == dict and any(type(val) == bytes for val in data.values()): data = {k: str(v, encoding="utf-8") for k, v in data.items()} with open(filepath, "wb") as jsonfile: if min: dump(data, getwriter("utf-8")(jsonfile), ensure_ascii=False) else: dump( data, getwriter("utf-8")(jsonfile), indent=4, sort_keys=True, ensure_ascii=False, ) def save_jasm(filepath: str, data: JASM, min: bool = False) -> None: """Alias for sjson (which stands for 'save-json')""" return sjson(filepath, data, min) def sjasm(filepath: str, data: JASM, min: bool = False) -> None: """Alias for sjson (which stands for 'save-json')""" return sjson(filepath, data, min) def ljson(filepath: str) -> JASM: """Load a json file given a filepath and return the file-data :param filepath: path to the jasm file you want to load :return: Loaded file contents """ try: with open(filepath) as infile: return load(infile) except UnicodeDecodeError as e: return loads(lstring(filepath)) def load_jasm(filepath: str) -> JASM: """Alias for ljson (which stands for 'load-json')""" return ljson(filepath) def ljasm(filepath: str) -> JASM: """Alias for ljson (which stands for 'load-json')""" return ljson(filepath) @mkdirs def touch(filepath: str) -> None: """Touches a file just like touch on the command line :param filepath: filepath to 'touch' in a unix-y sense :return: None """ with open(filepath, "a"): utime(filepath, None) def shebang(filepath: str) -> Union[None, str]: """returns the shebang path given a filepath or None if it does not exist. :param filepath: path to a file w/ a shebange line :return: shebang line or None .. doctest::python >>> from inspect import getabsfile >>> from pupy.savings_n_loads import sstr >>> script = 'ashellscript.sh' >>> sstr(script, '#!/bin/bash\\necho "howdy"\\n') >>> shebang(script) '#!/bin/bash' >>> from os import remove >>> remove(script) """ with open(filepath, "r") as f: first = f.readline().strip("\n") return first if first[:2] == "#!" else None def stoml(filepath: str, data: JASM) -> None: try: filepath = filepath if "." in filepath else "{}.toml".format(filepath) sstring(filepath, toml_dumps(data)) return filepath except NameError: raise EnvironmentError("'pip install toml' if you wanna use this!") def ltoml(filepath: str) -> JASM: try: with open(filepath) as f: return toml_load(f) except NameError: raise EnvironmentError("'pip install toml' if you wanna use this!") except UnicodeDecodeError as e: return toml_loads(lstring(filepath)) def spak(filepath: str, data: JASM) -> None: try: filepath = filepath if "." in filepath else "{}.pak".format(filepath) with open(filepath, "wb") as outfile: pack(data, outfile) return filepath except NameError: raise EnvironmentError("'pip install msgpack' if you wanna use this!") def lpak(filepath: str, bytes: bool = False) -> JASM: try: with open(filepath, "rb") as data_file: return unpack(data_file, raw=bytes) except NameError: raise EnvironmentError("'pip install msgpack' if you wanna use this!") def syaml(filepath: str, data: JASM) -> None: try: filepath = filepath if "." in filepath else "{}.yml".format(filepath) with open(filepath, "w") as data_file: _yaml_saver.dump(data, data_file) return filepath except NameError: raise EnvironmentError("'pip install ruamel.yaml' if you wanna use this!") def lyaml(filepath: str) -> JASM: try: with open(filepath) as data_file: return _yaml_loader.load(data_file) except NameError: raise EnvironmentError("'pip install ruamel.yaml' if you wanna use this!") PK!;­û“hhpupy/savings_n_loads.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Union from pupy._typing import JASM def safepath(path_str: str) -> str: ... def lbytes(filepath: str) -> None: ... def sstring(filepath: str, string: str) -> None: ... def savings(filepath: str, string: str) -> None: ... def sstr(filepath: str, string: str) -> None: ... def lstring(filepath: str) -> str: ... def lstr(filepath: str) -> str: ... def sjson(filepath: str, data: JASM, min: bool = ...) -> None: ... def save_jasm(filepath: str, data: JASM, min: bool = ...) -> None: ... def sjasm(filepath: str, data: JASM, min: bool = ...) -> None: ... def ljson(filepath: str) -> JASM: ... def load_jasm(filepath: str) -> JASM: ... def ljasm(filepath: str) -> JASM: ... def touch(filepath: str) -> None: ... def shebang(filepath: str) -> Union[None, str]: ... def stoml(filepath: str, data: JASM) -> None: ... def ltoml(filepath: str) -> JASM: ... def spak(filepath: str, data: JASM) -> None: ... def lpak(filepath: str, bytes: bool = ...) -> JASM: ... def syaml(filepath: str, data: JASM) -> None: ... def lyaml(filepath: str) -> JASM: ... PK![@š¯íípupy/sodoku.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from itertools import chain from typing import Dict from typing import List from typing import Optional from typing import Set from typing import Tuple from pupy.decorations import cash_it class SodokuError(ValueError): """simple Sodoku error""" def __init__(self, message: str, row: None = None, col: None = None) -> None: self.message = message self.row, self.col = row, col super(SodokuError, self).__init__(message, row, col) class Sodoku(object): """Sodoku [ 0, 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] """ def __init__(self, board: str) -> None: self.is_solved = False self.board = board.replace(".", "0") def solve(self) -> None: """Solve soduku :return: Nuthing... but the thing is solved in the obj """ if 17 > sum(1 for n in self.board if n != "0"): raise SodokuError("not enough info") full_set = "123456789" d = { i: ("".join(c for c in full_set) if self.board[i] == "0" else self.board[i]) for i in range(81) } d = Sodoku.update_dictionary(d) tf, d = Sodoku.reduce_dictionary(d) if not tf: raise SodokuError("check_unsolvable") a = [d[ind] for ind in range(81)] self.board = "".join(a) self.is_solved = True def euler_096_three_digit_number(self): """ Returns: """ if not self.is_solved: self.solve() return int(self.board[0:3]) @staticmethod def first_unknown(d: Dict[int, str]) -> Optional[int]: """ Args: d: Returns: """ for i in range(81): if len(d[i]) > 1: return i @staticmethod def unsolvable(rcbd: Dict[str, List[int]]) -> bool: """ Args: rcbd: Returns: """ return any(len(v) == 0 for v in rcbd.values()) @staticmethod def check_unsolvable(d: Dict[int, str]) -> Dict[int, str]: """ Args: d: Returns: """ nd = {k: v for k, v in d.items()} for rcb in range(9): box = { str(n): [ ind for ind in Sodoku.ibox(*divmod(rcb, 3)) if str(n) in d[ind] ] for n in range(1, 10) } row = { str(n): [ind for ind in Sodoku.irow(rcb) if str(n) in d[ind]] for n in range(1, 10) } col = { str(n): [ ind for ind in Sodoku.icolumn(rcb) if str(n) in d[ind] or str(n) == d[ind] ] for n in range(1, 10) } if ( Sodoku.unsolvable(box) or Sodoku.unsolvable(row) or Sodoku.unsolvable(col) ): raise SodokuError("UNSOLVABLE") return nd @staticmethod def update_dictionary(d: Dict[int, str]) -> Dict[int, str]: """ Args: d: Returns: """ nd = {k: v for k, v in d.items()} for i in range(81): if len(nd[i]) == 1: for nay in Sodoku.neighbors(i): if len(nd[nay]) != 1 and nd[i] in nd[nay]: nd[nay] = nd[nay].replace(nd[i], "") return nd @staticmethod def reduce_dictionary(d: Dict[int, str]) -> Tuple[bool, Dict[int, str]]: """ Args: d: Returns: """ if all(len(v) == 1 for v in d.values()): return True, d try: d = Sodoku.check_unsolvable(d) except SodokuError: return False, d d = Sodoku.update_dictionary(d) if any(len(v) == 0 for k, v in d.items()): return False, d fz = Sodoku.first_unknown(d) if fz is None: if Sodoku.hasdup(d): return False, d return Sodoku.reduce_dictionary(d) for poss in d[fz]: nd = {k: v for k, v in d.items()} nd[fz] = str(poss) if not Sodoku.hasdup(nd): valid, ret = Sodoku.reduce_dictionary(nd) if valid: return valid, ret return False, d def __str__(self) -> str: header = " S O D O K U " top_border = "â•”â•â•â•â•â•â•â•╦â•â•â•â•â•â•â•╦â•â•â•â•â•â•â•â•—" mid_border = "â• â•â•â•â•â•â•â•╬â•â•â•â•â•â•â•╬â•â•â•â•â•â•â•â•£" bot_border = "╚â•â•â•â•â•â•â•â•©â•â•â•â•â•â•â•â•©â•â•â•â•â•â•â•â•" top_boxes = "\n".join( "â•‘ {} {} {} â•‘ {} {} {} â•‘ {} {} {} â•‘".format(*self.board[l * 9 : l * 9 + 9]) for l in range(0, 3) ) mid_boxes = "\n".join( "â•‘ {} {} {} â•‘ {} {} {} â•‘ {} {} {} â•‘".format(*self.board[l * 9 : l * 9 + 9]) for l in range(3, 6) ) bot_boxes = "\n".join( "â•‘ {} {} {} â•‘ {} {} {} â•‘ {} {} {} â•‘".format(*self.board[l * 9 : l * 9 + 9]) for l in range(6, 9) ) strings = [ header, top_border, top_boxes, mid_border, mid_boxes, mid_border, bot_boxes, bot_border, ] return "\n".join(strings) @staticmethod def hasdup(d: Dict[int, str]) -> bool: """ Args: d: Returns: """ for i in range(81): if len(d[i]) == 1: for n in Sodoku.neighbors(i): if d[n] == d[i]: return True return False def get_oneline_str(self) -> str: """ Returns: """ return self.board @staticmethod def neighbors(index: int, size: int = 9) -> Set[int]: """ Args: index: size: Returns: """ return { ni for ni in chain( Sodoku.irow(index // size), Sodoku.icolumn(index % size), Sodoku.box_box(index), ) } - {index} @staticmethod def irow(n: int, bsize: int = 9) -> Set[int]: """ Args: n: bsize: Returns: """ return {i for i in range(n * bsize, n * bsize + bsize)} @staticmethod def icolumn(n: int, bsize: int = 9) -> Set[int]: """ Args: n: bsize: Returns: """ return {i for i in range(n, bsize ** 2, bsize)} @staticmethod def ibox(box_r: int, box_c: int, bsize: int = 9) -> Set[int]: """ Args: box_r: box_c: bsize: Returns: """ return { i * bsize + j for i in range((box_r * 3), (box_r * 3) + 3) for j in range((box_c * 3), (box_c * 3) + 3) } @staticmethod @cash_it def box_box(index: int) -> Set[int]: """ Args: index: bsize: Returns: """ for box_r in range(3): for box_c in range(3): box = Sodoku.ibox(box_r, box_c) if index in box: return box PK!x㿺ºpupy/sodoku.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any from typing import Dict from typing import List from typing import Optional from typing import Set from typing import Tuple class SodokuError(ValueError): message: Any = ... def __init__(self, message: str, row: None = ..., col: None = ...) -> None: ... class Sodoku: is_solved: bool = ... board: Any = ... def __init__(self, board: str) -> None: ... def solve(self) -> None: ... def euler_096_three_digit_number(self): ... @staticmethod def first_unknown(d: Dict[int, str]) -> Optional[int]: ... @staticmethod def unsolvable(rcbd: Dict[str, List[int]]) -> bool: ... @staticmethod def check_unsolvable(d: Dict[int, str]) -> Dict[int, str]: ... @staticmethod def update_dictionary(d: Dict[int, str]) -> Dict[int, str]: ... @staticmethod def reduce_dictionary(d: Dict[int, str]) -> Tuple[bool, Dict[int, str]]: ... @staticmethod def hasdup(d: Dict[int, str]) -> bool: ... def get_oneline_str(self) -> str: ... @staticmethod def neighbors(index: int, size: int = ...) -> Set[int]: ... @staticmethod def irow(n: int, bsize: int = ...) -> Set[int]: ... @staticmethod def icolumn(n: int, bsize: int = ...) -> Set[int]: ... @staticmethod def ibox(box_r: int, box_c: int, bsize: int = ...) -> Set[int]: ... @staticmethod def box_box(index: int) -> Set[int]: ... PK!€‚–ÛÞÞpupy/string_cheese.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python """ String Methods """ from binascii import hexlify from os import urandom from re import compile from re import sub from string import printable def bytes2str(bites: bytes, encoding: str = "utf-8") -> str: """Convert bytes to a string :param bites: bytes :type bites: bytes :param encoding: encoding of the string (default is utf-8) :type encoding: str :return: converted bytes :rtype: str .. doctest:: python >>> a = b'abcdefg' >>> type(a) >>> bytes2str(a) 'abcdefg' >>> type(bytes2str(a)) """ return bites.decode(encoding) def binary_string(number: int) -> str: """Number to binary string :param number: some number (an integer) to turn into a binary string :return: Some string which is the binary string :rtype: str .. doctest:: python >>> binary_string(200) '11001000' >>> binary_string(10) '1010' """ return bin(number)[2:] def string_score(strang: str) -> int: """Sum of letter values where a==1 and z == 26 :param strang: string to be scored :type strang: str :returns: -> score of the string :rtype: int .. doctest:: python >>> string_score('me') 18 >>> string_score('poooood') 95 >>> string_score('gregory') 95 """ return sum((ord(character) - 96 for character in strang.lower())) def is_palindrome(string: str) -> bool: """True a string is a palindrome; False if string is not a palindrome. :param string: .. doctest::python >>> is_palindrome("racecar") True >>> is_palindrome("greg") False """ return all( character == string[-index - 1] for index, character in enumerate(string) ) def strip_comments(string): filelines = string.splitlines(keepends=False) r = compile(r'(?:"(?:[^"\\]|\\.)*"|[^"#])*(#|$)') return "\n".join((line[: r.match(line).start(1)] for line in filelines)) def strip_ascii(s: str) -> str: """Remove all ascii characters from a string :param s: string with non-ascii characters :type s: string :return: string of only the non-ascii characters .. doctest:: >>> string_w_non_ascii_chars = 'Three fourths: ¾' >>> strip_ascii(string_w_non_ascii_chars) '¾' """ return "".join(sc for sc in (str(c) for c in s) if sc not in printable) def no_b(string: str) -> str: """Removes the b'' from binary strings and sub-strings that contain b'' :param string: A string surrounded by b'' or a sub-string with b'' :return: A string without binary b'' quotes surround it .. doctest:: >>> no_b("b'a_string'") 'a_string' """ return sub("b'([^']*)'", r"\1", string) def no_u(string: str) -> str: """Removes the u'' from unicode strings and sub-strings that contain u'' :param string: A string surrounded by u'' or a sub-string with u'' :return: A string without unicode u'' quotes surround it .. doctest:: python >>> a = "u'a_string'" >>> no_u(a) 'a_string' """ return sub("u'([^']*)'", r"\1", string) def rhex_str(length: int = 4) -> str: """Returns a random hex string :param length: length of random bytes to turn into hex (defaults to 4) :type length: int :return: random hexadecimal string :rtype: str .. doctest:: python >>> a = rhex_str() >>> isinstance(a, str) True >>> len(a) == 8 True """ return bytes2str(hexlify(urandom(length))) if __name__ == "__main__": from doctest import testmod testmod() PK!ªFÒÒpupy/string_cheese.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any def bytes2str(bites: bytes, encoding: str = ...) -> str: ... def binary_string(number: int) -> str: ... def string_score(strang: str) -> int: ... def is_palindrome(string: str) -> bool: ... def strip_comments(string: Any): ... def strip_ascii(s: str) -> str: ... def no_b(string: str) -> str: ... def no_u(string: str) -> str: ... def rhex_str(length: int = ...) -> str: ... PK!1q&«SS pupy/ui.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from math import ceil from shutil import get_terminal_size from sys import stdout from typing import Any from typing import Iterator from typing import List def yesno(question, default=True, tries=3): """Ask a yes/no question and return an answer as a boolean :param question: question to ask a user :param default: True=yes; False=no; None=no-default :param tries: number of tries before giving up :return: True/False depending on the response """ valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} default_prompts = {None: "[y/n]", True: "[Y/n]", False: "[y/N]"} if default is not None: valid[""] = True if default else False stdout.write("{} {} ".format(question, default_prompts[default])) try: return valid[input().lower()] except KeyError: stdout.write("Valid responses: [y]es/[n]o (case insensitive)\n") return yesno(question, default) def term_table( strings: List[str], row_wise: bool = False, filler: str = "~" ) -> Iterator[Any]: """ :param strings: :param row_wise: :param filler: :return: """ max_str_len = max(len(str) for str in strings) + 5 terminal_cols = get_terminal_size((80, 20)).columns n_cols = terminal_cols // max_str_len n_rows = int(ceil(len(strings) / n_cols)) spaces = " " * ((terminal_cols - (max_str_len * n_cols)) // n_cols) size_string = "{:<" + str(max_str_len) + "}" + spaces fmtstring = size_string * (n_cols - 1) + "{:<}" strings.extend(filler for _ in range(n_rows * n_cols - len(strings))) if row_wise: line_iter = zip(*(strings[i::n_cols] for i in range(n_cols))) else: line_iter = (strings[i::n_rows] for i in range(n_rows)) return (fmtstring.format(*row) for row in line_iter) PK!Š¿º=<< pupy/ui.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any from typing import Iterator from typing import List def yesno(question: Any, default: bool = ..., tries: int = ...): ... def term_table( strings: List[str], row_wise: bool = ..., filler: str = ... ) -> Iterator[Any]: ... PK!d€í&%&% pupy/utils.py# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python import contextlib import shutil import tempfile from datetime import datetime from os import environ from os import getcwd from os import listdir from os import makedirs from os import path from os import stat from platform import system from tempfile import mkdtemp from typing import List from typing import Optional from typing import Tuple from weakref import finalize import pupy._typing from pupy import lin from pupy import win _OS = system().lower() def fmt_bytes(num: pupy._typing.Flint) -> str: """ this function will convert bytes to MB.... GB... etc .. doctest:: python >>> fmt_bytes(100) '100.0 bytes' >>> fmt_bytes(1000) '1000.0 bytes' >>> fmt_bytes(10000) '9.8 KB' >>> fmt_bytes(100000) '97.7 KB' >>> fmt_bytes(1000000) '976.6 KB' >>> fmt_bytes(10000000) '9.5 MB' >>> fmt_bytes(100000000) '95.4 MB' >>> fmt_bytes(1000000000) '953.7 MB' >>> fmt_bytes(10000000000) '9.3 GB' >>> fmt_bytes(100000000000) '93.1 GB' >>> fmt_bytes(1000000000000) '931.3 GB' >>> fmt_bytes(10000000000000) '9.1 TB' >>> fmt_bytes(100000000000000) '90.9 TB' """ for x in ["bytes", "KB", "MB", "GB", "TB"]: if num < 1024.0: return "%3.1f %s" % (num, x) num /= 1024.0 def fmt_file_size(filepath: str) -> str: """ this function will return the file size """ if path.isfile(filepath): file_info = stat(filepath) return fmt_bytes(file_info.st_size) def fmt_seconds(t1: float, t2: Optional[float] = None) -> str: """Formats time string Formats t1 if t2 is None as a string; Calculates the time and formats the time t2-t1 if t2 is not None. :param t1: time 1/initial in seconds :type t1: double :param t2: time 2 (Default value = None) :type t2: None or double :returns: formated string of the t1 - t2 or t1 :rtype: str """ if t2 is not None: return fmt_seconds((t2 - t1)) elif t1 == 0.0: return "0 sec" elif t1 >= 1: return "%.3f sec" % t1 elif 1 > t1 >= 0.001: return "%.3f ms" % ((10 ** 3) * t1) elif 0.001 > t1 >= 0.000001: return "%.3f μs" % ((10 ** 6) * t1) elif 0.000001 > t1 >= 0.000000001: return "%.3f ns" % ((10 ** 9) * t1) else: return fmt_seconds((t2 - t1)) def path2name(path_str: str) -> str: """Get the parent-directory for a file or directory path as a string :param path_str: :return: The parent directory as a string .. doctest:: python >>> from os import getcwd >>> path2name(getcwd()) in getcwd() True """ return path.split(path.abspath(path_str))[-1] def parent_dirpath(fdpath: str) -> str: """ :param fdpath: file/dir-path as as string :return: .. doctest:: python >>> from os import path >>> parent_dirpath(path.abspath(__file__)) in path.abspath(__file__) True """ return path.split(fdpath)[0] def timestamp(ts: Optional[float] = None) -> str: """Time stamp string w/ format yyyymmdd-HHMMSS :return: timestamp string .. doctest:: python >>> from datetime import datetime >>> stamps = ['20190225-161151', '20190225-081151'] >>> timestamp(1551111111.111111) in stamps True >>> datetime.now().strftime("%Y%m%d-%H%M%S") == timestamp() True """ if ts is None: return datetime.now().strftime("%Y%m%d-%H%M%S") elif isinstance(ts, float): return datetime.fromtimestamp(ts).strftime("%Y%m%d-%H%M%S") elif isinstance(ts, datetime): return ts.strftime("%Y%m%d-%H%M%S") def ls(dirpath=".", abs=False): if abs: return [path.join(dirpath, item) for item in listdir(dirpath)] return listdir(dirpath) def ls_files(dirpath, abs=False): files = (file for file in ls(dirpath, abs=True) if path.isfile(file)) if not abs: return list(map(lambda el: el.replace(dirpath, "."), files)) return list(files) def ls_dirs(dirpath: str = ".", abs: bool = False): dirs = (dir for dir in ls(dirpath, abs=True) if path.isdir(dir)) if not abs: return list(map(lambda el: el.replace(dirpath, "."), dirs)) return list(dirs) def ls_files_dirs(dirpath: str = '.', abs=False) -> Tuple[List[str], List[str]]: return ls_files(dirpath, abs=abs), ls_dirs(dirpath, abs=abs) def link_dir(link, target): _link = win.link_dir if "win" in _OS else lin.link_dir return _link(link, target) def link_dirs(link_target_tuples): _link = win.link_dirs if "win" in _OS else lin.link_dirs return _link(link_target_tuples) def link_file(link, target): _link = win.link_file if "win" in _OS else lin.link_file makedirs(parent_dirpath(link), exist_ok=True) return _link(link, target) def link_files(link_target_tuples): _link = win.link_files if "win" in _OS else lin.link_files link_target_tuples = list(link_target_tuples) for link, target in link_target_tuples: makedirs(parent_dirpath(link), exist_ok=True) _link(link_target_tuples) for link, target in link_target_tuples: makedirs(parent_dirpath(link), exist_ok=True) def unlink_dir(link_path: str): _unlink = win.unlink_dir if "win" in _OS else lin.unlink_dir return _unlink(link_path) def unlink_dirs(link_paths): try: _unlink = win.unlink_dirs if "win" in _OS else lin.unlink_dirs return _unlink(link_paths) except TypeError: pass def unlink_file(link): _unlink = win.unlink_file if "win" in _OS else lin.unlink_file return _unlink(link) def unlink_files(links): try: _unlink = win.unlink_files if "win" in _OS else lin.unlink_files return _unlink(links) except TypeError: pass def sync(src, dest): """Update (rsync/robocopy) a local test directory from raid :param dest: path to local tdir :param src: path to remote tdir :return: subprocess return code for rsync/robocopy """ _sync = win.robocopy if "win" in _OS else lin.rsync return _sync(src=src, dest=dest) def environ_dict(): return {k: environ[k] for k in environ} class LinkedTmpDir(object): """ make a temp dir and have links.""" def __init__(self, suffix=None, prefix=None, dir=None, mkdirs=None, lndirs=None, lnfiles=None, link_targets=None ): self.dirpath = mkdtemp(suffix, prefix, dir) self.dirname = path.split(self.dirpath)[-1] file_targets = [] dir_targets = [] if link_targets is None: link_targets = [] for target in link_targets: if path.isfile(target): file_targets.append(target) elif path.isdir(target): dir_targets.append(target) _rel_file_links = list(map(path2name, file_targets)) _rel_dir_links = list(map(path2name, dir_targets)) try: assert len(set(_rel_file_links)) == len(_rel_file_links) except AssertionError as e: raise ValueError("Duplicate filenames present") try: assert len(set(_rel_dir_links)) == len(_rel_dir_links) except AssertionError as e: raise ValueError("Duplicate dirnames present") _lnk_path = lambda _lnk: path.join(self.dirpath, _lnk) self.file_links = list(map(_lnk_path, _rel_file_links)) self.dir_links = list(map(_lnk_path, _rel_dir_links)) link_files(zip(self.file_links, file_targets)) link_dirs(zip(self.dir_links, dir_targets)) self._finalizer = finalize( self, self._cleanup, self.dirpath, warn_message="Implicitly cleaning up {!r}".format(self), ) @classmethod def _cleanup(cls, name, warn_message): pass def __repr__(self): return "<{} {!r}>".format(self.__class__.__name__, self.dirpath) def __enter__(self): return self.dirpath def __exit__(self, exc, value, tb): self.cleanup() def cleanup(self): unlink_dirs(self.dir_links) unlink_files(self.file_links) if self._finalizer.detach(): pass @contextlib.contextmanager def linked_tmp_dir(suffix=None, prefix=None, dir=None, mkdirs=None, lndirs=None, lnfiles=None): temp_dir = tempfile.mkdtemp(suffix=suffix, prefix=prefix, dir=dir) try: for dirpath_route in mkdirs: _abs_dirpath = path.join(temp_dir, dirpath_route if isinstance(dirpath_route, str) else path.join(*dirpath_route)) makedirs(_abs_dirpath, exist_ok=True) except TypeError as e: pass try: lnfiles = ((path.join(temp_dir, _rel_link), target) for _rel_link, target in lnfiles) link_files(lnfiles) except TypeError as e: pass try: lndirs = ((path.join(temp_dir, _rel_link), target) for _rel_link, target in lndirs) link_dirs(lndirs) except TypeError as e: pass try: yield temp_dir finally: pass unlink_files(lnfiles) unlink_dirs(lndirs) shutil.rmtree(temp_dir) PK!¸@allpupy/utils.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any from typing import List from typing import Optional from typing import Tuple from pupy._typing import Flint def fmt_bytes(num: Flint) -> str: ... def fmt_file_size(filepath: str) -> str: ... def fmt_seconds(t1: float, t2: Optional[float] = ...) -> str: ... def path2name(path_str: str) -> str: ... def parent_dirpath(fdpath: str) -> str: ... def timestamp(ts: Optional[float] = ...) -> str: ... def ls(dirpath: str = ..., abs: bool = ...): ... def ls_files(dirpath: Any, abs: bool = ...): ... def ls_dirs(dirpath: str = ..., abs: bool = ...) -> Any: ... def ls_files_dirs(dirpath: str) -> Tuple[List[str], List[str]]: ... def link_dir(link: Any, target: Any): ... def link_dirs(link_target_tuples: Any): ... def link_file(link: Any, target: Any): ... def link_files(link_target_tuples: Any): ... def unlink_dir(link_path: str) -> Any: ... def unlink_dirs(link_paths: Any): ... def unlink_file(link: Any): ... def unlink_files(links: Any): ... def sync(src: Any, dest: Any): ... def environ_dict(): ... def linked_tmp_dir(suffix: Any, prefix: Any, dir: Any, mkdirs: Any, lndirs: Any, lnfiles: Any): ... class LinkedTmpDir: dirpath: Any = ... dirname: Any = ... file_links: Any = ... dir_links: Any = ... def __init__( self, suffix: Optional[Any] = ..., prefix: Optional[Any] = ..., dir: Optional[Any] = ..., link_targets: Optional[Any] = ..., ) -> None: ... def __enter__(self): ... def __exit__(self, exc: Any, value: Any, tb: Any) -> None: ... def cleanup(self) -> None: ... PK!ºBÖ- pupy/win.py# -*- coding: utf-8 -*- from subprocess import PIPE from subprocess import run def robocopy(src, dest): """Robocopy for sheldon :param dest: path to local tdir :param src: path to remote (raid) tdir :return: subprocess return code from robocopy Robocopy return codes: 0. No files were copied. No failure was encountered. No files were mismatched. The files already exist in the destination directory; therefore, the copy operation was skipped. 1. All files were copied successfully. 2. There are some additional files in the destination directory that are not present in the source directory. No files were copied. 3. Some files were copied. Additional files were present. No failure was encountered. 5. Some files were copied. Some files were mismatched. No failure was encountered. 6. Additional files and mismatched files exist. No files were copied and no failures were encountered. This means that the files already exist in the destination directory. 7. Files were copied, a file mismatch was present, and additional files were present. 8. Several files did not copy. """ subproc = run(["robocopy", src, dest, "/mir", "/mt"], stdout=PIPE, stderr=PIPE) if subproc.returncode not in (0, 1, 2, 3): print(subproc) return subproc.returncode def link_dir(link, target): run(args=["mklink", "/D", link, target], stdout=PIPE, stderr=PIPE, shell=True) def link_dirs(link_target_tuples): link_args = [] for link_target_tuple in link_target_tuples: link_args.extend(["mklink", "/D", *link_target_tuple, "&&"]) run(args=link_args[:-1], stdout=PIPE, stderr=PIPE, shell=True) def link_file(link, target): run(args=["mklink", link, target], stdout=PIPE, stderr=PIPE, shell=True) def link_files(link_target_tuples): link_args = [] for link_target_tuple in link_target_tuples: link_args.extend(["mklink", *link_target_tuple, "&&"]) run(args=link_args[:-1], stdout=PIPE, stderr=PIPE, shell=True) def unlink_dir(link): run(args=["RD", link], stdout=PIPE, stderr=PIPE, shell=True) def unlink_dirs(links): cmd_args = " && ".join("RD {}".format(link) for link in links).split(" ") run(args=cmd_args, stdout=PIPE, stderr=PIPE, shell=True) def unlink_file(link): run(args=["Del", link], stdout=PIPE, stderr=PIPE, shell=True) def unlink_files(links): cmd_args = " && ".join("Del {}".format(link) for link in links).split(" ") run(args=cmd_args, stdout=PIPE, stderr=PIPE, shell=True) PK!¶&ýÎòò pupy/win.pyi# -*- coding: utf-8 -*- # ~ Jesse K. Rubin ~ Pretty Useful Python from typing import Any def robocopy(src: Any, dest: Any): ... def link_dir(link: Any, target: Any) -> None: ... def link_dirs(link_target_tuples: Any) -> None: ... def link_file(link: Any, target: Any) -> None: ... def link_files(link_target_tuples: Any) -> None: ... def unlink_dir(link: Any) -> None: ... def unlink_dirs(links: Any) -> None: ... def unlink_file(link: Any) -> None: ... def unlink_files(links: Any) -> None: ... PK!ðWÎ/$$pupy-2.13.2.dist-info/LICENSEBSD 2-Clause License Copyright (c) 2018, jesse k rubin All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PK!H|n-æWYpupy-2.13.2.dist-info/WHEEL ÊA € н§ð#Z;/í"¢ÖŸ bFF]xûzëwÜÀK;´<ÂÑç͆¦*mTíÖ»0¸Ì*Ri.´4ÅûœÑVm0[ºþÙ—ûH, JÜÍPK!H]izîFpupy-2.13.2.dist-info/METADATA•ËN1†÷}Šã–8 tÔIš`$n$Â@u]àP«Ó‹íÔdÞÞŽ0‰,»ûú_rþ.±ÑŠâ }PÖp`tBj¡‘ƒ‹®#ÿõ’2²‰Z ß%×cÛvc®k?¬!OVcá„Lí×ú¹^½×d¡öhB–ó-™Åó>1üòq§ÌY+P Õ\8²—èÞjò‚ßQy Åúï‡û)£Õ5\MK:¦£L`”ÜÐylDê¨0Ý?Îaµ™ÃÌ9oðпÓÌÁ¾¨¬½•^h­Œ„…02¦_öùÓ¢žXnžV™27Oo³wÙŠüPK!HébƒÕ pupy-2.13.2.dist-info/RECORDuÕG’£Xà}Ÿzðf1 œðH'´!pÂáÑéGU¥VMO_à‹?óeæëç~ÿ+ж˜‚àg¿c"8ñ÷UÂUÍO™ˆÖɲc–‡Ý„ò~S‹¾± [9a#$ö£ÿ€š°h¿Af|lІás¥S‚Û(eLMÈikB&ç²GÎMib>æ>ãø§3í}ÑfoÌ©,;1æÅ±æò8XEâ{¬—² +ëk5Z…ÛîK*ånß(ñÅ,é0]û渚ƒø®‘çCñÈwsÒ“sï“q4¢ÊHñ|êÕ¸5Ž2€"Ô‡ÝÑÓ²dR3nB? ÛÖ) m¬‡&š£ñ‰[LFÚ©gì›Q|!Išþ±ÝåãCjɵB;'Áëîe©­²h6òì&k¡ÏJ€£È§Ñ„® ú¡hÒ÷šìéj3ÔIfQ¾ÔYVg«KØE¼ªäVW 7k<%žk ‰¯ÿ{%#[ ro¾œåZ0ÂE†í÷æmCPGZGo§g0ïš0„@Z\o‰ŒÂëMa:¶µuP,*—xç ŒOˆ¬Ù4¢µí}ý#tÆ0ìÝøÕ¢ÁHu2 è(Ê;gZ@aÖºŸhîº#Ì6–ÕÔX¡ @`úÃHÒ¸Âéùèã[žCI³WdKÓG‚ÖÌiâ‚üÒù·"2ÑLm›Ùâ*&zÖV€@ øÿ¬W.Ó½¨‚ë‚"­ë‘xR&8’vqÜPèèRÛÆNJÝ£†’‚À)òúuCZdï“f “0Dg^aï~j” ÜaxHJÅx&¨îP`¿8=¢Ó©M™Q5ˆ÷¥>l —çÙƒ6¨»0yϵœê8#YBï6º‚˜à·Ð®â®-.b£è!½«d¯Ãñ»÷«[Òé2 Y Ý CÂdÏõÏŸfÖp›Æ*«·tÄbt~]ÒUó[®‡Ï÷l®\·5å²3ØIðuŸË‘°Åñ^ÇDvZ~¨¸cœ™IÃä˜WœÆ<úNÒ¦Â.¦É>z¡vÓölƒ–‡Ìåf7ÕÅå©ç½ÇâÓ™†g}Aœ§éø~‘|4àHÛH| Âà­ëçú›À*épÀ u°ÍàÚ,ø @)èÚ+ܲ‘êFUVÅ`»üN:Þ&k_=Úo‚ÇÏñêÑ<¼²Íï©^‘KÝòUª7FG½Ào,ÀÆ–I7ãR_[{­ÚÀ‰¿¯éñ8ù‡óf]¦Ù®µ±¹ã$s¾ŠÎ/£–Õƒ¹Ä AËs¤à¯SQ¿ÏÑFñ"/rWØjM1„g¢Õ†)/W˜©«Z0çèa³uE4Cß•W–âÑc(Ø~؇¤ ç‡0U„`”TBªòé¾Uy•<ë!¾ìúméëA÷€ÔA5Z|×kS®²K†SFëz)‚µCœøß¥GÞWbõƒ¨#ˆÕ¯UDÓ–åÅ]ð#µ¥h …ni•¹ˆÕBÕB ¤#“pupy/maths.pyPK!”°Uú6 6 ¤#Öpupy/maths.pyiPK!Ëo  ¤…ãpupy/savings_n_loads.pyPK!;­û“hh¤Zpupy/savings_n_loads.pyiPK![@š¯íí¤øpupy/sodoku.pyPK!x㿺º¤$pupy/sodoku.pyiPK!€‚–ÛÞÞ¤ø)pupy/string_cheese.pyPK!ªFÒÒ¤ 9pupy/string_cheese.pyiPK!1q&«SS ¤;pupy/ui.pyPK!Š¿º=<< ¤ŠBpupy/ui.pyiPK!d€í&%&% ¤ïCpupy/utils.pyPK!¸@all¤@ipupy/utils.pyiPK!ºBÖ- ¤Øopupy/win.pyPK!¶&ýÎòò ¤!zpupy/win.pyiPK!ðWÎ/$$¤=|pupy-2.13.2.dist-info/LICENSEPK!H|n-æWY€œpupy-2.13.2.dist-info/WHEELPK!H]izîF€,‚pupy-2.13.2.dist-info/METADATAPK!HébƒÕ €Vƒpupy-2.13.2.dist-info/RECORDPK""Ze‰