Assorted function decorators.
*Latest release 20241206*:
@uses_cmd_options: use an object subclass if there is no cs.cmdutils so that we can add attributes to the placeholder options object.
## <a name="ALL"></a>`ALL(func)`
Include this function's name in its module's `__all__` list.
Example:
from cs.deco import ALL
__all__ = []
def obscure_function(...):
...
@ALL
def well_known_function(...):
...
## <a name="cached"></a>`cached(*a, **kw)`
OBSOLETE cached
Former name for @cachedmethod.
## <a name="cachedmethod"></a>`cachedmethod(*da, **dkw)`
Decorator to cache the result of an instance or class method
and keep a revision counter for changes.
The cached values are stored on the instance (`self`).
The revision counter supports the `@revised` decorator.
This decorator may be used in 2 modes.
Directly:
@cachedmethod
def method(self, ...)
or indirectly:
@cachedmethod(poll_delay=0.25)
def method(self, ...)
Optional keyword arguments:
* `attr_name`: the basis name for the supporting attributes.
Default: the name of the method.
* `poll_delay`: minimum time between polls; after the first
access, subsequent accesses before the `poll_delay` has elapsed
will return the cached value.
Default: `None`, meaning the value never becomes stale.
* `sig_func`: a signature function, which should be significantly
cheaper than the method. If the signature is unchanged, the
cached value will be returned. The signature function
expects the instance (`self`) as its first parameter.
Default: `None`, meaning no signature function;
the first computed value will be kept and never updated.
* `unset_value`: the value to return before the method has been
called successfully.
Default: `None`.
If the method raises an exception, this will be logged and
the method will return the previously cached value,
unless there is not yet a cached value
in which case the exception will be reraised.
If the signature function raises an exception
then a log message is issued and the signature is considered unchanged.
An example use of this decorator might be to keep a "live"
configuration data structure, parsed from a configuration
file which might be modified after the program starts. One
might provide a signature function which called `os.stat()` on
the file to check for changes before invoking a full read and
parse of the file.
*Note*: use of this decorator requires the `cs.pfx` module.
## <a name="contextdecorator"></a>`contextdecorator(*da, **dkw)`
A decorator for a context manager function `cmgrfunc`
which turns it into a decorator for other functions.
This supports easy implementation of "setup" and "teardown"
code around other functions without the tedium of defining
the wrapper function itself. See the examples below.
The resulting context manager accepts an optional keyword
parameter `provide_context`, default `False`. If true, the
context returned from the context manager is provided as the
first argument to the call to the wrapped function.
Note that the context manager function `cmgrfunc`
has _not_ yet been wrapped with `@contextmanager`,
that is done by `@contextdecorator`.
This decorator supports both normal functions and generator functions.
With a normal function the process is:
* call the context manager with `(func,a,kw,*da,**dkw)`,
returning `ctxt`,
where `da` and `dkw` are the positional and keyword parameters
supplied when the decorator was defined.
* within the context
return the value of `func(ctxt,*a,**kw)` if `provide_context` is true
or the value of `func(*a,**kw)` if not (the default)
With a generator function the process is:
* obtain an iterator by calling `func(*a,**kw)`
* for iterate over the iterator, yielding its results,
by calling the context manager with `(func,a,kw,**da,**dkw)`,
around each `next()`
Note that it is an error to provide a true value for `provide_context`
if the decorated function is a generator function.
Some examples follow.
Trace the call and return of a specific function:
@contextdecorator
def tracecall(func, a, kw):
""" Trace the call and return from some function.
This can easily be adapted to purposes such as timing a
function call or logging use.
"""
print("call %s(*%r,**%r)" % (func, a, kw))
try:
yield
except Exception as e:
print("exception from %s(*%r,**%r): %s" % (func, a, kw, e))
raise
else:
print("return from %s(*%r,**%r)" % (func, a, kw))
@tracecall
def f():
""" Some function to trace.
"""
@tracecall(provide_context=True):
def f(ctxt, *a, **kw):
""" A function expecting the context object as its first argument,
ahead of whatever other arguments it would normally require.
"""
See who is making use of a generator's values,
when a generator might be invoked in one place and consumed elsewhere:
from cs.py.stack import caller
@contextdecorator
def genuser(genfunc, *a, **kw):
user = caller(-4)
print(f"iterate over {genfunc}(*{a!r},**{kw!r}) from {user}")
yield
@genuser
def linesof(filename):
with open(filename) as f:
yield from f
# obtain a generator of lines here
lines = linesof(__file__)
# perhaps much later, or in another function
for lineno, line in enumerate(lines, 1):
print("line %d: %d words" % (lineno, len(line.split())))
Turn on "verbose mode" around a particular function:
import sys
import threading
from cs.context import stackattrs
class State(threading.local):
def __init__(self):
# verbose if stderr is on a terminal
self.verbose = sys.stderr.isatty()
# per thread global state
state = State()
@contextdecorator
def verbose(func):
with stackattrs(state, verbose=True) as old_attrs:
if not old_attrs['verbose']:
print(f"enabled verbose={state.verbose} for function {func}")
# yield the previous verbosity as the context
yield old_attrs['verbose']
# turn on verbose mode
@verbose
def func(x, y):
if state.verbose:
# print if verbose
print("x =", x, "y =", y)
# turn on verbose mode and also pass in the previous state
# as the first argument
@verbose(provide_context=True):
def func2(old_verbose, x, y):
if state.verbose:
# print if verbose
print("old_verbosity =", old_verbose, "x =", x, "y =", y)
## <a name="contextual"></a>`contextual(func)`
Wrap a simple function as a context manager.
This was written to support users of `@strable`,
which requires its `open_func` to return a context manager;
this turns an arbitrary function into a context manager.
Example promoting a trivial function:
>>> f = lambda: 3
>>> cf = contextual(f)
>>> with cf() as x: print(x)
3
## <a name="decorator"></a>`decorator(deco)`
Wrapper for decorator functions to support optional arguments.
The actual decorator function ends up being called as:
mydeco(func, *da, **dkw)
allowing `da` and `dkw` to affect the behaviour of the decorator `mydeco`.
Examples:
# define your decorator as if always called with func and args
@decorator
def mydeco(func, *da, arg2=None):
... decorate func subject to the values of da and arg2
# mydeco called with defaults
@mydeco
def func1(...):
...
@ mydeco called with nondefault arguments
@mydeco('foo', arg2='bah')
def func2(...):
...
## <a name="default_params"></a>`default_params(*da, **dkw)`
Decorator to provide factory functions for default parameters.
This decorator accepts the following keyword parameters:
* `_strict`: default `False`; if true only replace genuinely
missing parameters; if false also replace the traditional
`None` placeholder value
The remaining keyword parameters are factory functions
providing the respective default values.
Atypical one off direct use:
@default_params(dbconn=open_default_dbconn,debug=lambda:settings.DB_DEBUG_MODE)
def dbquery(query, *, dbconn):
dbconn.query(query)
Typical use as a decorator factory:
# in your support module
uses_ds3 = default_params(ds3client=get_ds3client)
# calling code which needs a ds3client
@uses_ds3
def do_something(.., *, ds3client, ...):
... make queries using ds3client ...
This replaces the standard boilerplate and avoids replicating
knowledge of the default factory as exhibited in this legacy code:
def do_something(.., *, ds3client=None, ...):
if ds3client is None:
ds3client = get_ds3client()
... make queries using ds3client ...
## <a name="fmtdoc"></a>`fmtdoc(func)`
Decorator to replace a function's docstring with that string
formatted against the function's module `__dict__`.
This supports simple formatted docstrings:
ENVVAR_NAME = 'FUNC_DEFAULT'
@fmtdoc
def func():
"""Do something with os.environ[{ENVVAR_NAME}]."""
print(os.environ[ENVVAR_NAME])
This gives `func` this docstring:
Do something with os.environ[FUNC_DEFAULT].
*Warning*: this decorator is intended for wiring "constants"
into docstrings, not for dynamic values. Use for other types
of values should be considered with trepidation.
## <a name="logging_wrapper"></a>`logging_wrapper(*da, **dkw)`
Decorator for logging call shims
which bumps the `stacklevel` keyword argument so that the logging system
chooses the correct frame to cite in messages.
Note: has no effect on Python < 3.8 because `stacklevel` only
appeared in that version.
## <a name="observable_class"></a>`observable_class(property_names, only_unequal=False)`
Class decorator to make various instance attributes observable.
Parameters:
* `property_names`:
an interable of instance property names to set up as
observable properties. As a special case a single `str` can
be supplied if only one attribute is to be observed.
* `only_unequal`:
only call the observers if the new property value is not
equal to the previous proerty value. This requires property
values to be comparable for inequality.
Default: `False`, meaning that all updates will be reported.
## <a name="OBSOLETE"></a>`OBSOLETE(*da, **dkw)`
A decorator for obsolete functions or classes.
Use:
@OBSOLETE
def func(...):
or
@OBSOLETE("new_func_name")
def func(...):
This emits a warning log message before calling the decorated function.
Only one warning is emitted per calling location.
## <a name="Promotable"></a>Class `Promotable`
A mixin class which supports the `@promote` decorator.
*`Promotable.promote(obj, **from_t_kw)`*:
Promote `obj` to an instance of `cls` or raise `TypeError`.
This method supports the `@promote` decorator.
This base method will call the `from_`*typename*`(obj,**from_t_kw)`
class factory method if present, where *typename* is
`obj.__class__.__name__`.
Subclasses may override this method to promote other types,
typically:
@classmethod
def promote(cls, obj):
if isinstance(obj, cls):
return obj
... various specific type promotions
... not done via a from_typename factory method
# fall back to Promotable.promote
return super().promote(obj)
An typical `from_`*typename*` factory method:
class Foo(Promotable):
def __init__(self, dbkey, dbrow):
self.key = dbkey
self.row_data = row
@classmethod
def from_str(cls, s : str):
"""Accept a database key string, return a `Foo` instance."""
row = db_lookup(s)
return cls(s, row)
This supports using `@promote` on functions with `Foo` instances:
@promote
def do_it(foo : Foo):
... work with foo ...
but calling it as:
do_it("foo_key_value")
## <a name="promote"></a>`promote(*da, **dkw)`
A decorator to promote argument values automatically in annotated functions.
If the annotation is `Optional[some_type]` or `Union[some_type,None]`
then the promotion will be to `some_type` but a value of `None`
will be passed through unchanged.
The decorator accepts optional parameters:
* `params`: if supplied, only parameters in this list will
be promoted
* `types`: if supplied, only types in this list will be
considered for promotion
For any parameter with a type annotation, if that type has a
`.promote(value)` class method and the function is called with a
value not of the type of the annotation, the `.promote` method
will be called to promote the value to the expected type.
Note that the `Promotable` mixin provides a `.promote()`
method which promotes `obj` to the class if the class has a
factory class method `from_`*typename*`(obj)` where *typename*
is `obj.__class__.__name__`.
A common case for me is lexical objects which have a `from_str(str)`
factory to produce an instance from its textual form.
Additionally, if the `.promote(value)` class method raises a `TypeError`
and `value` has a `.as_`*typename* attribute
(where *typename* is the name of the type annotation),
if that attribute is an instance method of `value`
then promotion will be attempted by calling `value.as_`*typename*`()`
otherwise the attribute will be used directly
on the presumption that it is a property.
A typical `promote(cls, obj)` method looks like this:
@classmethod
def promote(cls, obj):
if isinstance(obj, cls):
return obj
... recognise various types ...
... and return a suitable instance of cls ...
raise TypeError(
"%s.promote: cannot promote %s:%r",
cls.__name__, obj.__class__.__name__, obj)
Example:
>>> from cs.timeseries import Epoch
>>> from typeguard import typechecked
>>>
>>> @promote
... @typechecked
... def f(data, epoch:Epoch=None):
... print("epoch =", type(epoch), epoch)
...
>>> f([1,2,3], epoch=12.0)
epoch = <class 'cs.timeseries.Epoch'> Epoch(start=0, step=12)
Example using a class with an `as_P` instance method:
>>> class P:
... def __init__(self, x):
... self.x = x
... @classmethod
... def promote(cls, obj):
... raise TypeError("dummy promote method")
...
>>> class C:
... def __init__(self, n):
... self.n = n
... def as_P(self):
... return P(self.n + 1)
...
>>> @promote
... def p(p: P):
... print("P =", type(p), p.x)
...
>>> c = C(1)
>>> p(c)
P = <class 'cs.deco.P'> 2
*Note*: one issue with this is due to the conflict in name
between this decorator and the method it looks for in a class.
The `promote` _method_ must appear after any methods in the
class which are decorated with `@promote`, otherwise the
`promote` method supplants the name `promote` making it
unavailable as the decorator.
I usually just make `.promote` the last method.
Failing example:
class Foo:
@classmethod
def promote(cls, obj):
... return promoted obj ...
@promote
def method(self, param:Type, ...):
...
Working example:
class Foo:
@promote
def method(self, param:Type, ...):
...
# promote method as the final method of the class
@classmethod
def promote(cls, obj):
... return promoted obj ...
## <a name="strable"></a>`strable(*da, **dkw)`
Decorator for functions which may accept a `str`
instead of their core type.
Parameters:
* `func`: the function to decorate
* `open_func`: the "open" factory to produce the core type
if a string is provided;
the default is the builtin "open" function.
The returned value should be a context manager.
Simpler functions can be decorated with `@contextual`
to turn them into context managers if need be.
The usual (and default) example is a function to process an
open file, designed to be handed a file object but which may
be called with a filename. If the first argument is a `str`
then that file is opened and the function called with the
open file.
Examples:
@strable
def count_lines(f):
return len(line for line in f)
class Recording:
"Class representing a video recording."
...
@strable(open_func=Recording)
def process_video(r):
... do stuff with `r` as a Recording instance ...
*Note*: use of this decorator requires the `cs.pfx` module.
## <a name="uses_cmd_options"></a>`uses_cmd_options(*da, **dkw)`
A decorator to provide default keyword arguments
from the prevailing `cs.cmdutils.BaseCommandOptions`
if available, otherwise from `option_defaults`.
This exists to provide plumbing free access to options set
up by a command line invocation using `cs.cmdutils.BaseCommand`.
If no `option_defaults` are provided, a single `options`
keyword argument is provided which is the prevailing
`BaseCommand.Options` instance.
The decorator accepts two optional "private" keyword arguments
commencing with underscores:
* `_strict`: default `False`; if true then an `option_defaults`
will only be applied if the argument is _missing_ from the
function arguments, otherwise it will be applied if the
argument is missing or `None`
* `_options_param_name`: default `'options'`; this is the
name of the single `options` keyword argument which will be
supplied if there are no `option_defaults`
Examples:
@uses_cmd_options(doit=True, quiet=False)
def func(x, *, doit, quiet, **kw):
if not quiet:
print("something", x, kw)
if doit:
... do the thing ...
... etc ...
@uses_cmd_options()
def func(x, *, options, **kw):
if not options.quiet:
print("something", x, kw)
if options.doit:
... do the thing ...
... etc ...
## <a name="uses_doit"></a>`uses_doit(func)`
`func` is not supplied, collect the arguments supplied and return a
decorator which takes the subsequent callable and returns
`deco(func, *da, **kw)`.
## <a name="uses_force"></a>`uses_force(func)`
`func` is not supplied, collect the arguments supplied and return a
decorator which takes the subsequent callable and returns
`deco(func, *da, **kw)`.
## <a name="uses_quiet"></a>`uses_quiet(func)`
`func` is not supplied, collect the arguments supplied and return a
decorator which takes the subsequent callable and returns
`deco(func, *da, **kw)`.
## <a name="uses_verbose"></a>`uses_verbose(func)`
`func` is not supplied, collect the arguments supplied and return a
decorator which takes the subsequent callable and returns
`deco(func, *da, **kw)`.
# Release Log
*Release 20241206*:
@uses_cmd_options: use an object subclass if there is no cs.cmdutils so that we can add attributes to the placeholder options object.
*Release 20241109*:
* Add @uses_force to @uses_doit, @uses_quiet, @uses_verbose decorator set.
* Fold @uses_cmd_option into @uses_cmd_options, provide "options" if no option_defaults.
* @promote: accept optional keyword arguments, plumb to the from_typename factory method.
*Release 20241007*:
@uses_cmd_option: bugfix the infill of the defaults.
*Release 20241003*:
New @uses_cmd_option and @uses_doit, @uses_quiet and @uses_verbose based on it.
*Release 20240709*:
Bugfix @default_params: assemble the new signature with a complete set of parameters instead of only the defaulted ones.
*Release 20240630*:
* @default_params: apply modified signature parameters one at a time, as applying them in a single list seems to trigger some ordering checks.
* @promote: pass the original Parameter through to the wrapper so that we can use .default if needed, have the wrapper test for None last, so that we can promote None to something with a .from_NoneType method, make the check for the default value more specific.
*Release 20240412*:
@decorator: apply the decorated function name to the metadecorator.
*Release 20240326*:
default_params: update wrapper signature to mark the defaulted params as optional.
*Release 20240316*:
Fixed release upload artifacts.
*Release 20240314.1*:
New release with corrected install path.
*Release 20240314*:
Tiny doc update.
*Release 20240303*:
Promotable.promote: do not just handle str, handle anything with a from_*typename* factory method.
*Release 20240211*:
Promotable: no longer abstract, provide a default promote() method which tries cls.from_str for str.
*Release 20231129*:
@cachedmethod: ghastly hack for the revision attribute to accomodate objects which return None for missing attributes.
*Release 20230331*:
@promote: pass None through for Optional parameters.
*Release 20230212*:
New Promotable abstract class mixin, which requires the creation of a .promote(cls,obj)->cls class method.
*Release 20230210*:
@promote: add support for .as_TypeName() instance method on the source object or a .as_TypeName property/attribut.
*Release 20221214*:
@decorator: use functools.update_wrapper to propagate the decorated function's attributes to the wrapper (still legacy code for Python prior to 3.2).
*Release 20221207*:
Small updates.
*Release 20221106.1*:
@promote: support Optional[sometype] parameters.
*Release 20221106*:
New @promote decorator to autopromote parameter values according to their type annotation.
*Release 20220918.1*:
@default_param: append parameter descriptions to the docstring of the decorated function, bugfix __name__ setting.
*Release 20220918*:
* @OBSOLETE: tweak message format.
* @default_params: docstring: simplify the example and improve the explaination.
* @default_params: set the __name__ of the wrapper function.
*Release 20220905*:
* New @ALL decorator to include a function in __all__.
* New @default_params decorator for making decorators which provide default argument values from callables.
*Release 20220805*:
@OBSOLETE: small improvements.
*Release 20220327*:
Some fixes for @cachedmethod.
*Release 20220311*:
@cachedmethod: change the meaning of poll_delay=None to mean "never goes stale" as I had thought it already did.
*Release 20220227*:
@cachedmethod: more paranoid access to the revision attribute.
*Release 20210823*:
@decorator: preserve the __name__ of the wrapped function.
*Release 20210123*:
Syntax backport for older Pythons.
*Release 20201202*:
@decorator: tweak test for callable(da[0]) to accord with the docstring.
*Release 20201025*:
New @contextdecorator decorator for context managers to turn them into setup/teardown decorators.
*Release 20201020*:
* @cachedmethod: bugfix cache logic.
* @strable: support generator functions.
*Release 20200725*:
Overdue upgrade of @decorator to support combining the function and decorator args in one call.
*Release 20200517.2*:
Minor upgrade to @OBSOLETE.
*Release 20200517.1*:
Tweak @OBSOLETE and @cached (obsolete name for @cachedmethod).
*Release 20200517*:
Get warning() from cs.gimmicks.
*Release 20200417*:
* @decorator: do not override __doc__ on the decorated function, just provide default.
* New @logging_wrapper which bumps the `stacklevel` parameter in Python 3.8 and above so that shims recite the correct caller.
*Release 20200318.1*:
New @OBSOLETE to issue a warning on a call to an obsolete function, like an improved @cs.logutils.OBSOLETE (which needs to retire).
*Release 20200318*:
@cachedmethod: tighten up the "is the value changed" try/except.
*Release 20191012*:
* New @contextual decorator to turn a simple function into a context manager.
* @strable: mention context manager requirement and @contextual as workaround.
*Release 20191006*:
Rename @cached to @cachedmethod, leave compatible @cached behind which issues a warning (will be removed in a future release).
*Release 20191004*:
Avoid circular import with cs.pfx by removing requirement and doing the import later if needed.
*Release 20190905*:
Bugfix @deco: it turns out that you may not set the .__module__ attribute on a property object.
*Release 20190830.2*:
Make some getattr calls robust.
*Release 20190830.1*:
@decorator: set the __module__ of the wrapper.
*Release 20190830*:
@decorator: set the __module__ of the wrapper from the decorated target, aids cs.distinf.
*Release 20190729*:
@cached: sidestep uninitialised value.
*Release 20190601.1*:
@strable: fix the example in the docstring.
*Release 20190601*:
* Bugfix @decorator to correctly propagate the docstring of the subdecorator.
* Improve other docstrings.
*Release 20190526*:
@decorator: add support for positional arguments and rewrite - simpler and clearer.
*Release 20190512*:
@fmtdoc: add caveat against misuse of this decorator.
*Release 20190404*:
New @fmtdoc decorator to format a function's doctsring against its module's globals.
*Release 20190403*:
* @cached: bugfix: avoid using unset sig_func value on first pass.
* @observable_class: further tweaks.
*Release 20190322.1*:
@observable_class: bugfix __init__ wrapper function.
*Release 20190322*:
* New class decorator @observable_class.
* Bugfix import of "warning".
*Release 20190309*:
@cached: improve the exception handling.
*Release 20190307.2*:
Fix docstring typo.
*Release 20190307.1*:
Bugfix @decorator: final plumbing step for decorated decorator.
*Release 20190307*:
* @decorator: drop unused arguments, they get used by the returned decorator.
* Rework the @cached logic.
*Release 20190220*:
* Bugfix @decorator decorator, do not decorate twice.
* Have a cut at inheriting the decorated function's docstring.
*Release 20181227*:
* New decoartor @strable for function which may accept a str instead of their primary type.
* Improvements to @cached.
*Release 20171231*:
Initial PyPI release.
Raw data
{
"_id": null,
"home_page": null,
"name": "cs-deco",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "python2, python3",
"author": null,
"author_email": "Cameron Simpson <cs@cskk.id.au>",
"download_url": "https://files.pythonhosted.org/packages/0f/67/bdcc885816a55fe7f7bed2adde2518f9fb662f63a2962d0bd8500116a43f/cs_deco-20241206.tar.gz",
"platform": null,
"description": "Assorted function decorators.\n\n*Latest release 20241206*:\n@uses_cmd_options: use an object subclass if there is no cs.cmdutils so that we can add attributes to the placeholder options object.\n\n## <a name=\"ALL\"></a>`ALL(func)`\n\nInclude this function's name in its module's `__all__` list.\n\nExample:\n\n from cs.deco import ALL\n\n __all__ = []\n\n def obscure_function(...):\n ...\n\n @ALL\n def well_known_function(...):\n ...\n\n## <a name=\"cached\"></a>`cached(*a, **kw)`\n\nOBSOLETE cached\n\nFormer name for @cachedmethod.\n\n## <a name=\"cachedmethod\"></a>`cachedmethod(*da, **dkw)`\n\nDecorator to cache the result of an instance or class method\nand keep a revision counter for changes.\n\nThe cached values are stored on the instance (`self`).\nThe revision counter supports the `@revised` decorator.\n\nThis decorator may be used in 2 modes.\nDirectly:\n\n @cachedmethod\n def method(self, ...)\n\nor indirectly:\n\n @cachedmethod(poll_delay=0.25)\n def method(self, ...)\n\nOptional keyword arguments:\n* `attr_name`: the basis name for the supporting attributes.\n Default: the name of the method.\n* `poll_delay`: minimum time between polls; after the first\n access, subsequent accesses before the `poll_delay` has elapsed\n will return the cached value.\n Default: `None`, meaning the value never becomes stale.\n* `sig_func`: a signature function, which should be significantly\n cheaper than the method. If the signature is unchanged, the\n cached value will be returned. The signature function\n expects the instance (`self`) as its first parameter.\n Default: `None`, meaning no signature function;\n the first computed value will be kept and never updated.\n* `unset_value`: the value to return before the method has been\n called successfully.\n Default: `None`.\n\nIf the method raises an exception, this will be logged and\nthe method will return the previously cached value,\nunless there is not yet a cached value\nin which case the exception will be reraised.\n\nIf the signature function raises an exception\nthen a log message is issued and the signature is considered unchanged.\n\nAn example use of this decorator might be to keep a \"live\"\nconfiguration data structure, parsed from a configuration\nfile which might be modified after the program starts. One\nmight provide a signature function which called `os.stat()` on\nthe file to check for changes before invoking a full read and\nparse of the file.\n\n*Note*: use of this decorator requires the `cs.pfx` module.\n\n## <a name=\"contextdecorator\"></a>`contextdecorator(*da, **dkw)`\n\nA decorator for a context manager function `cmgrfunc`\nwhich turns it into a decorator for other functions.\n\nThis supports easy implementation of \"setup\" and \"teardown\"\ncode around other functions without the tedium of defining\nthe wrapper function itself. See the examples below.\n\nThe resulting context manager accepts an optional keyword\nparameter `provide_context`, default `False`. If true, the\ncontext returned from the context manager is provided as the\nfirst argument to the call to the wrapped function.\n\nNote that the context manager function `cmgrfunc`\nhas _not_ yet been wrapped with `@contextmanager`,\nthat is done by `@contextdecorator`.\n\nThis decorator supports both normal functions and generator functions.\n\nWith a normal function the process is:\n* call the context manager with `(func,a,kw,*da,**dkw)`,\n returning `ctxt`,\n where `da` and `dkw` are the positional and keyword parameters\n supplied when the decorator was defined.\n* within the context\n return the value of `func(ctxt,*a,**kw)` if `provide_context` is true\n or the value of `func(*a,**kw)` if not (the default)\n\nWith a generator function the process is:\n* obtain an iterator by calling `func(*a,**kw)`\n* for iterate over the iterator, yielding its results,\n by calling the context manager with `(func,a,kw,**da,**dkw)`,\n around each `next()`\nNote that it is an error to provide a true value for `provide_context`\nif the decorated function is a generator function.\n\nSome examples follow.\n\nTrace the call and return of a specific function:\n\n @contextdecorator\n def tracecall(func, a, kw):\n \"\"\" Trace the call and return from some function.\n This can easily be adapted to purposes such as timing a\n function call or logging use.\n \"\"\"\n print(\"call %s(*%r,**%r)\" % (func, a, kw))\n try:\n yield\n except Exception as e:\n print(\"exception from %s(*%r,**%r): %s\" % (func, a, kw, e))\n raise\n else:\n print(\"return from %s(*%r,**%r)\" % (func, a, kw))\n\n @tracecall\n def f():\n \"\"\" Some function to trace.\n \"\"\"\n\n @tracecall(provide_context=True):\n def f(ctxt, *a, **kw):\n \"\"\" A function expecting the context object as its first argument,\n ahead of whatever other arguments it would normally require.\n \"\"\"\n\nSee who is making use of a generator's values,\nwhen a generator might be invoked in one place and consumed elsewhere:\n\n from cs.py.stack import caller\n\n @contextdecorator\n def genuser(genfunc, *a, **kw):\n user = caller(-4)\n print(f\"iterate over {genfunc}(*{a!r},**{kw!r}) from {user}\")\n yield\n\n @genuser\n def linesof(filename):\n with open(filename) as f:\n yield from f\n\n # obtain a generator of lines here\n lines = linesof(__file__)\n\n # perhaps much later, or in another function\n for lineno, line in enumerate(lines, 1):\n print(\"line %d: %d words\" % (lineno, len(line.split())))\n\nTurn on \"verbose mode\" around a particular function:\n\n import sys\n import threading\n from cs.context import stackattrs\n\n class State(threading.local):\n def __init__(self):\n # verbose if stderr is on a terminal\n self.verbose = sys.stderr.isatty()\n\n # per thread global state\n state = State()\n\n @contextdecorator\n def verbose(func):\n with stackattrs(state, verbose=True) as old_attrs:\n if not old_attrs['verbose']:\n print(f\"enabled verbose={state.verbose} for function {func}\")\n # yield the previous verbosity as the context\n yield old_attrs['verbose']\n\n # turn on verbose mode\n @verbose\n def func(x, y):\n if state.verbose:\n # print if verbose\n print(\"x =\", x, \"y =\", y)\n\n # turn on verbose mode and also pass in the previous state\n # as the first argument\n @verbose(provide_context=True):\n def func2(old_verbose, x, y):\n if state.verbose:\n # print if verbose\n print(\"old_verbosity =\", old_verbose, \"x =\", x, \"y =\", y)\n\n## <a name=\"contextual\"></a>`contextual(func)`\n\nWrap a simple function as a context manager.\n\nThis was written to support users of `@strable`,\nwhich requires its `open_func` to return a context manager;\nthis turns an arbitrary function into a context manager.\n\nExample promoting a trivial function:\n\n >>> f = lambda: 3\n >>> cf = contextual(f)\n >>> with cf() as x: print(x)\n 3\n\n## <a name=\"decorator\"></a>`decorator(deco)`\n\nWrapper for decorator functions to support optional arguments.\n\nThe actual decorator function ends up being called as:\n\n mydeco(func, *da, **dkw)\n\nallowing `da` and `dkw` to affect the behaviour of the decorator `mydeco`.\n\nExamples:\n\n # define your decorator as if always called with func and args\n @decorator\n def mydeco(func, *da, arg2=None):\n ... decorate func subject to the values of da and arg2\n\n # mydeco called with defaults\n @mydeco\n def func1(...):\n ...\n\n @ mydeco called with nondefault arguments\n @mydeco('foo', arg2='bah')\n def func2(...):\n ...\n\n## <a name=\"default_params\"></a>`default_params(*da, **dkw)`\n\nDecorator to provide factory functions for default parameters.\n\nThis decorator accepts the following keyword parameters:\n* `_strict`: default `False`; if true only replace genuinely\n missing parameters; if false also replace the traditional\n `None` placeholder value\nThe remaining keyword parameters are factory functions\nproviding the respective default values.\n\nAtypical one off direct use:\n\n @default_params(dbconn=open_default_dbconn,debug=lambda:settings.DB_DEBUG_MODE)\n def dbquery(query, *, dbconn):\n dbconn.query(query)\n\nTypical use as a decorator factory:\n\n # in your support module\n uses_ds3 = default_params(ds3client=get_ds3client)\n\n # calling code which needs a ds3client\n @uses_ds3\n def do_something(.., *, ds3client, ...):\n ... make queries using ds3client ...\n\nThis replaces the standard boilerplate and avoids replicating\nknowledge of the default factory as exhibited in this legacy code:\n\n def do_something(.., *, ds3client=None, ...):\n if ds3client is None:\n ds3client = get_ds3client()\n ... make queries using ds3client ...\n\n## <a name=\"fmtdoc\"></a>`fmtdoc(func)`\n\nDecorator to replace a function's docstring with that string\nformatted against the function's module `__dict__`.\n\nThis supports simple formatted docstrings:\n\n ENVVAR_NAME = 'FUNC_DEFAULT'\n\n @fmtdoc\n def func():\n \"\"\"Do something with os.environ[{ENVVAR_NAME}].\"\"\"\n print(os.environ[ENVVAR_NAME])\n\nThis gives `func` this docstring:\n\n Do something with os.environ[FUNC_DEFAULT].\n\n*Warning*: this decorator is intended for wiring \"constants\"\ninto docstrings, not for dynamic values. Use for other types\nof values should be considered with trepidation.\n\n## <a name=\"logging_wrapper\"></a>`logging_wrapper(*da, **dkw)`\n\nDecorator for logging call shims\nwhich bumps the `stacklevel` keyword argument so that the logging system\nchooses the correct frame to cite in messages.\n\nNote: has no effect on Python < 3.8 because `stacklevel` only\nappeared in that version.\n\n## <a name=\"observable_class\"></a>`observable_class(property_names, only_unequal=False)`\n\nClass decorator to make various instance attributes observable.\n\nParameters:\n* `property_names`:\n an interable of instance property names to set up as\n observable properties. As a special case a single `str` can\n be supplied if only one attribute is to be observed.\n* `only_unequal`:\n only call the observers if the new property value is not\n equal to the previous proerty value. This requires property\n values to be comparable for inequality.\n Default: `False`, meaning that all updates will be reported.\n\n## <a name=\"OBSOLETE\"></a>`OBSOLETE(*da, **dkw)`\n\nA decorator for obsolete functions or classes.\n\nUse:\n\n @OBSOLETE\n def func(...):\n\nor\n\n @OBSOLETE(\"new_func_name\")\n def func(...):\n\nThis emits a warning log message before calling the decorated function.\nOnly one warning is emitted per calling location.\n\n## <a name=\"Promotable\"></a>Class `Promotable`\n\nA mixin class which supports the `@promote` decorator.\n\n*`Promotable.promote(obj, **from_t_kw)`*:\nPromote `obj` to an instance of `cls` or raise `TypeError`.\nThis method supports the `@promote` decorator.\n\nThis base method will call the `from_`*typename*`(obj,**from_t_kw)`\nclass factory method if present, where *typename* is\n`obj.__class__.__name__`.\n\nSubclasses may override this method to promote other types,\ntypically:\n\n @classmethod\n def promote(cls, obj):\n if isinstance(obj, cls):\n return obj\n ... various specific type promotions\n ... not done via a from_typename factory method\n # fall back to Promotable.promote\n return super().promote(obj)\n\nAn typical `from_`*typename*` factory method:\n\n class Foo(Promotable):\n\n def __init__(self, dbkey, dbrow):\n self.key = dbkey\n self.row_data = row\n\n @classmethod\n def from_str(cls, s : str):\n \"\"\"Accept a database key string, return a `Foo` instance.\"\"\"\n row = db_lookup(s)\n return cls(s, row)\n\nThis supports using `@promote` on functions with `Foo` instances:\n\n @promote\n def do_it(foo : Foo):\n ... work with foo ...\n\nbut calling it as:\n\n do_it(\"foo_key_value\")\n\n## <a name=\"promote\"></a>`promote(*da, **dkw)`\n\nA decorator to promote argument values automatically in annotated functions.\n\nIf the annotation is `Optional[some_type]` or `Union[some_type,None]`\nthen the promotion will be to `some_type` but a value of `None`\nwill be passed through unchanged.\n\nThe decorator accepts optional parameters:\n* `params`: if supplied, only parameters in this list will\n be promoted\n* `types`: if supplied, only types in this list will be\n considered for promotion\n\nFor any parameter with a type annotation, if that type has a\n`.promote(value)` class method and the function is called with a\nvalue not of the type of the annotation, the `.promote` method\nwill be called to promote the value to the expected type.\n\nNote that the `Promotable` mixin provides a `.promote()`\nmethod which promotes `obj` to the class if the class has a\nfactory class method `from_`*typename*`(obj)` where *typename*\nis `obj.__class__.__name__`.\nA common case for me is lexical objects which have a `from_str(str)`\nfactory to produce an instance from its textual form.\n\nAdditionally, if the `.promote(value)` class method raises a `TypeError`\nand `value` has a `.as_`*typename* attribute\n(where *typename* is the name of the type annotation),\nif that attribute is an instance method of `value`\nthen promotion will be attempted by calling `value.as_`*typename*`()`\notherwise the attribute will be used directly\non the presumption that it is a property.\n\nA typical `promote(cls, obj)` method looks like this:\n\n @classmethod\n def promote(cls, obj):\n if isinstance(obj, cls):\n return obj\n ... recognise various types ...\n ... and return a suitable instance of cls ...\n raise TypeError(\n \"%s.promote: cannot promote %s:%r\",\n cls.__name__, obj.__class__.__name__, obj)\n\nExample:\n\n >>> from cs.timeseries import Epoch\n >>> from typeguard import typechecked\n >>>\n >>> @promote\n ... @typechecked\n ... def f(data, epoch:Epoch=None):\n ... print(\"epoch =\", type(epoch), epoch)\n ...\n >>> f([1,2,3], epoch=12.0)\n epoch = <class 'cs.timeseries.Epoch'> Epoch(start=0, step=12)\n\nExample using a class with an `as_P` instance method:\n\n >>> class P:\n ... def __init__(self, x):\n ... self.x = x\n ... @classmethod\n ... def promote(cls, obj):\n ... raise TypeError(\"dummy promote method\")\n ...\n >>> class C:\n ... def __init__(self, n):\n ... self.n = n\n ... def as_P(self):\n ... return P(self.n + 1)\n ...\n >>> @promote\n ... def p(p: P):\n ... print(\"P =\", type(p), p.x)\n ...\n >>> c = C(1)\n >>> p(c)\n P = <class 'cs.deco.P'> 2\n\n*Note*: one issue with this is due to the conflict in name\nbetween this decorator and the method it looks for in a class.\nThe `promote` _method_ must appear after any methods in the\nclass which are decorated with `@promote`, otherwise the\n`promote` method supplants the name `promote` making it\nunavailable as the decorator.\nI usually just make `.promote` the last method.\n\nFailing example:\n\n class Foo:\n @classmethod\n def promote(cls, obj):\n ... return promoted obj ...\n @promote\n def method(self, param:Type, ...):\n ...\n\nWorking example:\n\n class Foo:\n @promote\n def method(self, param:Type, ...):\n ...\n # promote method as the final method of the class\n @classmethod\n def promote(cls, obj):\n ... return promoted obj ...\n\n## <a name=\"strable\"></a>`strable(*da, **dkw)`\n\nDecorator for functions which may accept a `str`\ninstead of their core type.\n\nParameters:\n* `func`: the function to decorate\n* `open_func`: the \"open\" factory to produce the core type\n if a string is provided;\n the default is the builtin \"open\" function.\n The returned value should be a context manager.\n Simpler functions can be decorated with `@contextual`\n to turn them into context managers if need be.\n\nThe usual (and default) example is a function to process an\nopen file, designed to be handed a file object but which may\nbe called with a filename. If the first argument is a `str`\nthen that file is opened and the function called with the\nopen file.\n\nExamples:\n\n @strable\n def count_lines(f):\n return len(line for line in f)\n\n class Recording:\n \"Class representing a video recording.\"\n ...\n @strable(open_func=Recording)\n def process_video(r):\n ... do stuff with `r` as a Recording instance ...\n\n*Note*: use of this decorator requires the `cs.pfx` module.\n\n## <a name=\"uses_cmd_options\"></a>`uses_cmd_options(*da, **dkw)`\n\nA decorator to provide default keyword arguments\nfrom the prevailing `cs.cmdutils.BaseCommandOptions`\nif available, otherwise from `option_defaults`.\n\nThis exists to provide plumbing free access to options set\nup by a command line invocation using `cs.cmdutils.BaseCommand`.\n\nIf no `option_defaults` are provided, a single `options`\nkeyword argument is provided which is the prevailing\n`BaseCommand.Options` instance.\n\nThe decorator accepts two optional \"private\" keyword arguments\ncommencing with underscores:\n* `_strict`: default `False`; if true then an `option_defaults`\n will only be applied if the argument is _missing_ from the\n function arguments, otherwise it will be applied if the\n argument is missing or `None`\n* `_options_param_name`: default `'options'`; this is the\n name of the single `options` keyword argument which will be\n supplied if there are no `option_defaults`\n\nExamples:\n\n @uses_cmd_options(doit=True, quiet=False)\n def func(x, *, doit, quiet, **kw):\n if not quiet:\n print(\"something\", x, kw)\n if doit:\n ... do the thing ...\n ... etc ...\n\n @uses_cmd_options()\n def func(x, *, options, **kw):\n if not options.quiet:\n print(\"something\", x, kw)\n if options.doit:\n ... do the thing ...\n ... etc ...\n\n## <a name=\"uses_doit\"></a>`uses_doit(func)`\n\n`func` is not supplied, collect the arguments supplied and return a\ndecorator which takes the subsequent callable and returns\n`deco(func, *da, **kw)`.\n\n## <a name=\"uses_force\"></a>`uses_force(func)`\n\n`func` is not supplied, collect the arguments supplied and return a\ndecorator which takes the subsequent callable and returns\n`deco(func, *da, **kw)`.\n\n## <a name=\"uses_quiet\"></a>`uses_quiet(func)`\n\n`func` is not supplied, collect the arguments supplied and return a\ndecorator which takes the subsequent callable and returns\n`deco(func, *da, **kw)`.\n\n## <a name=\"uses_verbose\"></a>`uses_verbose(func)`\n\n`func` is not supplied, collect the arguments supplied and return a\ndecorator which takes the subsequent callable and returns\n`deco(func, *da, **kw)`.\n\n# Release Log\n\n\n\n*Release 20241206*:\n@uses_cmd_options: use an object subclass if there is no cs.cmdutils so that we can add attributes to the placeholder options object.\n\n*Release 20241109*:\n* Add @uses_force to @uses_doit, @uses_quiet, @uses_verbose decorator set.\n* Fold @uses_cmd_option into @uses_cmd_options, provide \"options\" if no option_defaults.\n* @promote: accept optional keyword arguments, plumb to the from_typename factory method.\n\n*Release 20241007*:\n@uses_cmd_option: bugfix the infill of the defaults.\n\n*Release 20241003*:\nNew @uses_cmd_option and @uses_doit, @uses_quiet and @uses_verbose based on it.\n\n*Release 20240709*:\nBugfix @default_params: assemble the new signature with a complete set of parameters instead of only the defaulted ones.\n\n*Release 20240630*:\n* @default_params: apply modified signature parameters one at a time, as applying them in a single list seems to trigger some ordering checks.\n* @promote: pass the original Parameter through to the wrapper so that we can use .default if needed, have the wrapper test for None last, so that we can promote None to something with a .from_NoneType method, make the check for the default value more specific.\n\n*Release 20240412*:\n@decorator: apply the decorated function name to the metadecorator.\n\n*Release 20240326*:\ndefault_params: update wrapper signature to mark the defaulted params as optional.\n\n*Release 20240316*:\nFixed release upload artifacts.\n\n*Release 20240314.1*:\nNew release with corrected install path.\n\n*Release 20240314*:\nTiny doc update.\n\n*Release 20240303*:\nPromotable.promote: do not just handle str, handle anything with a from_*typename* factory method.\n\n*Release 20240211*:\nPromotable: no longer abstract, provide a default promote() method which tries cls.from_str for str.\n\n*Release 20231129*:\n@cachedmethod: ghastly hack for the revision attribute to accomodate objects which return None for missing attributes.\n\n*Release 20230331*:\n@promote: pass None through for Optional parameters.\n\n*Release 20230212*:\nNew Promotable abstract class mixin, which requires the creation of a .promote(cls,obj)->cls class method.\n\n*Release 20230210*:\n@promote: add support for .as_TypeName() instance method on the source object or a .as_TypeName property/attribut.\n\n*Release 20221214*:\n@decorator: use functools.update_wrapper to propagate the decorated function's attributes to the wrapper (still legacy code for Python prior to 3.2).\n\n*Release 20221207*:\nSmall updates.\n\n*Release 20221106.1*:\n@promote: support Optional[sometype] parameters.\n\n*Release 20221106*:\nNew @promote decorator to autopromote parameter values according to their type annotation.\n\n*Release 20220918.1*:\n@default_param: append parameter descriptions to the docstring of the decorated function, bugfix __name__ setting.\n\n*Release 20220918*:\n* @OBSOLETE: tweak message format.\n* @default_params: docstring: simplify the example and improve the explaination.\n* @default_params: set the __name__ of the wrapper function.\n\n*Release 20220905*:\n* New @ALL decorator to include a function in __all__.\n* New @default_params decorator for making decorators which provide default argument values from callables.\n\n*Release 20220805*:\n@OBSOLETE: small improvements.\n\n*Release 20220327*:\nSome fixes for @cachedmethod.\n\n*Release 20220311*:\n@cachedmethod: change the meaning of poll_delay=None to mean \"never goes stale\" as I had thought it already did.\n\n*Release 20220227*:\n@cachedmethod: more paranoid access to the revision attribute.\n\n*Release 20210823*:\n@decorator: preserve the __name__ of the wrapped function.\n\n*Release 20210123*:\nSyntax backport for older Pythons.\n\n*Release 20201202*:\n@decorator: tweak test for callable(da[0]) to accord with the docstring.\n\n*Release 20201025*:\nNew @contextdecorator decorator for context managers to turn them into setup/teardown decorators.\n\n*Release 20201020*:\n* @cachedmethod: bugfix cache logic.\n* @strable: support generator functions.\n\n*Release 20200725*:\nOverdue upgrade of @decorator to support combining the function and decorator args in one call.\n\n*Release 20200517.2*:\nMinor upgrade to @OBSOLETE.\n\n*Release 20200517.1*:\nTweak @OBSOLETE and @cached (obsolete name for @cachedmethod).\n\n*Release 20200517*:\nGet warning() from cs.gimmicks.\n\n*Release 20200417*:\n* @decorator: do not override __doc__ on the decorated function, just provide default.\n* New @logging_wrapper which bumps the `stacklevel` parameter in Python 3.8 and above so that shims recite the correct caller.\n\n*Release 20200318.1*:\nNew @OBSOLETE to issue a warning on a call to an obsolete function, like an improved @cs.logutils.OBSOLETE (which needs to retire).\n\n*Release 20200318*:\n@cachedmethod: tighten up the \"is the value changed\" try/except.\n\n*Release 20191012*:\n* New @contextual decorator to turn a simple function into a context manager.\n* @strable: mention context manager requirement and @contextual as workaround.\n\n*Release 20191006*:\nRename @cached to @cachedmethod, leave compatible @cached behind which issues a warning (will be removed in a future release).\n\n*Release 20191004*:\nAvoid circular import with cs.pfx by removing requirement and doing the import later if needed.\n\n*Release 20190905*:\nBugfix @deco: it turns out that you may not set the .__module__ attribute on a property object.\n\n*Release 20190830.2*:\nMake some getattr calls robust.\n\n*Release 20190830.1*:\n@decorator: set the __module__ of the wrapper.\n\n*Release 20190830*:\n@decorator: set the __module__ of the wrapper from the decorated target, aids cs.distinf.\n\n*Release 20190729*:\n@cached: sidestep uninitialised value.\n\n*Release 20190601.1*:\n@strable: fix the example in the docstring.\n\n*Release 20190601*:\n* Bugfix @decorator to correctly propagate the docstring of the subdecorator.\n* Improve other docstrings.\n\n*Release 20190526*:\n@decorator: add support for positional arguments and rewrite - simpler and clearer.\n\n*Release 20190512*:\n@fmtdoc: add caveat against misuse of this decorator.\n\n*Release 20190404*:\nNew @fmtdoc decorator to format a function's doctsring against its module's globals.\n\n*Release 20190403*:\n* @cached: bugfix: avoid using unset sig_func value on first pass.\n* @observable_class: further tweaks.\n\n*Release 20190322.1*:\n@observable_class: bugfix __init__ wrapper function.\n\n*Release 20190322*:\n* New class decorator @observable_class.\n* Bugfix import of \"warning\".\n\n*Release 20190309*:\n@cached: improve the exception handling.\n\n*Release 20190307.2*:\nFix docstring typo.\n\n*Release 20190307.1*:\nBugfix @decorator: final plumbing step for decorated decorator.\n\n*Release 20190307*:\n* @decorator: drop unused arguments, they get used by the returned decorator.\n* Rework the @cached logic.\n\n*Release 20190220*:\n* Bugfix @decorator decorator, do not decorate twice.\n* Have a cut at inheriting the decorated function's docstring.\n\n*Release 20181227*:\n* New decoartor @strable for function which may accept a str instead of their primary type.\n* Improvements to @cached.\n\n*Release 20171231*:\nInitial PyPI release.\n",
"bugtrack_url": null,
"license": "GNU General Public License v3 or later (GPLv3+)",
"summary": "Assorted function decorators.",
"version": "20241206",
"project_urls": {
"MonoRepo Commits": "https://bitbucket.org/cameron_simpson/css/commits/branch/main",
"Monorepo Git Mirror": "https://github.com/cameron-simpson/css",
"Monorepo Hg/Mercurial Mirror": "https://hg.sr.ht/~cameron-simpson/css",
"Source": "https://github.com/cameron-simpson/css/blob/main/lib/python/cs/deco.py"
},
"split_keywords": [
"python2",
" python3"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "6f1ad25877aa13fa56e08986e2d5bee6803bf02cfa4ac7c4bf5e0ae3eaac49da",
"md5": "171e37d6c9316abdcd7af844103a7add",
"sha256": "6780d29bdb83d717562442459af09247658d14a3ff9d2cc5f61ff167455eb661"
},
"downloads": -1,
"filename": "cs_deco-20241206-py3-none-any.whl",
"has_sig": false,
"md5_digest": "171e37d6c9316abdcd7af844103a7add",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 21310,
"upload_time": "2024-12-06T04:16:25",
"upload_time_iso_8601": "2024-12-06T04:16:25.021478Z",
"url": "https://files.pythonhosted.org/packages/6f/1a/d25877aa13fa56e08986e2d5bee6803bf02cfa4ac7c4bf5e0ae3eaac49da/cs_deco-20241206-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "0f67bdcc885816a55fe7f7bed2adde2518f9fb662f63a2962d0bd8500116a43f",
"md5": "90a4e926f48bfaa59be40fa258a19bed",
"sha256": "d04ca21c00efcfafcfa69505dd6930c83830600ab239e63d8785661c6b4e4e62"
},
"downloads": -1,
"filename": "cs_deco-20241206.tar.gz",
"has_sig": false,
"md5_digest": "90a4e926f48bfaa59be40fa258a19bed",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 35928,
"upload_time": "2024-12-06T04:16:27",
"upload_time_iso_8601": "2024-12-06T04:16:27.271530Z",
"url": "https://files.pythonhosted.org/packages/0f/67/bdcc885816a55fe7f7bed2adde2518f9fb662f63a2962d0bd8500116a43f/cs_deco-20241206.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-06 04:16:27",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "cameron-simpson",
"github_project": "css",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "cs-deco"
}