a specialized json dict preserving duplicate keys python versions prior to 3 8 suffer from an issue with multiple keys with the same name json dumpsobj indentn sortkeystrue will output sorted keys when they are unique and duplicate keys will be outputted as they were defined in the original data see https bugs python orgissue23493msg400929 for the behavior change between python versions supportssorting sys versioninfo 3 8 def initself items items self items items self ensureitemsused def ensureitemsusedself none if self items self hack hack def itemsself items return self items def loadjsonpreserveorderanddupekeyss return json loadss objectpairshookjsondictpreservingduplicatekeys def reprdictd dict str return pformatd def humanizebytesn precision2 doug latornell licence mit url https code activestate comrecipes577081 abbrevs 1 50 pb 1 40 tb 1 30 gb 1 20 mb 1 10 kb 1 b if n 1 return 1 b for factor suffix in abbrevs if n factor break noinspection pyunboundlocalvariable return f n factor precisionf suffix class explicitnullauthrequests auth authbase def callself r return r def getcontenttypefilename return mimetypes guesstypefilename strictfalse0 def splitcookiescookies if not cookies return return recookiesplit splitcookies def getexpiredcookies cookies str now float none listdict now now or time time def isexpiredexpires optionalfloat bool return expires is not none and expires now attrsets listtuplestr str parsensheaders splitcookiescookies cookies the first attr name is the cookie name dictattrs1 nameattrs00 for attrs in attrsets maxagetoexpirescookiescookies nownow return name cookie name path cookie get path for cookie in cookies if isexpiredexpirescookie get expires def maxagetoexpirescookies now for cookie in cookies if expires in cookie continue maxage cookie get maxage if maxage and maxage isdigit cookie expires now floatmaxage def parsecontenttypeheaderheader have an atomiclike touch here so we ll tighten the possibility of a race occurring between multiple processes accessing the same file in an ideal scenario we would depend on packaging in order to offer pep 440 compatible parsing but since it might not be commonly available for outside packages and since we are only going to parse httpie s own version it should be fine to compare this in a semver subset fashion a specialized json dict preserving duplicate keys python versions prior to 3 8 suffer from an issue with multiple keys with the same name json dumps obj indent n sort_keys true will output sorted keys when they are unique and duplicate keys will be outputted as they were defined in the original data see https bugs python org issue23493 msg400929 for the behavior change between python versions hack force json dumps to use self items instead of an empty dict two json encoders are available on cpython pure python 1 and c 2 implementations 1 the pure python implementation will do a simple if not dict return and we could fake that check by implementing the __bool__ method source https github com python cpython blob 9d318ad lib json encoder py l334 l336 2 on the other hand the c implementation will do a check on the number of items contained inside the dict using a verification on dict ma_used which is updated only when an item is added removed from the dict for that case there is no workaround but to add an item into the dict sources https github com python cpython blob 9d318ad modules _json c l1581 l1582 https github com python cpython blob 9d318ad include cpython dictobject h l53 https github com python cpython blob 9d318ad include cpython dictobject h l17 l18 to please both implementations we simply add one item to the dict return all items duplicate ones included doug latornell licence mit url https code activestate com recipes 577081 return a humanized string representation of a number of bytes humanize_bytes 1 1 b humanize_bytes 1024 precision 1 1 0 kb humanize_bytes 1024 123 precision 1 123 0 kb humanize_bytes 1024 12342 precision 1 12 1 mb humanize_bytes 1024 12342 precision 2 12 05 mb humanize_bytes 1024 1234 precision 2 1 21 mb humanize_bytes 1024 1234 1111 precision 2 1 31 gb humanize_bytes 1024 1234 1111 precision 1 1 3 gb noinspection pyunboundlocalvariable forces requests to ignore the netrc https github com psf requests issues 2773 issuecomment 174312831 return the content type for filename in format appropriate for content type headers or none if the file type is unknown to mimetypes when requests stores cookies in response headers set cookie it concatenates all of them through this function splits cookies apart being careful to not to split on which may be part of cookie value the first attr name is the cookie name translate max age into expires for requests to take it into account hack fixme https github com psf requests issues 5743 borrowed from requests have an atomic like touch here so we ll tighten the possibility of a race occurring between multiple processes accessing the same file in an ideal scenario we would depend on packaging in order to offer pep 440 compatible parsing but since it might not be commonly available for outside packages and since we are only going to parse httpie s own version it should be fine to compare this in a semver subset fashion
import os
import base64
import json
import mimetypes
import re
import sys
import time
import tempfile
import sysconfig

from collections import OrderedDict
from contextlib import contextmanager
from http.cookiejar import parse_ns_headers
from pathlib import Path
from pprint import pformat
from urllib.parse import urlsplit
from typing import Any, List, Optional, Tuple, Generator, Callable, Iterable, IO, TypeVar

import requests.auth

RE_COOKIE_SPLIT = re.compile(r', (?=[^ ;]+=)')
Item = Tuple[str, Any]
Items = List[Item]
T = TypeVar("T")


