예제 #1
0
async def _async_sys(proc,
                     *args,
                     write: STRBYTES = None,
                     **kwargs) -> Tuple[str, str]:
    """Small async wrapper function for :func:`.call_sys_async` to simplify calling ``git``, including detecting git errors"""
    stderr_raise = kwargs.pop('stderr_raise', False)
    strip = kwargs.pop('strip', True)
    out, err = await call_sys_async(proc, *args, write=write, **kwargs)
    out, err = stringify(out, if_none=''), stringify(err, if_none='')
    if strip: out, err = out.strip(), err.strip()

    if len(err) > 1 and stderr_raise:
        raise SysCallError(f'Git returned an error: "{err}"')

    return out, err
예제 #2
0
 def get(self,
         key: Union[bytes, str],
         default: Any = None,
         fail: bool = False) -> Any:
     key = str(stringify(key))
     res = self.mcache.get(key)
     if empty(res):
         if fail: raise CacheNotFound(f'Cache key "{key}" was not found.')
         return default
     return pickle.loads(res) if self.use_pickle else res
예제 #3
0
 async def commit(self,
                  message: Optional[Union[str, bool]],
                  *args,
                  repo: Optional[str] = None) -> str:
     """
     Calls ``git commit`` with the arguments ``-m "message" [args]``, where each positional argument passed after the message
     is passed as a command line argument.
     
     To disable prepending ``-m`` to the arguments, pass ``None`` as the ``message`` and only your specified ``args`` will be
     passed along to ``git commit``.
     
     You may also set ``message`` to ``False`` which will call ``git commit --allow-empty-message [args]`` instead of
     prepending ``-m``.
     
     
     **Basic Usage**
     
     Standard commit with a message, plus the argument '-a'::
     
         >>> g = Git()
         >>> g.commit("added example.txt", "-a")
     
     Pass ``None`` as the message to disable prepending ``-m`` to the git arguments. For example, the following call commits
     the current staged changes, while re-using the commit message/author info/timestamp from the previous
     commit ``9e27d7233ac5bc59bc37c0572a401068fbd5e6be``::
     
         >>> g.commit(None, "-C", "9e27d7233ac5bc59bc37c0572a401068fbd5e6be")
     
     Pass ``False`` as the message for an easy way to call ``git commit --allow-empty-message``, plus any additional
     arguments you might pass::
     
         >>> g.commit(False, '-a')
     
     
     :param str|bool message: The git commit message to commit with. Alternatively ``None`` to remove the default ``-m``, or
                              ``False`` as a shortcut for ``--allow-empty-message`` instead of ``-m``.
     :param args: Additional CLI arguments to pass to ``git commit``
     :param str repo: (as a kwarg only!) An absolute path to a Git repository to run ``git commit`` within. By default, this is
                      ``None`` which results in the repo passed in the constructor ( :attr:`.repo` ) being used.
     :return str stdout: The string text printed to stdout by ``git commit`` while running the command.
     """
     if message is None:
         return await self.git("commit", *args, repo=repo)
     if message is False:
         return await self.git("commit",
                               "--allow-empty-message",
                               *args,
                               repo=repo)
     return await self.git("commit",
                           "-m",
                           stringify(message),
                           *args,
                           repo=repo)
예제 #4
0
    def remove(self, *key: Union[bytes, str]) -> bool:
        removed = 0
        for k in key:
            k = str(stringify(k))
            try:
                self.get(k, fail=True)
                self.mcache.delete(k)
                removed += 1
            except CacheNotFound:
                pass

        return removed == len(key)
예제 #5
0
def generate_http_request(
    url="/", host=None, method="GET", user_agent=settings.DEFAULT_USER_AGENT, extra_data: Union[STRBYTES, List[str]] = None,
    body: STRBYTES = None, **kwargs
) -> bytes:
    method, url = stringify(method), stringify(url)
    http_ver = stringify(kwargs.get('http_ver', '1.0'))
    data = f"{method.upper()} {url} HTTP/{http_ver}\n"
    if host is not None: data += f"Host: {stringify(host)}\n"
    if user_agent is not None: data += f"User-Agent: {stringify(user_agent)}\n"
    data = byteify(data)
    
    if extra_data is not None:
        if isinstance(extra_data, list):
            extra_data = [byteify(x) for x in extra_data]
            data += b"\n".join(extra_data)
        else:
            data += byteify(extra_data)
        if not data.endswith(b"\n"): data += b"\n"
    
    if body is not None:
        data += byteify(body)
    if not data.endswith(b"\n"): data += b"\n"
    if not data.endswith(b"\n\n"): data += b"\n"
    return data
