flutils¶
flutils (flu-tills) is a collection of commonly used utility functions for Python projects.
Codecs¶
flutils contains additional codecs that, when registered, can be used to convert bytes to strings and strings to bytes.
b64¶
The
b64
codec will decode bytes to base64 string characters; and will encode strings containing base64 string characters into bytes. Base64 string characters can span multiple lines and may have whitespace indentation.New in version 0.4.
Raw UTF-8 Escape¶
The
raw_utf8_escape
codec will decode a byte string containing escaped UTF-8 hexadecimal into a string with the proper characters. Strings encoded with theraw_utf8_escape
codec will be of ascii bytes and have escaped UTF-8 hexadecimal used for non printable characters and each character with anord
value above127
New in version 0.4.
Registering Codecs¶
Using any of the above codecs requires registering them with Python by using the following function:
register_codecs
()[source]¶Register additional codecs.
New in version 0.4.
- Return type
Examples
>>> from flutils.codecs import register_codecs >>> register_codecs() >>> 'test©'.encode('raw_utf8_escape') b'test\\xc2\\xa9' >>> b'test\\xc2\\xa9'.decode('raw_utf8_escape') 'test©' >>> 'dGVzdA=='.encode('b64') b'test' >>> b'test'.decode('b64') 'dGVzdA=='
General¶
-
get_encoding
(name=None, default='UTF-8')[source]¶ Validate and return the given encoding codec name.
- Parameters
name (str) – The name of the encoding to validate. if empty or invalid then the value of the given
default
will be returned.default (str, optional) – If set, this encoding name will be returned if the given
name
is invalid. Defaults to:SYSTEM_ENCODING
. If set toNone
which will raise aLookupError
if the givenname
is not valid.
- Raises
LookupError – If the given
name
is not a valid encoding codec name and the givendefault
is set toNone
or an empty string.LookupError – If the given
default
is not a valid encoding codec name.
- Return type
- Returns
str – The encoding codec name.
Example
>>> from flutils.codecs import get_encoding >>> get_encoding() 'utf-8'
Commands¶
flutils offers the following utilities for running commands.
run
(command, stdout=None, stderr=None, columns=80, lines=24, force_dimensions=False, interactive=False, **kwargs)[source]¶Run the given command line command and return the command’s return code.
When the given
command
is executed, the command’s stdout and stderr outputs are captured in a pseudo terminal. The captured outputs are then added to this function’sstdout
andstderr
IO objects.This function will capture any ANSI escape codes in the output of the given command. This even includes ANSI colors.
- Parameters
command (str, List[str], Tuple[str]) – The command to execute.
stdout (
typing.IO
, optional) – An input/output stream that will hold the command’sstdout
. Defaults to:sys.stdout
; which will output the command’sstdout
to the terminal.stderr (
typing.IO
, optional) – An input/output stream that will hold the command’sstderr
. Defaults to:sys.stderr
; which will output the command’sstderr
to the terminal.columns (int, optional) – The number of character columns the pseudo terminal may use. If
force_dimensions
isFalse
, this will be the fallback columns value when the the current terminal’s column size cannot be found. Ifforce_dimensions
isTrue
, this will be actual character column value. Defaults to:80
.lines (int, optional) – The number of character lines the pseudo terminal may use. If
force_dimensions
isFalse
, this will be the fallback lines value when the the current terminal’s line size cannot be found. Ifforce_dimensions
isTrue
, this will be actual character lines value. Defaults to:24
.force_dimensions (bool, optional) – This controls how the given
columns
andlines
values are to be used. A value ofFalse
will use the givencolumns
andlines
as fallback values if the current terminal dimensions cannot be successfully queried. A value ofTrue
will resize the pseudo terminal using the givencolumns
andlines
values. Defaults to:False
.interactive (bool, optional) – A value of
True
will interactively run the givencommand
. Defaults to:False
.**kwargs – Any additional key-word-arguments used with
Popen
.stdout
andstderr
will not be used if given in**default_kwargs
. Defaults to:{}
.- Return type
- Returns
int – The return value from running the given
command
- Raises
RuntimeError – When using
interactive=True
and thebash
executable cannot be located.OSError – Any errors raised when trying to read the pseudo terminal.
Example
An example using
run
in code:from flutils.cmdutils import run from io import BytesIO import sys import os home = os.path.expanduser('~') with BytesIO() as stream: return_code = run( 'ls "%s"' % home, stdout=stream, stderr=stream ) text = stream.getvalue() text = text.decode(sys.getdefaultencoding()) if return_code == 0: print(text) else: print('Error: %s' % text)
prep_cmd
(cmd)[source]¶Convert a given command into a tuple for use by
subprocess.Popen
.
- Parameters
cmd (
Sequence
) – The command to be converted.This is for converting a command of type string or bytes to a tuple of strings for use by
subprocess.Popen
.Example
>>> from flutils.cmdutils import prep_cmd >>> prep_cmd('ls -Flap') ('ls', '-Flap')
- class
CompletedProcess
[source]¶A
NamedTuple
that holds a completed process’ information.
- class
RunCmd
(raise_error=True, output_encoding=None, **default_kwargs)[source]¶A simple callable that simplifies many calls to
subprocess.run
.
- Parameters
raise_error (bool, optional) – A value of
True
will raise aChildProcessError
if the process, exits with a non-zero return code. Default:True
output_encoding (str, optional) – If set, the returned
stdout
andstderr
will be converted to from bytes to a Python string using this givenencoding
. Defaults to:None
which will use the value fromlocale.getpreferredencoding
or, if not set, the value fromsys.getdefaultencoding
will be used. If the given encoding does NOT exist the default will be used.**default_kwargs – Any
subprocess.Popen
keyword argument.
default_kwargs
¶The
default_kwargs
passed into the constructor which may be passed on tosubprocess.run
.
- Type
__call__
(cmd, **kwargs)[source]¶Run the given command and return the result.
- Parameters
cmd (
Sequence
) – The command**kwargs – Any default_kwargs to pass to
subprocess.run
. These default_kwargs will override anydefault_kwargs
set in the constructor.- Raises
FileNotFoundError – If the given
cmd
cannot be found.ChildProcessError – If
raise_error=True
was set in this class’ constructor; and, the process (from running the givencmd
) returns a non-zero value.ValueError – If the given
**kwargs
has invalid arguments.Example
>>> from flutils.cmdutils import RunCmd >>> from subprocess import PIPE >>> import os >>> run_command = RunCmd(stdout=PIPE, stderr=PIPE) >>> result = run_command('ls -flap %s' % os.getcwd()) >>> result.return_code 0 >>> result.stdout ... >>> result = run_command('ls -flap %s' % os.path.expanduser('~'))
- Return type
Decorators¶
flutils offers the following decorators:
-
@
cached_property
[source]¶ A property decorator that is only computed once per instance and then replaces itself with an ordinary attribute.
Deleting the attribute resets the property.
Note
In Python 3.8 the
functools.cached_property
decorator was added. It is recommended to use the built-infunctools.cached_property
; provided you’re using Python >= 3.8.cached_property
remains for use with Python 3.6 and 3.7.Example
Code:
from flutils.decorators import cached_property class MyClass: def __init__(self): self.x = 5 @cached_property def y(self): return self.x + 1
Usage:
>>> obj = MyClass() >>> obj.y 6
New in version 0.2.0
This decorator is a derivative work of cached_property and is:
Copyright © 2015 Daniel Greenfeld; All Rights Reserved
Also this decorator is a derivative work of cached_property and is:
Modules¶
flutils offers the following module utility functions:
cherry_pick
(namespace)[source]¶Replace the calling cherry-pick-definition package module with a cherry-picking module.
Use this function when there is a need to cherry-pick modules. This means the loading and executing, of a module, will be postponed until an attribute is accessed.
Warning
For projects where startup time is critical, this function allows for potentially minimizing the cost of loading a module if it is never used. For projects where startup time is not essential, the use of this function is heavily discouraged due to error messages created during loading being postponed and thus occurring out of context.
Example
It is recommended to first build the root package (
__init__.py
) as a normally desired root package. (Make sure that no functions or classes are defined. If needed, define these in a submodule). For example (mymodule/__init__.py
):"""This is the mymodule docstring.""" from mymodule import mysubmoduleone import mymodule.mysubmoduletwo as two from mymodule.mysubmodulethree import afunction from mymodule.mysubmodulethree import anotherfunction as anotherfuc MYVAL = 123To use the
cherry_pick
function, the root package module (__init__.py
) must be converted to a cherry-pick-definition package module. This example is the result of rewriting the root package (above):"""This is the mymodule docstring.""" from flutils.moduleutils import cherry_pick MYVAL = 123 __attr_map__ = ( 'mymodule.mysubmoduleone', 'mymodule.mysubmoduletwo,two', 'mymodule.mysubmodulethree:afunction', 'mymodule.mysubmodulethree:anotherfunction,anotherfuc' ) __additional_attrs__ = dict( MYVAL=MYVAL ) cherry_pick(globals())As you can see, the imports were each rewritten to a foreign-name and placed in the
__attr_map__
tuple
.Then,
MYVAL
was put in the__additional_attrs__
dictionary. Use this dictionary to pass any values to cherry-picking module.And finally the
cherry_pick
function was called withglobals()
as the only argument.The result is the expected usage of
mymodule
:>> import mymodule >> mymodule.anotherfunc() foo barTo test if a cherry-picked module has been loaded, or not:
>> import sys >> sys.modules.get('mymodule.mysubmodulethree')If you get nothing back, it means the cherry-picked module has not been loaded.
Please be aware that there are some cases when all of the cherry-picked modules will be loaded automatically. Using any program that automatically inspects the cherry-picking module will cause the all of the cherry-picked modules to be loaded. Programs such as ipython and pycharm will do this.
lazy_import_module
(name, package=None)[source]¶Lazy import a python module.
- Parameters
name (
str
) – specifies what module to import in absolute or relative terms (e.g. eitherpkg.mod
or..mod
).package (
str
, optional) – Ifname
is specified in relative terms, then thepackage
argument must be set to the name of the package which is to act as the anchor for resolving the package name. Defaults toNone
.- Raises
ImportError – if the given
name
andpackage
can not be loaded.- Return type
The lazy imported module with the execution of it’s loader postponed until an attribute accessed.
Warning
For projects where startup time is critical, this function allows for potentially minimizing the cost of loading a module if it is never used. For projects where startup time is not essential then use of this function is heavily discouraged due to error messages created during loading being postponed and thus occurring out of context.
Examples
>>> from flutils.moduleutils import lazy_import_module >>> module = lazy_import_module('mymodule')Relative import:
>>> module = lazy_import_module('.mysubmodule', package='mymodule')
Named Tuples¶
flutils offers the following named-tuple utility functions:
to_namedtuple
(obj)[source]¶Convert particular objects into a namedtuple.
- Parameters
obj (
Union
[List
,Mapping
,NamedTuple
,SimpleNamespace
,Tuple
]) – The object to be converted (or have it’s contents converted) to aNamedTuple
.If the given type is of
list
ortuple
, each item will be recursively converted to aNamedTuple
provided the items can be converted. Items that cannot be converted will still exist in the returned object.If the given type is of
list
the return value will be a newlist
. This means the items are not changed in the givenobj
.If the given type is of
Mapping
(dict
), keys that can be proper identifiers will become attributes on the returnedNamedTuple
. Additionally, the attributes of the returnedNamedTuple
are sorted alphabetically.If the given type is of
OrderedDict
, the attributes of the returnedNamedTuple
keep the same order as the givenOrderedDict
keys.If the given type is of
SimpleNamespace
, The attributes are sorted alphabetically in the returnedNamedTuple
.Any identifier (key or attribute name) that starts with an underscore cannot be used as a
NamedTuple
attribute.All values are recursively converted. This means a dictionary that contains another dictionary, as one of it’s values, will be converted to a
NamedTuple
with the attribute’s value also converted to aNamedTuple
.
- Return type
A list with any of it’s values converted to a
NamedTuple
.A tuple with any of it’s values converted to a
NamedTuple
.Example
>>> from flutils.namedtupleutils import to_namedtuple >>> dic = {'a': 1, 'b': 2} >>> to_namedtuple(dic) NamedTuple(a=1, b=2)
Objects¶
flutils offers the following object utility functions:
has_any_attrs
(obj, *attrs)[source]¶Check if the given
obj
has ANY of the given*attrs
.
- Parameters
- Return type
Example
>>> from flutils.objutils import has_any_attrs >>> has_any_attrs(dict(),'get','keys','items','values','something') True
has_any_callables
(obj, *attrs)[source]¶Check if the given
obj
has ANY of the givenattrs
and are callable.
- Parameters
- Return type
Example
>>> from flutils.objutils import has_any_callables >>> has_any_callables(dict(),'get','keys','items','values','foo') True
has_attrs
(obj, *attrs)[source]¶Check if given
obj
has all the given*attrs
.
- Parameters
- Return type
Example
>>> from flutils.objutils import has_attrs >>> has_attrs(dict(),'get','keys','items','values') True
has_callables
(obj, *attrs)[source]¶Check if given
obj
has all the givenattrs
and are callable.
- Parameters
- Return type
Example
>>> from flutils.objutils import has_callables >>> has_callables(dict(),'get','keys','items','values') True
is_list_like
(obj)[source]¶Check that given
obj
acts like a list and is iterable.List-like objects are instances of:
List-like objects are NOT instances of:
etc…
- Parameters
obj (
Any
) – The object to check.- Return type
Examples
>>> from flutils.objutils import is_list_like >>> is_list_like([1, 2, 3]) True >>> is_list_like(reversed([1, 2, 4])) True >>> is_list_like('hello') False >>> is_list_like(sorted('hello')) True
is_subclass_of_any
(obj, *classes)[source]¶Check if the given
obj
is a subclass of any of the given*classes
.
- Parameters
- Return type
Example
>>> from flutils.objutils import is_subclass_of_any >>> from collections import ValuesView, KeysView, UserList >>> obj = dict(a=1, b=2) >>> is_subclass_of_any(obj.keys(),ValuesView,KeysView,UserList) True
Packages¶
flutils offers the following package utilities:
Version Numbers¶
In flutils a version number consists of two or three dot-separated numeric components, with an optional “pre-release” tag on the right-component. The pre-release tag consists of the letter ‘a’ (alpha) or ‘b’ (beta) followed by a number. If the numeric components of two version numbers are equal, then one with a pre-release tag will always be deemed earlier (lesser) than one without.
The following are valid version numbers:
0.4 0.4.1 0.5a1 0.5b3 0.5 0.9.6 1.0 1.0.4a3 1.0.4b1 1.0.4The following are examples of invalid version numbers:
1 2.7.2.2 1.3.a4 1.3pl1 1.3c4The following function is designed to work with version numbers formatted as described above:
bump_version
(version, position=2, pre_release=None)[source]¶Increase the version number from a version number string.
New in version 0.3
- Parameters
version (str) – The version number to be bumped.
position (int, optional) – The position (starting with zero) of the version number component to be increased. Defaults to:
2
pre_release (str, Optional) – A value of
a
oralpha
will create or increase an alpha version number. A value ofb
orbeta
will create or increase a beta version number.- Raises
ValueError – if the given
version
is an invalid version number.ValueError – if the given
position
does not exist.ValueError – if the given
prerelease
is not in:a, alpha, b, beta
ValueError – if trying to ‘major’ part, of a version number, to a pre-release version.
- Return type
The increased version number.
Examples
>>> from flutils.packages import bump_version >>> bump_version('1.2.2') '1.2.3' >>> bump_version('1.2.3', position=1) '1.3' >>> bump_version('1.3.4', position=0) '2.0' >>> bump_version('1.2.3', prerelease='a') '1.2.4a0' >>> bump_version('1.2.4a0', pre_release='a') '1.2.4a1' >>> bump_version('1.2.4a1', pre_release='b') '1.2.4b0' >>> bump_version('1.2.4a1') '1.2.4' >>> bump_version('1.2.4b0') '1.2.4' >>> bump_version('2.1.3', position=1, pre_release='a') '2.2a0' >>> bump_version('1.2b0', position=2) '1.2.1'
Paths¶
flutils offers the following path utility functions:
chmod
(path, mode_file=None, mode_dir=None, include_parent=False)[source]¶Change the mode of a path.
This function processes the given
path
withnormalize_path
.If the given
path
does NOT exist, nothing will be done.This function will NOT change the mode of:
symlinks (symlink targets that are files or directories will be changed)
sockets
fifo
block devices
char devices
- Parameters
path (
str
,bytes
orPath
) – The path of the file or directory to have it’s mode changed. This value can be a glob pattern.mode_file (
int
, optional) – The mode applied to the givenpath
that is a file or a symlink target that is a file. Defaults to0o600
.mode_dir (
int
, optional) – The mode applied to the givenpath
that is a directory or a symlink target that is a directory. Defaults to0o700
.include_parent (
bool
, optional) – A value ofTrue`
will chmod the parent directory of the givenpath
that contains a a glob pattern. Defaults toFalse
.- Return type
Examples
>>> from flutils.pathutils import chmod >>> chmod('~/tmp/flutils.tests.osutils.txt', 0o660)Supports a glob pattern. So to recursively change the mode of a directory just do:
>>> chmod('~/tmp/**', mode_file=0o644, mode_dir=0o770)To change the mode of a directory’s immediate contents:
>>> chmod('~/tmp/*')
chown
(path, user=None, group=None, include_parent=False)[source]¶Change ownership of a path.
This function processes the given
path
withnormalize_path
.If the given
path
does NOT exist, nothing will be done.
- Parameters
path (
str
,bytes
orPath
) – The path of the file or directory that will have it’s ownership changed. This value can be a glob pattern.user (
str
orint
, optional) – The “login name” used to set the owner ofpath
. A value of'-1'
will leave the owner unchanged. Defaults to the “login name” of the current user.group (
str
orint
, optional) – The group name used to set the group ofpath
. A value of'-1'
will leave the group unchanged. Defaults to the current user’s group.include_parent (
bool
, optional) – A value ofTrue
will chown the parent directory of the givenpath
that contains a glob pattern. Defaults toFalse
.- Raises
- Return type
Examples
>>> from flutils.pathutils import chown >>> chown('~/tmp/flutils.tests.osutils.txt')Supports a glob pattern. So to recursively change the ownership of a directory just do:
>>> chown('~/tmp/**')To change ownership of all the directory’s immediate contents:
>>> chown('~/tmp/*', user='foo', group='bar')
directory_present
(path, mode=None, user=None, group=None)[source]¶Ensure the state of the given
path
is present and a directory.This function processes the given
path
withnormalize_path
.If the given
path
does NOT exist, it will be created as a directory.If the parent paths of the given
path
do not exist, they will also be created with themode
,user
andgroup
.If the given
path
does exist as a directory, themode
,user
, and :group
will be applied.
- Parameters
mode (
int
, optional) – The mode applied to thepath
. Defaults to0o700
.user (
str
orint
, optional) – The “login name” used to set the owner of the givenpath
. A value of'-1'
will leave the owner unchanged. Defaults to the “login name” of the current user.group (
str
orint
, optional) – The group name used to set the group of the givenpath
. A value of'-1'
will leave the group unchanged. Defaults to the current user’s group.- Raises
ValueError – if the given
path
contains a glob pattern.ValueError – if the given
path
is not an absolute path.FileExistsError – if the given
path
exists and is not a directory.FileExistsError – if a parent of the given
path
exists and is not a directory.- Return type
PosixPath
orWindowsPath
depending on the system.Example
>>> from flutils.pathutils import directory_present >>> directory_present('~/tmp/test_path') PosixPath('/Users/len/tmp/test_path')
exists_as
(path)[source]¶Return a string describing the file type if it exists.
This function processes the given
path
withnormalize_path
.
- Parameters
path (
str
,bytes
orPath
) – The path to check for existence.- Return type
''
(empty string): if the givenpath
does NOT exist; or, is a broken symbolic link; or, other errors (such as permission errors) are propagated.
'directory'
: if the givenpath
points to a directory or is a symbolic link pointing to a directory.
'file'
: if the givenpath
points to a regular file or is a symbolic link pointing to a regular file.
'block device'
: if the givenpath
points to a block device or is a symbolic link pointing to a block device.
'char device'
: if the givenpath
points to a character device or is a symbolic link pointing to a character device.
'FIFO'
: if the givenpath
points to a FIFO or is a symbolic link pointing to a FIFO.
'socket'
: if the givenpath
points to a Unix socket or is a symbolic link pointing to a Unix socket.Example
>>> from flutils.pathutils import exists_as >>> exists_as('~/tmp') 'directory'
find_paths
(pattern)[source]¶Find all paths that match the given glob pattern.
This function pre-processes the given
pattern
withnormalize_path
.
- Parameters
pattern (
str
,bytes
orPath
) – The path to find; which may contain a glob pattern.- Return type
- Yields
Example
>>> from flutils.pathutils import find_paths >>> list(find_paths('~/tmp/*')) [PosixPath('/home/test_user/tmp/file_one'), PosixPath('/home/test_user/tmp/dir_one')]
get_os_group
(name=None)[source]¶Get an operating system group object.
- Parameters
name (
str
orint
, optional) – The “group name” orgid
. Defaults to the current users’s group.- Raises
- Return type
A tuple like object.
Example
>>> from flutils.pathutils import get_os_group >>> get_os_group('bar') grp.struct_group(gr_name='bar', gr_passwd='*', gr_gid=2001, gr_mem=['foo'])
get_os_user
(name=None)[source]¶Return an user object representing an operating system user.
- Parameters
name (
str
orint
, optional) – The “login name” oruid
. Defaults to the current user’s “login name”.- Raises
- Return type
A tuple like object.
Example
>>> from flutils.pathutils import get_os_user >>> get_os_user('foo') pwd.struct_passwd(pw_name='foo', pw_passwd='********', pw_uid=1001, pw_gid=2001, pw_gecos='Foo Bar', pw_dir='/home/foo', pw_shell='/usr/local/bin/bash')
normalize_path
(path)[source]¶Normalize a given path.
The given
path
will be normalized in the following process.
bytes
will be converted to astr
using the encoding given bygetfilesystemencoding()
.
PosixPath
andWindowsPath
will be converted to astr
using theas_posix()
method.An initial component of
~
will be replaced by that user’s home directory.Any environment variables will be expanded.
Non absolute paths will have the current working directory from
os.getcwd()
to change the current working directory before calling this function.Redundant separators and up-level references will be normalized, so that
A//B
,A/B/
,A/./B
andA/foo/../B
all becomeA/B
.
- Parameters
- Return type
PosixPath
orWindowsPath
depending on the system.Example
>>> from flutils.pathutils import normalize_path >>> normalize_path('~/tmp/foo/../bar') PosixPath('/home/test_user/tmp/bar')
path_absent
(path)[source]¶Ensure the given
path
does NOT exist.New in version 0.4.
If the given
path
does exist, it will be deleted.If the given
path
is a directory, this function will recursively delete all of the directory’s contents.This function processes the given
path
withnormalize_path
.Example
>>> from flutils.pathutils import path_absent >>> path_absent('~/tmp/test_path')
Setup¶
Custom setup.py
Commands¶
flutils offers the ability to quickly add additional
setup.py
custom commands to a Python project.
Requirements¶
Custom Setup Commands can be used in Python projects that are using setuptools with a
setup.cfg
configuration file.Setup Commands requires that the
setup.py
andsetup.cfg
files exist in the root directory of a typical Python project structure:my_project/ │ ├── docs/ │ ├── doc1.rst │ └── doc2.rst │ ├── my_project/ │ ├── __init__.py │ ├── module1.py │ └── module2.py │ ├── setup.cfg ├── setup.py └── tests/ ├── test1.py └── test2.pyTo use Custom Setup Commands, the
setup.cfg
file must have, at least, the following defined:[metadata] name == my_project
Custom Command Definitions¶
The actual command definitions can exist in
setup.cfg
and/orsetup_commands.cfg
. If thesetup_commands.cfg
file exists then the command definitions from in this file will be used; and, command definitions insetup.cfg
are ignored.The general idea is to have
setup_commands.cfg
ignored by version control, allowing developers customize command definitions to their specific needs. While command definitions needed for deployment, testing etc can be kept in version control.Each individual Custom-Setup-Command definition starts with a section header of:
[setup.command.<name-of-custom-setup-command>]Underneath the section header the following options can be used:
description = <text>
: A short description about the custom setup command. This is displayed withsetup.py --help-commands
command = <text>
: The command line command or commands to execute when thesetup.py
custom command is called.
commands = <text>
: The command line command or commands to execute when thesetup.py
custom command is called. If bothcommand
andcommands
exist, the value ofcommand
will be executed first.
name = <text>
: This option can override the<name-of-custom-setup-command>
set in the section header.The following string interpolation variables can be used on all but the
name
option.
{name}
: the name of the project as set in the[metadata]
sectionname
option.
{setup_dir}
: The full path to the directory that contains thesetup.py
file.
{home}
: The full path to the user’s home directory.
Example setup.cfg
¶
The following example of
setup.cfg
contains the definitions for thesetup.py lint
andsetup.py lint-all
commands:[metadata] name = my_project [setup.command.lint] description = Lint the {name} project files command = pylint --rcfile={setup_dir}/.pylintrc {setup_dir}/{name} [setup.command.lint_all] name = lint-all description = Lint the {name} project and test files commands = pylint --rcfile={setup_dir}/.pylintrc {setup_dir}/{name} pylint --rcfile={setup_dir}/.pylintrc {setup_dir}/tests
Implementation¶
The final step of using Custom Setup Commands is to prepare the commands and tell setuptools.
add_setup_cfg_commands
(setup_kwargs, setup_dir=None)[source]¶Add additional custom
setup.py
commands that are defined insetup.cfg
.
- Parameters
setup_kwargs (dict) – A dictionary holding the setuptools.setup keyword arguments. (see example below).
setup_dir (
str
orPath
, optional) – The root directory of the project. (e.g. the directory that contains thesetup.py
file). Defaults to:None
which will try to determine the directory using the call stack.- Return type
Example
Use in
setup.py
like the following:#!/usr/bin/env python import os from setuptools import setup from flutils.setuputils import add_setup_cfg_commands setup_kwargs = {} setup_dir = os.path.dirname(os.path.realpath(__file__)) add_setup_cfg_commands(setup_kwargs, setup_dir=setup_dir) setup(**setup_kwargs)
Strings¶
flutils offers the following string utility functions:
as_escaped_unicode_literal
(text)[source]¶Convert the given
text
into a string of escaped Unicode hexadecimal.
- Parameters
text (
str
) – The string to convert.- Return type
A string with each character of the given
text
converted into an escaped Python literal.Example
>>> from flutils.strutils import as_escaped_unicode_literal >>> t = '1.★ 🛑' >>> as_literal(t) '\\x31\\x2e\\u2605\\x20\\U0001f6d1'
as_escaped_utf8_literal
(text)[source]¶Convert the given
text
into a string of escaped UTF8 hexadecimal.
- Parameters
text (
str
) – The string to convert.- Return type
A string with each character of the given
text
converted into an escaped UTF8 hexadecimal.Example
>>> from flutils.strutils import as_literal_utf8 >>> t = '1.★ 🛑' >>> as_escaped_utf8_literal(t) '\\x31\\x2e\\xe2\\x98\\x85\\x20\\xf0\\x9f\\x9b \\x91'
camel_to_underscore
(text)[source]¶Convert a camel-cased string to a string containing words separated with underscores.
Example
>>> from flutils.strutils import camel_to_underscore >>> camel_to_underscore('FooBar') 'foo_bar'
convert_escaped_unicode_literal
(text)[source]¶Convert any escaped Unicode literal hexadecimal character(s) to the proper character(s).
This function will convert a string, that may contain escaped Unicode literal hexadecimal characters, into a string with the proper characters.
- Parameters
text (
str
) – The string that may have escaped Unicode hexadecimal.- Return type
A string with each escaped Unicode hexadecimal character converted into the proper character.
The following Unicode literal formats are supported:
\x31 \u0031 \U00000031Examples
Basic usage:
>>> from flutils.strutils import convert_escaped_unicode_literal >>> a = '\\x31\\x2e\\u2605\\x20\\U0001f6d1' >>> convert_escaped_unicode_literal(a) '1.★ 🛑'This function is intended for cases when the value of an environment variable contains escaped Unicode literal characters that need to be converted to proper characters:
$ export TEST='\x31\x2e\u2605\x20\U0001f6d1' $ python>>> import os >>> from flutils.strutils import convert_escaped_unicode_literal >>> a = os.getenv('TEST') >>> a '\\x31\\x2e\\u2605\\x20\\U0001f6d1' >>> convert_escaped_unicode_literal(a) '1.★ 🛑'
convert_escaped_utf8_literal
(text)[source]¶Convert any escaped UTF-8 hexadecimal character bytes into the proper string characters(s).
This function will convert a string, that may contain escaped UTF-8 literal hexadecimal bytes, into a string with the proper characters.
- Parameters
text (
str
) – The string that may have escaped UTF8 hexadecimal.- Raises
UnicodeDecodeError – if any of the escaped hexadecimal characters are not proper UTF8 bytes.
- Return type
A string with each escaped UTF8 hexadecimal character converted into the proper character.
Examples
Basic usage:
>>> from flutils.strutils import convert_raw_utf8_escape >>> a = 'test\\xc2\\xa9' >>> convert_escaped_utf8_literal(a) 'test©'This function is intended for cases when the value of an environment variable contains escaped UTF-8 literal characters (bytes) that need to be converted to proper characters:
$ export TEST='test\\xc2\\xa9' $ python>>> import os >>> from flutils.strutils import convert_raw_utf8_escape >>> a = os.getenv('TEST') >>> a 'test\\xc2\\xa9' >>> convert_escaped_utf8_literal(a) 'test©'
underscore_to_camel
(text, lower_first=True)[source]¶Convert a string with words separated by underscores to a camel-cased string.
- Parameters
- Return type
Examples
>>> from flutils.strutils import underscore_to_camel >>> underscore_to_camel('foo_bar') 'fooBar' >>> underscore_to_camel('_one__two___',lower_first=False) 'OneTwo'
Text¶
flutils offers the following text functions and objects:
len_without_ansi
(seq)[source]¶Return the character length of the given
Sequence
without counting any ANSI codes.New in version 0.6
Example
>>> from flutils.txtutils import len_without_ansi >>> text = '\x1b[38;5;209mfoobar\x1b[0m' >>> len_without_ansi(text) 6
- class
AnsiTextWrapper
(width=70, initial_indent='', subsequent_indent='', expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]')[source]¶A
TextWrapper
object that correctly wraps text containing ANSI codes.New in version 0.6
- Parameters
width (int, optional) – The maximum length of wrapped lines. As long as there are no individual words in the input text longer than this given
width
,AnsiTextWrapper
guarantees that no output line will be longer thanwidth
characters. Defaults to:70
initial_indent (str, optional) – Text that will be prepended to the first line of wrapped output. Counts towards the length of the first line. An empty string value will not indent the first line. Defaults to:
''
an empty string.subsequent_indent (str, optional) – Text that will be prepended to all lines of wrapped output except the first. Counts towards the length of each line except the first. Defaults to:
''
an empty string.expand_tabs (bool, optional) – If
True
, then all tab characters in text will be expanded to spaces using theexpandtabs
. Also see thetabsize
argument. Defaults to:True
.replace_whitespace (bool, optional) – If
True
, after tab expansion but before wrapping, the wrap() method will replace each whitespace character with a single space. The whitespace characters replaced are as follows: tab, newline, vertical tab, form-feed, and carriage return ('\t\n\v\f\r'
). Defaults to:True
.fix_sentence_endings (bool, optional) – If
True
,AnsiTextWrapper
attempts to detect sentence endings and ensure that sentences are always separated by exactly two spaces. This is generally desired for text in a monospaced font. However, the sentence detection algorithm is imperfect; it assumes that a sentence ending consists of a lowercase letter followed by one of ‘.’, ‘!’, or ‘?’, possibly followed by one of ‘”’ or “’”, followed by a space. Defaults to:False
.break_long_words (bool, optional) – If
True
, then words longer than width will be broken in order to ensure that no lines are longer than width. If it isFalse
, long words will not be broken, and some lines may be longer than width. (Long words will be put on a line by themselves, in order to minimize the amount by which width is exceeded.) Defaults to:True
.drop_whitespace (bool, optional) – If
True
, whitespace at the beginning and ending of every line (after wrapping but before indenting) is dropped. Whitespace at the beginning of the paragraph, however, is not dropped if non-whitespace follows it. If whitespace being dropped takes up an entire line, the whole line is dropped. Defaults to:True
break_on_hyphens (bool, optional) – If
True
, wrapping will occur preferably on whitespaces and right after hyphens in compound words, as it is customary in English. Iffalse
, only whitespaces will be considered as potentially good places for line breaks, but you need to setbreak_long_words
toFalse
if you want truly insecable words. Defaults to:True
.tabsize (int, optional) – If
expand_tabs
isTrue
, then all tab characters in text will be expanded to zero or more spaces, depending on the current column and the given tab size. Defaults to:8
.max_lines (
int
orNone
, optional) – If notNone
, then the output will contain at mostmax_lines lines
, withplaceholder
appearing at the end of the output. Defaults to:None
.placeholder (str, optional) – Text that will appear at the end of the output text if it has been truncated. Defaults to:
' [...]'
Note
The
initial_indent
,subsequent_indent
andplaceholder
parameters can also contain ANSI codes.Note
If
expand_tabs
isFalse
andreplace_whitespace
isTrue
, each tab character will be replaced by a single space, which is not the same as tab expansion.Note
If
replace_whitespace
isFalse
, newlines may appear in the middle of a line and cause strange output. For this reason, text should be split into paragraphs (usingstr.splitlines
or similar) which are wrapped separately.Example
Use
AnsiTextWrapper
the same way as usingTextWrapper
:from flutils.txtutils import AnsiTextWrapper text = ( '\x1b[31m\x1b[1m\x1b[4mLorem ipsum dolor sit amet, ' 'consectetur adipiscing elit. Cras fermentum maximus ' 'auctor. Cras a varius ligula. Phasellus ut ipsum eu ' 'erat consequat posuere.\x1b[0m Pellentesque habitant ' 'morbi tristique senectus et netus et malesuada fames ac ' 'turpis egestas. Maecenas ultricies lacus id massa ' 'interdum dignissim. Curabitur \x1b[38;2;55;172;230m ' 'efficitur ante sit amet nibh consectetur, consequat ' 'rutrum nunc\x1b[0m egestas. Duis mattis arcu eget orci ' 'euismod, sit amet vulputate ante scelerisque. Aliquam ' 'ultrices, turpis id gravida vestibulum, tortor ipsum ' 'consequat mauris, eu cursus nisi felis at felis. ' 'Quisque blandit lacus nec mattis suscipit. Proin sed ' 'tortor ante. Praesent fermentum orci id dolor ' '\x1b[38;5;208meuismod, quis auctor nisl sodales.\x1b[0m' ) wrapper = AnsiTextWrapper(width=40) wrapped_text = wrapper.fill(text) print(wrapped_text)The output:
wrap
(text)[source]¶Wraps the single paragraph in the given
text
so every line is at mostwidth
characters long. All wrapping options are taken from instance attributes of theAnsiTextWrapper
instance.
Validation¶
flutils offers the following validation functions.
validate_identifier
(identifier, allow_underscore=True)[source]¶Validate the given string is a proper identifier.
This validator will also raise an error if the given identifier is a keyword or a builtin identifier.
- Parameters
identifier (
str
orUserString
) – The value to be tested.allow_underscore (
bool
, optional) – A value ofFalse
will raise an error when theidentifier
has a value that starts with an underscore_
. (UseFalse
when validating potentialnamedtuple
keys) Defaults to:True
.- Raises
SyntaxError – If the given identifier is invalid.
TypeError – If the given identifier is not a
str
orUserString
.- Return type
Example
>>> from flutils.validators import validate_identifier >>> validate_identifier('123') SyntaxError: The given 'identifier', '123', cannot start with a number
Install¶
Because flutils has no dependencies, installing flutils is quite easy.
Requirements¶
flutils will only work with Python 3.6, 3.7 and 3.8+
Install with pip¶
>>> pip install flutils
Install from source¶
Clone the repo:
>>> git clone https://gitlab.com/finite-loop/flutils.git flutils >>> cd flutilsUse the latest release:
{VERSION}
= ‘v0.7’Checkout the release version:
>>> git checkout tags/{VERSION}Install:
>>> ./setup.py install
Glossary¶
- cherry-pick
is a term used within the context of flutils to describe the process of choosing modules that will be lazy-loaded. Meaning, the module (as set in the foreign-name) will be loaded (unless already loaded) and executed when an attribute is accessed.
Cherry-picking differs from tree shaking in that it does not remove “dead” code. Instead, code is loaded (unless already loaded) and executed when used. Unused code will not be loaded and executed.
- cherry-pick-definition package module
is a term used within the context of flutils to describe a Python package module (
__init__.py
) which contains an__attr_map__
attribute and calls thecherry_pick
function.
__attr_map__
must be atuple
with each row containing a foreign-nameThis module may also have an optional
__additional_attrs__
attribute which is adictionary
of attribute names and values to be passed to the cherry-picking module.This module should not have any functions or classes defined.
- cherry-picking module
is a term used within the context of flutils to describe a dynamically generated Python module that will load (unless already loaded) and execute a cherry-picked module when an attribute (on the cherry-picking module) is accessed.
- foreign-name
is a term used within the context of flutils to describe a string that contains the full dotted notation to a module. This is used for cherry-picking modules.
This full dotted notation can not contain any relative references (e.g
'..othermodule'
,'.mysubmodule'
). However,theimportlib.util.resolve_name
function can be used to generate the full dotted notation string of a relative referenced module in a cherry-pick-definition package module:from importlib.util import resolve_name from flutils import cherry_pick __attr_map__ = ( resolve_name('.mysubmodule', __package__) ) cherry_pick(globals())The foreign-name for the
os.path
module is:'os.path'
A foreign-name may also reference a module attribute by using the full dotted notation to the module, followed with a colon
:
and then the desired module attribute.To reference the
dirname
function:'os.path:dirname'
A foreign-name can also contain an alias which will become the attribute name on the cherry-picking module. This attribute (alias) will be bound to the cherry-picked module. Follow the pep-8 naming conventions. when creating the the alias. A foreign-name with an alias is just the foreign-name followed by a comma
,
then the alias:'mymodule.mysubmodule:hello,custom_function'
Or:
'mymodule.mysubmodule,mymodule'
Foreign-names are used in a cherry-picking module to manage the loading and executing of modules when calling attributes on the cherry-picking module.
- glob pattern
flutils provides functions for working with filesystem paths. Some of these functions offer the ability to find matching paths using “glob patterns”.
Glob patterns are Unix shell-style wildcards (pattern), which are not the same as regular expressions. The special characters used in shell-style wildcards are:
Pattern
Meaning
*
matches everything
**
matches any files and zero or more directories and sub directories
?
matches any single character
[seq]
matches any character in seq
[!seq]
matches any character not in seq
Warning
Using the
**
pattern in large directory trees may consume an inordinate amount of time.Examples:
To find all python files in a directory:
>>> from flutils import find_paths >>> list(find_paths('~/tmp/*.py') [PosixPath('/home/test_user/tmp/one.py') PosixPath('/home/test_user/tmp/two.py')]To find all python files in a directory and any subdirectories:
>>> list(find_paths('~/tmp/**/*.py') [PosixPath('/home/test_user/tmp/one.py') PosixPath('/home/test_user/tmp/two.py')] PosixPath('/home/test_user/tmp/zero/__init__.py')]To find all python files that have a 3 character extension:
>>> list(find_paths('~/tmp/*.py?')To find all .pyc and .pyo files:
>>> list(find_paths('~/tmp/*.py[co]')If you want to match an arbitrary literal string that may have any of the patterns, use
glob.escape
:>>> import glob >>> base = glob.escape('~/a[special]file%s') >>> list(find_paths(base % '[0-9].txt'))- module attribute
is an executable statement or a function/class definition. In other words a module attribute is an attribute on a python module that can reference pretty much anything, such as functions, objects, variables, etc…
- tree shaking
is a term commonly used within JavaScript context to describe the removal of dead code.
Index¶
Release Notes¶
0.7¶
Released: 2020-07-23
Added
get_encoding
Added
prep_cmd
Added
RunCmd
Added
CompletedProcess
0.6¶
Released: 2020-03-30
Added
len_without_ansi
Added
AnsiTextWrapper
Added
run
0.5¶
Released: 2020-02-20
Added
to_namedtuple
Works with Python3.8
0.4¶
Released: 2019-07-16
Added
add_setup_cfg_commands
Added
as_escaped_utf8_literal
Added the b64 codecs via
register_codecs
Added the Raw UTF-8 Escape codec via
register_codecs
Removed the restriction of Python string method names as identifiers in:
Rewrite of
bump_version
Improved unit and integration tests.
0.3¶
Released: 2018-10-29
Added
bump_version
0.2¶
Released: 2018-10-27
Added
cached_property
Works with Python3.7
Development¶
Contributions to flutils can be made by sending a Merge request via fork of GitLab. If you don’t have a GitLab account you can sign up here.
Requirements¶
pyenv¶
pyenv is used to install and manage different versions of Python on your system and will not effect the system’s Python.
Install the pyenv prerequisites.
(Optional) By default pyenv will be installed at
~/.pyenv
. If you desire, change the install location by setting the environment variable,PYENV_ROOT
, with the new location (e.g.export PYENV_ROOT=/a/new/path
).Run the following to install:
>>> curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
Restart your shell so that path changes take effect:
>>> exec "${SHELL}"
Add the
xxenv-latest
plug-in topyenv
:>>> git clone https://github.com/momo-lab/xxenv-latest.git "$(pyenv root)"/plugins/xxenv-latest
Python¶
flutils is designed to work with multiple versions of Python. So, we need to make sure the latest versions are installed.
As new versions of Python are released you’ll need to follow these same instructions.
Update pyenv:
>>> pyenv update
Install the latest versions of the following Pythons:
>>> pyenv latest install -v >>> pyenv latest install -v 3.7 >>> pyenv latest install -v 3.6
pipenv¶
pipenv is used for setting up a development virtualenv and installing and managing the development dependencies.
Follow the instructions for installing pipenv here.
Warning
There has not been a new release of pipenv since 2018-11-26. There are also rumors that the project may be dead. Because of this, sometime in the future, flutils will replace the use of pipenv with poetry.
Setup¶
Code¶
Clone the flutils code from GitLab :
Clone with SSH:
>>> git clone git@gitlab.com:finite-loop/flutils.git
or, with HTTPS:
>>> git clone https://gitlab.com/finite-loop/flutils.git
Change directory:
>>> cd flutils
Virtual Environment¶
In the code’s root directory run the following command to setup the virtualenv needed for development:
>>> pipenv install --dev --python "$(pyenv root)/versions/$(pyenv latest -p)/bin/python"
To activate the flutils virtualenv:
>>> pipenv shell
Testing¶
Within the activated flutils virtualenv, the following commands can be used to test code changes:
./setup.py test
will run all unit tests, integration tests and type checks (mypy)../setup.py coverage
will run all the tests and produce a coverage report../setup.py lint
will run code analysis checks using pylint../setup.py style
will run code styling enforcement checks using flake8../setup.py security
will run code security checks using bandit../setup.py pipelinetests
will run all of the above tests.make tests
will run all of the above tests across the multiple supported versions of Python using tox. make sure to run this command before submitting a pull-request. Code that does not pass this test will not be accepted.
CI Environment¶
Warning
This requires Docker to be installed and running.
Run the following commands when a new version of Python has been released:
If a new minor version (e.g. 3.9) of Python has been released, changes need to be made in
tests/Dockerfile
to reflect the new version.To build a new Docker image with the latest supported versions of Python:
>>> make docker-image
Deploy the newly built Docker image to the regestry:
>>> make docker-image-push
New Release¶
Bump the version number in
flutils/__init__.py
, commit and push.Update the build requirements for the documentation build server.
>>> make docs-requirements
Cut a new tag:
>>> git tag "v$(python -c 'import flutils; print(flutils.__version__)')"
Push the new tag:
>>> git push --tags
Build and push the release to
test.pypi.org
:>>> make sdist-push-test
Go to the link shown in the output of the above command and verify everything.
Build and push the release to
pypi.org
:>>> make sdist-push