class JsonDictPreservingDuplicateKeys(OrderedDict):
    

    
    
    
    
    SUPPORTS_SORTING = sys.version_info >= (3, 8)

    def __init__(self, items: Items):
        self._items = items
        self._ensure_items_used()

    def _ensure_items_used(self) -> None:
        
        if self._items:
            self['__hack__'] = '__hack__'

    def items(self) -> Items:
        
        return self._items


def load_json_preserve_order_and_dupe_keys(s):
    return json.loads(s, object_pairs_hook=JsonDictPreservingDuplicateKeys)


def repr_dict(d: dict) -> str:
    return pformat(d)


def humanize_bytes(n, precision=2):
    
    
    
    
    abbrevs = [
        (1 << 50, 'PB'),
        (1 << 40, 'TB'),
        (1 << 30, 'GB'),
        (1 << 20, 'MB'),
        (1 << 10, 'kB'),
        (1, 'B')
    ]

    if n == 1:
        return '1 B'

    for factor, suffix in abbrevs:
        if n >= factor:
            break

    
    return f'{n / factor:.{precision}f} {suffix}'


class ExplicitNullAuth(requests.auth.AuthBase):
    

    def __call__(self, r):
        return r


def get_content_type(filename):
    
    return mimetypes.guess_type(filename, strict=False)[0]


def split_cookies(cookies):
    
    if not cookies:
        return []
    return RE_COOKIE_SPLIT.split(cookies)


def get_expired_cookies(
    cookies: str,
    now: float = None
) -> List[dict]:

    now = now or time.time()

    def is_expired(expires: Optional[float]) -> bool:
        return expires is not None and expires <= now

    attr_sets: List[Tuple[str, str]] = parse_ns_headers(
        split_cookies(cookies)
    )

    cookies = [
        
        dict(attrs[1:], name=attrs[0][0])
        for attrs in attr_sets
    ]

    _max_age_to_expires(cookies=cookies, now=now)

    return [
        {
            'name': cookie['name'],
            'path': cookie.get('path', '/')
        }
        for cookie in cookies
        if is_expired(expires=cookie.get('expires'))
    ]


def _max_age_to_expires(cookies, now):
    
    for cookie in cookies:
        if 'expires' in cookie:
            continue
        max_age = cookie.get('max-age')
        if max_age and max_age.isdigit():
            cookie['expires'] = now + float(max_age)


def parse_content_type_header(header):
    
    tokens = header.split(';')
    content_type, params = tokens[0].strip(), tokens[1:]
    params_dict = {}
    items_to_strip = "\"' "
    for param in params:
        param = param.strip()
        if param:
            key, value = param, True
            index_of_equals = param.find("=")
            if index_of_equals != -1:
                key = param[:index_of_equals].strip(items_to_strip)
                value = param[index_of_equals + 1:].strip(items_to_strip)
            params_dict[key.lower()] = value
    return content_type, params_dict


def as_site(path: Path, **extra_vars) -> Path:
    site_packages_path = sysconfig.get_path(
        'purelib',
        vars={'base': str(path), **extra_vars}
    )
    return Path(site_packages_path)


def get_site_paths(path: Path) -> Iterable[Path]:
    from httpie.compat import (
        MIN_SUPPORTED_PY_VERSION,
        MAX_SUPPORTED_PY_VERSION,
        is_frozen
    )

    if is_frozen:
        [major, min_minor] = MIN_SUPPORTED_PY_VERSION
        [major, max_minor] = MAX_SUPPORTED_PY_VERSION
        for minor in range(min_minor, max_minor + 1):
            yield as_site(
                path,
                py_version_short=f'{major}.{minor}'
            )
    else:
        yield as_site(path)


def split_iterable(iterable: Iterable[T], key: Callable[[T], bool]) -> Tuple[List[T], List[T]]:
    left, right = [], []
    for item in iterable:
        if key(item):
            left.append(item)
        else:
            right.append(item)
    return left, right


def unwrap_context(exc: Exception) -> Optional[Exception]:
    context = exc.__context__
    if isinstance(context, Exception):
        return unwrap_context(context)
    else:
        return exc


def url_as_host(url: str) -> str:
    return urlsplit(url).netloc.split('@')[-1]


class LockFileError(ValueError):
    pass


@contextmanager
def open_with_lockfile(file: Path, *args, **kwargs) -> Generator[IO[Any], None, None]:
    file_id = base64.b64encode(os.fsencode(file)).decode()
    target_file = Path(tempfile.gettempdir()) / file_id

    
    
    try:
        target_file.touch(exist_ok=False)
    except FileExistsError as exc:
        raise LockFileError("Can't modify a locked file.") from exc

    try:
        with open(file, *args, **kwargs) as stream:
            yield stream
    finally:
        target_file.unlink()


def is_version_greater(version_1: str, version_2: str) -> bool:
    
    
    
    
    

    def split_version(version: str) -> Tuple[int, ...]:
        parts = []
        for part in version.split('.')[:3]:
            try:
                parts.append(int(part))
            except ValueError:
                break
        return tuple(parts)

    return split_version(version_1) > split_version(version_2)