예제 #6
0
 def load_keyfile(cls, filename: Text, password: Text = None):
     """
     Returns an instance of :class:`.KeyManager` using a public/private key loaded from disk, instead of
     from string/bytes key data.
     
     Example::
     
         >>> km = KeyManager.load_keyfile('id_rsa')
         >>> d = km.encrypt('hello world')
         >>> km.decrypt(d)
         b'hello world'
     
     
     :param str|bytes filename: The file location where the key is stored
     :param str|bytes password: If the key is encrypted, specify the password to decrypt it
     :raises InvalidFormat: When the key could not be identified, is not supported, or is corrupted
     :raises FileNotFoundError: The given ``filename`` couldn't be found.
     :return KeyManager cls: An instance of :class:`.KeyManager` (or child class) initialised with the key
     """
     filename = stringify(filename)
     with open(filename, 'r') as fp:
         data = fp.read()
     return cls(key=data, password=password)
예제 #7
0
    async def git(self,
                  *args,
                  repo: str = None,
                  strip=True,
                  stderr=False) -> Union[str, Tuple[str, str]]:
        """
        Wrapper async method for calling ``git`` executable command against a repo - casts args to :class:`str`.
        
        This is intended for use internally by :class:`._AsyncGit` methods, but is under a non-private method name to allow you to call
        Git commands semi-directly if the class methods are getting in the way.
        
        Example::
        
            >>> Git().git('commit', '--amend', '-a', '-m', 'lorem ipsum dolor')
        
        
        :param AnyStr|int|float|object|Any args: Positional command line arguments to pass to ``git``. Most formats are fine, as long
                                                 as they can be casted into a :class:`str`
        
        :param str|None repo: The repository to run the command against. When ``None``, uses the value of :attr:`.repo`
        
        :param bool strip: (Default: ``True``) Whether to run :meth:`str.strip` on the string outputs of the command
        
        :param bool stderr: (Default: ``False``) When ``True``, includes the stderr output in the response like so: ``(stdout, stderr)``
                            When ``False``, simply returns ``stdout`` directly as a :class:`str`
          
        :return str stdout: (when ``stderr=False``) The text outputted to stdout by the command
        :return Tuple[str,str] stdout_err: (when ``stderr=True``) A :class:`tuple` containing ``(stdout: str, stderr: str)``
        """
        repo = self._repo(repo)
        out, err = await _async_sys("git",
                                    *[stringify(a) for a in args],
                                    cwd=repo,
                                    strip=strip)

        return (out, err) if stderr else out
예제 #8
0
def convert_datetime(d,
                     if_empty=None,
                     fail_empty=False,
                     **kwargs) -> Optional[datetime]:
    """
    Convert the object ``d`` into a :class:`datetime.datetime` object.
    
    If ``d`` is a string or bytes, then it will be parsed using :func:`dateutil.parser.parse`
    
    If ``d`` is an int/float/Decimal, then it will be assumed to be a unix epoch timestamp.
    
    **Examples**::
        
        >>> convert_datetime("2019-01-01T00:00:00Z")          # ISO date/time
        datetime.datetime(2019, 1, 1, 0, 0, tzinfo=tzutc())
        
        >>> convert_datetime("01/JAN/2019 00:00:00.0000")     # Human date/time with month name
        datetime.datetime(2019, 1, 1, 0, 0, tzinfo=tzutc())
        
        >>> convert_datetime(1546300800)                      # Unix timestamp as integer
        datetime.datetime(2019, 1, 1, 0, 0, tzinfo=tzutc())
        
        >>> convert_datetime(1546300800000)                   # Unix timestamp (milliseconds) as integer
        datetime.datetime(2019, 1, 1, 0, 0, tzinfo=tzutc())
    
    :param d: Object to convert into a datetime
    :param if_empty: If ``d`` is empty / None, return this value
    :param bool fail_empty: (Def: ``False``) If this is True, then if ``d`` is empty, raises :class:`AttributeError`
    
    :key datetime.tzinfo tzinfo: (Default: :class:`dateutil.tz.tzutc`) If no timezone was detected by the parser,
                                 use this timezone. Set this to ``None`` to disable forcing timezone-aware dates.
    
    :raises AttributeError: When ``d`` is empty and ``fail_empty`` is set to True.
    :raises dateutil.parser.ParserError: When ``d`` could not be parsed into a date.
    :return datetime converted: The converted :class:`datetime.datetime` object.
    """
    from dateutil.tz import tzutc
    _tzinfo = kwargs.pop('tzinfo', tzutc())
    if isinstance(d, datetime):
        if d.tzinfo is None and _tzinfo is not None:
            d = d.replace(tzinfo=_tzinfo)
        return d

    # For datetime.date objects, we first convert them into a string, then we can parse them into a datetime + set tzinfo
    if isinstance(d, date):
        d = str(d)

    d = stringify(d) if isinstance(d, bytes) else d

    if isinstance(d, (int, float)):
        return convert_unixtime_datetime(d)

    if isinstance(d, str):
        from dateutil.parser import parse, ParserError
        try:
            t = parse(d)
            if t.tzinfo is None and _tzinfo is not None:
                t = t.replace(tzinfo=_tzinfo)
            return t
        except (ParserError, ValueError) as e:
            log.info(
                "Failed to parse string. Attempting to parse as unix time")
            try:
                t = convert_unixtime_datetime(d)
                return t
            except (BaseException, Exception, ParserError) as _err:
                log.warning(
                    "Failed to parse unix time. Re-raising original parser error. Unixtime error was: %s %s",
                    type(_err), str(_err))
                raise e
        except ImportError as e:
            msg = "ERROR: Could not import 'parse' from 'dateutil.parser'. Please " \
                  f"make sure 'python-dateutil' is installed. Exception: {type(e)} - {str(e)}"

            log.exception(msg)
            raise ImportError(msg)
    if empty(d):
        if fail_empty:
            raise AttributeError(
                "Error converting datetime. Parameter 'd' was empty!")
        return if_empty

    try:
        log.debug(
            "Passed object is not a supported type. Object type: %s || object repr: %s",
            type(d), repr(d))
        log.debug("Calling convert_datetime with object casted to string: %s",
                  str(d))
        _d = convert_datetime(str(d), fail_empty=True)
        d = _d
    except Exception as e:
        log.info(
            "Converted passed object with str() to try and parse string version, but failed."
        )
        log.info("Exception thrown from convert_datetime(str(d)) was: %s %s",
                 type(e), str(e))
        d = None  # By setting d to None, it will trigger the ValueError code below.

    if not isinstance(d, datetime):
        raise ValueError(
            'Timestamp must be either a datetime object, or an ISO8601 string...'
        )
    return d
