# Lully
A small library providing some helpers for python devs.
See the [tests](test/) for usage examples and ideas.
## Funcmore
More tools for functions, notably `lully.ẍ`, that create a function out of two :
import lully as ll
ll.ẍ(print, input)('$ ')
ll.ẍ(''.join, lambda v: v.split())('h e l l o') # --> 'hello'
And `lully.ÿ` that do the same thing, but splat the output in next function call :
ll.ÿ(print, '|'.join)('hello') # --> 'h|e|l|l|o'
## Collections
### Otom
The One-To-One-Mapping is a special kind of dict, where all values are also keys, associated to their key.
from lully import Otom
O = Otom({
'one': 1,
'two': 2,
'tee': 3,
})
assert O[1] == 'one'
assert O['one'] == 1
An Otom acts like a dict. See [tests](test/test_collections.py) for more.
### Transformations
## Date utils
Many functions, like `time_since`, `time_from_now`, `pretty_seconds`, `parse_date`, `now_plus`, `add_time`, `parse_isoformat_with_microseconds` may help dealing with (un)aware datetimes, relative times, and various date format.
## Human-readable hashing
You may get a string like *Pieuvre Minérale* out of any python object with `lully.human_code`.
## Confiseur
A Confiseur is here to help with configurations.
The principle is to subclass, for each configuration kind you need, the Confiseur base class and populate it with Bonbon instances, describing options.
from lully import Confiseur, Bonbon
class MyConfig(Confiseur):
def bonbons(self) -> [Bonbon]:
return (
Bonbon('server options', 'max instances', default=3),
)
def validate(self, cfg):
if cfg['server options']['max instances'] > 10:
self.add_error(f"Can't handle more than ten instances. Provided: {cfg['server options']['max instances']}.")
myconfig = MyConfig('{"server options": { "max instances": 11 }}')
assert myconfig['server options']['max instances'] == 11
assert myconfig.has_error
assert len(myconfig.errors)
See [tests](test/test_confiseur.py) for more.
## Itermore
More itertools functions !
from lully.itermore import window, grouper, flatten, dotproduct, ncycles
See the [source file](lully/itermore.py) for the full set of functions.
## Kotlin-inspired functions
When coding in other languages, you get ideas. Here are the ones i got after a 1h course of Kotlin.
from lully import first, last, zip_with_next
assert first([1, 2]) == 1
assert last([1, 2]) == 2
assert first([2, 3], lambda x: x % 2) == 3
assert tuple(zip_with_next('abc')) == (('a', 'b'), ('c', None))
See the [source file](lully/kotlin.py) for the full set of functions.
## Fief
This name should recall both its goal, which is *FIltering of EFfective paramaters*, and the fact it keeps functions to work within their [fief](https://en.wikipedia.org/wiki/Fief).
You have this function:
def func(a, b):
... # some implementation
and you have its parameter stored in a dict, with other keys that are not for that specific function:
config = {'a': 1, 'b': 'doe', 'loglevel': 'WARNING'}
Thus, you can't just do that:
func(**config)
Because of the expected:
TypeError: func() got an unexpected keyword argument 'loglevel'
One solution can be to filter that dict, but that's cumbersome and needs maintainance. And that's worse if you have a lot of functions to call that way.
Fief is a decorator that will make that for you, using `inspect` module.
from lully import fief
@fief
def func(a, b):
return a + b
config = {'a': 2, 'b': 3, 'loglevel': 'WARNING'}
# and suddenly, you can provide anything in keywords argument:
assert func(**config) == 5 # no TypeError, that's magic !
## random
### lsample
This is a function answering to the *n choose k* problem using the Vitter's algorithm.
The problem is to choose randomly n element in a set of k. That's usually done with the `random.sample(n, [1, 2, ...])` function. Hence the *sampling* part of `lsample` name.
However, that stdlib function will load everything in memory, forcing you to provide a list, not a generator.
Vitter's solution is to collect the `n` elements during a single pass over the list, making possible to work on generators,
as long as you have an idea of their size, hence not loading all data in memory.
from lully import lsample
print(lsample(3, [x for x in range(10)], it_size=10))
This enables you to pick 100 random tweets among the entire tweeter database without having to load it in memory.
Provided you have enough time for the full browsing to be performed.
See [that repo](https://github.com/aluriak/linear_choosens) for more information, sources and benchmarks.
## Logging
The generic `lully.logger_from(o)` will help you getting a logger, whatever `o` is. By default, it will be a logger using print statements written in stderr,
but you can also provide a configured logger.
Raw data
{
"_id": null,
"home_page": "https://github.com/aluriak/lully",
"name": "lully",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "utils, library, helpers",
"author": "Lucas Bourneuf",
"author_email": "lucas@bourneuf.net",
"download_url": "https://files.pythonhosted.org/packages/cf/e0/77b3cd7e92aba5288084512c3d6c83c0831dd64785cf2d7ae713999ce93d/lully-2.2.0.tar.gz",
"platform": null,
"description": "# Lully\n A small library providing some helpers for python devs.\n \n See the [tests](test/) for usage examples and ideas.\n \n ## Funcmore\n More tools for functions, notably `lully.\u1e8d`, that create a function out of two :\n \n import lully as ll\n ll.\u1e8d(print, input)('$ ')\n ll.\u1e8d(''.join, lambda v: v.split())('h e l l o') # --> 'hello'\n \n And `lully.\u00ff` that do the same thing, but splat the output in next function call :\n \n ll.\u00ff(print, '|'.join)('hello') # --> 'h|e|l|l|o'\n \n ## Collections\n \n ### Otom\n The One-To-One-Mapping is a special kind of dict, where all values are also keys, associated to their key.\n \n \n from lully import Otom\n \n O = Otom({\n 'one': 1,\n 'two': 2,\n 'tee': 3,\n })\n assert O[1] == 'one'\n assert O['one'] == 1\n \n An Otom acts like a dict. See [tests](test/test_collections.py) for more.\n \n \n ### Transformations\n \n ## Date utils\n Many functions, like `time_since`, `time_from_now`, `pretty_seconds`, `parse_date`, `now_plus`, `add_time`, `parse_isoformat_with_microseconds` may help dealing with (un)aware datetimes, relative times, and various date format.\n \n ## Human-readable hashing\n You may get a string like *Pieuvre Min\u00e9rale* out of any python object with `lully.human_code`.\n \n ## Confiseur\n A Confiseur is here to help with configurations.\n \n The principle is to subclass, for each configuration kind you need, the Confiseur base class and populate it with Bonbon instances, describing options.\n \n from lully import Confiseur, Bonbon\n \n class MyConfig(Confiseur):\n \n def bonbons(self) -> [Bonbon]:\n return (\n Bonbon('server options', 'max instances', default=3),\n )\n \n def validate(self, cfg):\n if cfg['server options']['max instances'] > 10:\n self.add_error(f\"Can't handle more than ten instances. Provided: {cfg['server options']['max instances']}.\")\n \n myconfig = MyConfig('{\"server options\": { \"max instances\": 11 }}')\n assert myconfig['server options']['max instances'] == 11\n assert myconfig.has_error\n assert len(myconfig.errors)\n \n See [tests](test/test_confiseur.py) for more.\n \n \n ## Itermore\n More itertools functions !\n \n from lully.itermore import window, grouper, flatten, dotproduct, ncycles\n \n See the [source file](lully/itermore.py) for the full set of functions.\n \n \n ## Kotlin-inspired functions\n When coding in other languages, you get ideas. Here are the ones i got after a 1h course of Kotlin.\n \n from lully import first, last, zip_with_next\n \n assert first([1, 2]) == 1\n assert last([1, 2]) == 2\n assert first([2, 3], lambda x: x % 2) == 3\n assert tuple(zip_with_next('abc')) == (('a', 'b'), ('c', None))\n \n See the [source file](lully/kotlin.py) for the full set of functions.\n \n \n ## Fief\n This name should recall both its goal, which is *FIltering of EFfective paramaters*, and the fact it keeps functions to work within their [fief](https://en.wikipedia.org/wiki/Fief).\n \n You have this function:\n \n def func(a, b):\n ... # some implementation\n \n and you have its parameter stored in a dict, with other keys that are not for that specific function:\n \n config = {'a': 1, 'b': 'doe', 'loglevel': 'WARNING'}\n \n Thus, you can't just do that:\n \n func(**config)\n \n Because of the expected:\n \n TypeError: func() got an unexpected keyword argument 'loglevel'\n \n One solution can be to filter that dict, but that's cumbersome and needs maintainance. And that's worse if you have a lot of functions to call that way.\n \n Fief is a decorator that will make that for you, using `inspect` module.\n \n \n from lully import fief\n \n @fief\n def func(a, b):\n return a + b\n \n config = {'a': 2, 'b': 3, 'loglevel': 'WARNING'}\n \n # and suddenly, you can provide anything in keywords argument:\n assert func(**config) == 5 # no TypeError, that's magic !\n \n \n ## random\n ### lsample\n This is a function answering to the *n choose k* problem using the Vitter's algorithm.\n \n The problem is to choose randomly n element in a set of k. That's usually done with the `random.sample(n, [1, 2, ...])` function. Hence the *sampling* part of `lsample` name.\n However, that stdlib function will load everything in memory, forcing you to provide a list, not a generator.\n \n Vitter's solution is to collect the `n` elements during a single pass over the list, making possible to work on generators,\n as long as you have an idea of their size, hence not loading all data in memory.\n \n from lully import lsample\n print(lsample(3, [x for x in range(10)], it_size=10))\n \n This enables you to pick 100 random tweets among the entire tweeter database without having to load it in memory.\n Provided you have enough time for the full browsing to be performed.\n \n See [that repo](https://github.com/aluriak/linear_choosens) for more information, sources and benchmarks.\n \n \n ## Logging\n The generic `lully.logger_from(o)` will help you getting a logger, whatever `o` is. By default, it will be a logger using print statements written in stderr,\n but you can also provide a configured logger.\n ",
"bugtrack_url": null,
"license": "GPLv3",
"summary": "Helpers and utils",
"version": "2.2.0",
"project_urls": {
"Homepage": "https://github.com/aluriak/lully"
},
"split_keywords": [
"utils",
" library",
" helpers"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "cfe077b3cd7e92aba5288084512c3d6c83c0831dd64785cf2d7ae713999ce93d",
"md5": "8e0bfcd5dd869f6966a4562c5566f832",
"sha256": "4842430f1401d7468088974ac8bd1aca7ec4919b5d1016cb52d4bf27981f0889"
},
"downloads": -1,
"filename": "lully-2.2.0.tar.gz",
"has_sig": false,
"md5_digest": "8e0bfcd5dd869f6966a4562c5566f832",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 64294,
"upload_time": "2025-08-21T14:39:16",
"upload_time_iso_8601": "2025-08-21T14:39:16.495726Z",
"url": "https://files.pythonhosted.org/packages/cf/e0/77b3cd7e92aba5288084512c3d6c83c0831dd64785cf2d7ae713999ce93d/lully-2.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-21 14:39:16",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "aluriak",
"github_project": "lully",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "lully"
}