generated from pyiron/pyiron_module_template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Give an example for each tool in the README
- Loading branch information
Showing
1 changed file
with
237 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,241 @@ | ||
# snippets | ||
|
||
This is a collection of independent python snippets used in the pyiron project, but hopefully useful elsewhere too. | ||
This is a collection of independent python snippets which we in the pyiron project find generically useful. | ||
|
||
To qualify for inclusion, a snippet must not have any dependencies outside the python standard library, and should fit reasonably inside a single file. | ||
|
||
(Note that the _tests_ may have non-standard dependencies, e.g. to ensure the snippets work in various edge cases we care about, but the actual snippets themselves must be able to behave well in a clean environment.) | ||
|
||
|
||
# Summary | ||
|
||
The snippets may have more functionality that this -- taking a look at the test suite is the best way to get an exhaustive sense of their functionality -- but these examples will give you the gist of things. | ||
|
||
## Colors | ||
|
||
Just a shortcut to the `seaborn.color_palette()` of colors in hex: | ||
|
||
```python | ||
>>> from snippets.colors import SeabornColors | ||
>>> SeabornColors.white | ||
'#ffffff' | ||
|
||
``` | ||
|
||
## Deprecate | ||
|
||
Easily indicate that some functionality is being deprecated | ||
|
||
```python | ||
>>> from snippets.deprecate import deprecate | ||
>>> | ||
>>> @deprecate(message="Use `bar(a, b)` instead", version="0.5.0") | ||
... def foo(a, b): | ||
... pass | ||
>>> foo(1, 2) | ||
|
||
``` | ||
|
||
Raises a warning like `DeprecationWarning: __main__.foo is deprecated: Use bar(a, b) instead. It is not guaranteed to be in service in vers. 0.5.0 foo(1, 2)` | ||
|
||
|
||
## DotDict | ||
|
||
A dictionary that allows dot-access. Has `.items()` etc. | ||
|
||
```python | ||
>>> from snippets.dotdict import DotDict | ||
>>> | ||
>>> d = DotDict({"a": 1}) | ||
>>> d.b = 2 | ||
>>> print(d.a, d.b) | ||
1 2 | ||
|
||
``` | ||
|
||
## Factory | ||
|
||
Make dynamic classes that are still pickle-able | ||
|
||
```python | ||
>>> from abc import ABC | ||
>>> import pickle | ||
>>> | ||
>>> from snippets.factory import classfactory | ||
>>> | ||
>>> class HasN(ABC): | ||
... '''Some class I want to make dynamically subclass.''' | ||
... def __init_subclass__(cls, /, n=0, s="foo", **kwargs): | ||
... super(HasN, cls).__init_subclass__(**kwargs) | ||
... cls.n = n | ||
... cls.s = s | ||
... | ||
... def __init__(self, x, y=0): | ||
... self.x = x | ||
... self.y = y | ||
>>> | ||
>>> @classfactory | ||
... def has_n_factory(n, s="wrapped_function", /): | ||
... return ( | ||
... f"{HasN.__name__}{n}{s}", # New class name | ||
... (HasN,), # Base class(es) | ||
... {}, # Class attributes dictionary | ||
... {"n": n, "s": s} | ||
... # dict of `builtins.type` kwargs (passed to `__init_subclass__`) | ||
... ) | ||
>>> Has2 = has_n_factory(2, "my_dynamic_class") | ||
>>> | ||
>>> foo = Has2(42, y=-1) | ||
>>> print(foo.n, foo.s, foo.x, foo.y) | ||
2 my_dynamic_class 42 -1 | ||
|
||
>>> reloaded = pickle.loads(pickle.dumps(foo)) # doctest: +SKIP | ||
>>> print(reloaded.n, reloaded.s, reloaded.x, reloaded.y) # doctest: +SKIP | ||
2 my_dynamic_class 42 -1 # doctest: +SKIP | ||
|
||
``` | ||
|
||
(Pickle doesn't play well with testing the docs -- you can't run `pickle.dumps(pickle.loads(5))` either!) | ||
|
||
|
||
## Files | ||
|
||
Shortcuts for filesystem manipulation | ||
|
||
```python | ||
>>> from snippets.files import DirectoryObject, FileObject | ||
>>> | ||
>>> d = DirectoryObject("some_dir") | ||
>>> d.write(file_name="my_filename.txt", content="Some content") | ||
>>> f = FileObject("my_filename.txt", directory=d) | ||
>>> f.is_file() | ||
True | ||
>>> f2 = f.copy("new_filename.txt", directory=d.create_subdirectory("sub")) | ||
>>> f2.read() | ||
'Some content' | ||
>>> d.file_exists("sub/new_filename.txt") | ||
True | ||
>>> d.delete() | ||
|
||
``` | ||
|
||
|
||
## Has post | ||
|
||
A meta-class introducing a `__post__` dunder which runs after the `__init__` of _everything_ in the MRO. | ||
|
||
```python | ||
>>> from snippets.has_post import HasPost | ||
>>> | ||
>>> class Foo(metaclass=HasPost): | ||
... def __init__(self, x=0): | ||
... self.x = x | ||
... print(f"Foo.__init__: x = {self.x}") | ||
>>> | ||
>>> class Bar(Foo): | ||
... def __init__(self, x=0, post_extra=2): | ||
... super().__init__(x) | ||
... self.x += 1 | ||
... print(f"Bar.__init__: x = {self.x}") | ||
... | ||
... def __post__(self, *args, post_extra=2, **kwargs): | ||
... self.x += post_extra | ||
... | ||
... print(f"Bar.__post__: x = {self.x}") | ||
>>> | ||
>>> Bar().x | ||
Foo.__init__: x = 0 | ||
Bar.__init__: x = 1 | ||
Bar.__post__: x = 3 | ||
3 | ||
|
||
``` | ||
|
||
Honestly, try thinking if there's another way to solve your problem; this is a dark magic. | ||
|
||
## Import alarm | ||
|
||
Fail gracefully when optional dependencies are missing for (optional) functionality. | ||
|
||
```python | ||
>>> from snippets.import_alarm import ImportAlarm | ||
>>> | ||
>>> with ImportAlarm( | ||
... "Some functionality unavailable: `magic` dependency missing" | ||
... ) as my_magic_alarm: | ||
... import magic | ||
>>> | ||
>>> with ImportAlarm("This warning won't show up") as datetime_alarm: | ||
... import datetime | ||
>>> | ||
>>> class Foo: | ||
... @my_magic_alarm | ||
... @datetime_alarm | ||
... def __init__(self, x): | ||
... self.x = x | ||
... | ||
... @property | ||
... def magical(self): | ||
... return magic.method(self.x) | ||
... | ||
... def a_space_odyssey(self): | ||
... print(datetime.date(2001, 1, 1)) | ||
... | ||
>>> | ||
>>> foo = Foo(0) | ||
>>> # Raises a warning re `magic` (since that does not exist) | ||
>>> # but not re `datetime` (since it does and we certainly have it) | ||
>>> foo.a_space_odyssey() | ||
2001-01-01 | ||
|
||
>>> try: | ||
... foo.magical(0) | ||
... except NameError as e: | ||
... print("ERROR:", e) | ||
ERROR: name 'magic' is not defined | ||
|
||
``` | ||
|
||
## Logger | ||
|
||
Configures the logger and writes to `pyiron.log` | ||
|
||
## Retry | ||
|
||
If at first you don't succeed | ||
|
||
```python | ||
>>> from time import time | ||
>>> | ||
>>> from snippets.retry import retry | ||
>>> | ||
>>> def at_most_three_seconds(): | ||
... t = int(time()) | ||
... if t % 3 != 0: | ||
... raise ValueError("Not yet!") | ||
... return t | ||
>>> | ||
>>> retry(at_most_three_seconds, msg="Tried and failed...", error=ValueError) % 3 | ||
0 | ||
|
||
``` | ||
|
||
Depending on the system clock at invokation, this simple example may give warnings like `UserWarning: Tried and failed... Trying again in 1.0s. Tried 1 times so far...` up to two times. | ||
|
||
|
||
## Singleton | ||
|
||
A metaclass for the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern). | ||
|
||
```python | ||
>>> from snippets.singleton import Singleton | ||
>>> | ||
>>> class Foo(metaclass=Singleton): | ||
... pass | ||
>>> | ||
>>> foo1 = Foo() | ||
>>> foo2 = Foo() | ||
>>> foo1 is foo2 | ||
True | ||
|
||
``` |