예제 #9
0
def _clean_strs(ob, **kwargs):
    try:
        return stringify(ob)
    except Exception:
        return str(repr(ob))
예제 #10
0
 def set(self,
         key: Union[bytes, str],
         value: Any,
         timeout: Optional[int] = DEFAULT_CACHE_TIMEOUT):
     v = pickle.dumps(value) if self.use_pickle else value
     return self.mcache.set(str(stringify(key)), v, timeout)
예제 #11
0
    def load_key(cls,
                 data: Text,
                 password: bytes = None) -> Tuple[combined_key_types, str]:
        """
        Load a private/public key from a string or bytes ``data`` containing the key in some format, such as PEM
        or OpenSSH. Use :py:meth:`.load_keyfile` to load a key from a file + auto-instantiate KeyManager with it.
        
        **Example:**
        
            >>> key = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG3v6guHpI/j7AIl3F/EWMSReX8fH8MOSq1bC3ZuEjjC'
            >>> KeyManager.load_key(key)
            (<cryptography.hazmat.backends.openssl.ed25519._Ed25519PublicKey object at 0x7fa118289ba8>, 'public')
        
        
        :param str|bytes data: The public/private key data, as a string or bytes
        :param str|bytes password: If your key data is encrypted, pass the password in this argument to decrypt it.
        :raises InvalidFormat: When the key could not be identified, is not supported, or is corrupted
        :return tuple key_data: A tuple containing an instance of the key, and a string ``public`` or ``private``
                                Example: ``(<_Ed25519PublicKey object>, 'public')``
        """
        data = stringify(data)
        password = None if not password else byteify(password)
        try:
            if data[0:4] == 'ssh-' or data[0:6] == 'ecdsa-':
                return load_ssh_public_key(byteify(data),
                                           backend=default_backend()), 'public'
            if '-----BEGIN PUBLIC' in data:
                return load_pem_public_key(byteify(data),
                                           backend=default_backend()), 'public'
            if '-----BEGIN PRIVATE' in data:
                return load_pem_private_key(
                    byteify(data),
                    password=password,
                    backend=default_backend()), 'private'
        except binascii.Error as e:
            raise InvalidFormat(
                f'Error while decoding key - possibly corrupted base64 encoding. '
                f'Original exception: {type(e)} {str(e)}')
        except ValueError as e:
            raise InvalidFormat(
                f'Error while decoding key - key may be corrupted. '
                f'Original exception: {type(e)} {str(e)}')
        ##
        # If we couldn't identify the key as PEM or OpenSSH, then fallback to attempting to load it as DER / PKCS12
        ##
        data = byteify(data)

        try:  # Try loading the key as a DER key
            key, ktype = cls._load_der_key(data=data, password=password)
            return key, ktype
        except InvalidFormat:
            pass

        try:  # Try loading the key as PKCS12
            key, ktype, _, _ = cls._load_pkcs12_key(data=data,
                                                    password=password)
            return key, ktype
        except InvalidFormat:
            pass

        # Otherwise we have no idea what to do with this key. Time to give up.
        raise InvalidFormat('Unknown key format...')