Skip to content

Commit 0976a48

Browse files
committed
Basic project and docs
1 parent 6dee5ed commit 0976a48

20 files changed

+2278
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,7 @@ cython_debug/
158158
# and can be added to the global gitignore or merged into this file. For a more nuclear
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161+
162+
163+
164+
**test.py**

README.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=================
2+
TkClassWizard
3+
=================
4+
5+
Library for graphically defining objects based on class annotations.
6+
Works with Tkinter / TTKBootstrap
7+
8+
Example
9+
============
10+
11+
.. code-block:: python
12+
13+
import tkclasswiz as wiz
14+
15+
16+
class Wheel:
17+
def __init__(self, diameter: float):
18+
self.diameter = diameter
19+
20+
class Car:
21+
def __init__(self, name: str, speed: float, wheels: list[Wheel]):
22+
self.name = name
23+
self.speed = speed
24+
self.wheels = wheels
25+
26+
27+
combo = wiz.ComboBoxObjects()
28+
window = ObjectEditWindow()
29+
window.open_object_edit_frame(Car, combo)

pyproject.toml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[build-system]
2+
requires = [
3+
"wheel",
4+
]
5+
6+
build-backend = "setuptools.build_meta"
7+
8+
9+
[project]
10+
name = "TkClassWiz"
11+
authors = [
12+
{name = "David Hozic"}
13+
]
14+
description = "Library for graphically defining objects based on class annotations. Works with Tkinter / TTKBootstrap"
15+
readme = "README.rst"
16+
requires-python = ">=3.8"
17+
keywords = ["Tkinter", "TTKBootstrap", "Python3", "ObjectDef", "ClassWiz", "ClassWiz", "GUI"]
18+
classifiers = [
19+
"Programming Language :: Python :: 3",
20+
"License :: OSI Approved :: MIT License",
21+
"Operating System :: OS Independent",
22+
]
23+
24+
dynamic = ["optional-dependencies", "version"]
25+
26+
[tool.setuptools]
27+
include-package-data = true
28+
29+
[tool.setuptools.dynamic]
30+
version = {attr = "tkclasswiz.__version__"}
31+
32+
[tool.setuptools.dynamic.optional-dependencies]
33+
docs = {file = "requirements/docs.txt"}
34+
35+
tool.setuptools.packages = [
36+
"tkclasswiz"
37+
]

requirements/docs.txt

Whitespace-only changes.

tkclasswiz/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""
2+
TkClassWizard - Library for graphically defining objects based on class annotations.
3+
Works with Tkinter / TTKBootstrap.
4+
"""
5+
6+
__version__ = "1.0.0"
7+
8+
from .object_frame import *
9+
from .annotations import *
10+
from .cache import *
11+
from .convert import *
12+
from .extensions import *
13+
from .messagebox import *
14+
from .storage import *
15+
from .utilities import *

tkclasswiz/annotations.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Module used for managing annotations.
3+
"""
4+
from datetime import datetime, timedelta, timezone
5+
from typing import Union, Optional
6+
from contextlib import suppress
7+
from inspect import isclass
8+
9+
10+
__all__ = (
11+
"register_annotations",
12+
"get_annotations",
13+
)
14+
15+
16+
ADDITIONAL_ANNOTATIONS = {
17+
timedelta: {
18+
"days": float,
19+
"seconds": float,
20+
"microseconds": float,
21+
"milliseconds": float,
22+
"minutes": float,
23+
"hours": float,
24+
"weeks": float
25+
},
26+
datetime: {
27+
"year": int,
28+
"month": Union[int, None],
29+
"day": Union[int, None],
30+
"hour": int,
31+
"minute": int,
32+
"second": int,
33+
"microsecond": int,
34+
"tzinfo": timezone
35+
},
36+
timezone: {
37+
"offset": timedelta,
38+
"name": str
39+
},
40+
}
41+
42+
43+
def register_annotations(cls: type, mapping: Optional[dict] = {}, **annotations):
44+
"""
45+
Extends original annotations of ``cls``.
46+
47+
This can be useful eg. when the class your
48+
are adding is not part of your code and is also not annotated.
49+
50+
Classes that already have additional annotations:
51+
52+
- datetime.timedelta
53+
- datetime.datetime
54+
- datetime.timezone
55+
56+
Parameters
57+
------------
58+
cls: type
59+
The class (or function) to register additional annotations on.
60+
mapping: Optional[Dict[str, type]]
61+
Mapping mapping the parameter name to it's type.
62+
annotations: Optional[Unpack[str, type]]
63+
Keyword arguments mapping parameter name to it's type (``name=type``).
64+
65+
Example
66+
-----------
67+
68+
.. code-block:: python
69+
70+
from datetime import timedelta, timezone
71+
72+
register_annotations(
73+
timezone,
74+
offset=timedelta,
75+
name=str
76+
)
77+
"""
78+
if cls not in ADDITIONAL_ANNOTATIONS:
79+
ADDITIONAL_ANNOTATIONS[cls] = {}
80+
81+
ADDITIONAL_ANNOTATIONS[cls].update(**annotations, **mapping)
82+
83+
84+
def get_annotations(class_) -> dict:
85+
"""
86+
Returns class / function annotations including the ones extended with ``register_annotations``.
87+
It does not return the return annotation.
88+
"""
89+
annotations = {}
90+
with suppress(AttributeError):
91+
if isclass(class_):
92+
annotations = class_.__init__.__annotations__
93+
else:
94+
annotations = class_.__annotations__
95+
96+
additional_annotations = ADDITIONAL_ANNOTATIONS.get(class_, {})
97+
annotations = {**annotations, **additional_annotations}
98+
99+
if "return" in annotations:
100+
del annotations["return"]
101+
102+
return annotations

tkclasswiz/cache.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
Utility module used for caching.
3+
"""
4+
from typing import Callable
5+
from functools import wraps
6+
import pickle
7+
8+
9+
__all__ = (
10+
"cache_result",
11+
)
12+
13+
14+
# Caching
15+
def cache_result(max: int = 256):
16+
"""
17+
Caching function that also allows dictionary and lists to be used as a key.
18+
19+
Parameters
20+
--------------
21+
max: int
22+
The maximum number of items inside the cache.
23+
"""
24+
def _decorator(fnc: Callable):
25+
cache_dict = {}
26+
27+
@wraps(fnc)
28+
def wrapper(*args, **kwargs):
29+
try:
30+
# Convert to pickle string to allow hashing of non-hashables (dictionaries, lists, ...)
31+
key = pickle.dumps((*args, kwargs))
32+
except Exception:
33+
return fnc(*args, **kwargs)
34+
35+
result = cache_dict.get(key, Ellipsis)
36+
if result is not Ellipsis:
37+
return result
38+
39+
result = fnc(*args, **kwargs)
40+
cache_dict[key] = result
41+
42+
if len(cache_dict) > max:
43+
items = list(cache_dict.items())[:max // 2]
44+
cache_dict.clear()
45+
cache_dict.update(items)
46+
47+
return result
48+
49+
return wrapper
50+
51+
return _decorator

0 commit comments

Comments
 (0)