classicist 1.0.3__py3-none-any.whl → 1.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
classicist/__init__.py CHANGED
@@ -1,20 +1,39 @@
1
- # Decorator Classes
1
+ # Decorators
2
2
  from classicist.decorators import (
3
+ # @alias decorator
3
4
  alias,
4
- annotate,
5
+ # @annotation decorator
5
6
  annotation,
6
- annotations,
7
+ # @classproperty decorator
7
8
  classproperty,
9
+ # @deprecated decorator
8
10
  deprecated,
11
+ # @hybridmethod decorator
9
12
  hybridmethod,
13
+ # @nocache decorator
10
14
  nocache,
15
+ # @runtimer decorator
16
+ runtimer,
11
17
  )
12
18
 
13
19
  # Decorator Helper Methods
14
20
  from classicist.decorators import (
21
+ # @alias decorator helper methods
15
22
  is_aliased,
16
23
  aliases,
24
+ # @annotation decorator helper methods
25
+ annotate,
26
+ annotations,
27
+ # @deprecated decorator helper methods
17
28
  is_deprecated,
29
+ # @runtimer decorator helper methods
30
+ runtime,
31
+ has_runtimer,
32
+ )
33
+
34
+ # Decorator Related Classes
35
+ from classicist.decorators import (
36
+ Runtimer,
18
37
  )
19
38
 
20
39
  # Meta Classes
@@ -25,6 +44,8 @@ from classicist.metaclasses import (
25
44
 
26
45
  # Exception Classes
27
46
  from classicist.exceptions import (
47
+ AliasError,
48
+ AnnotationError,
28
49
  AttributeShadowingError,
29
50
  )
30
51
 
@@ -38,13 +59,20 @@ __all__ = [
38
59
  "deprecated",
39
60
  "hybridmethod",
40
61
  "nocache",
62
+ "runtimer",
41
63
  # Decorator Helper Methods
42
64
  "is_aliased",
43
65
  "aliases",
44
66
  "is_deprecated",
67
+ "runtime",
68
+ "has_runtimer",
69
+ # Decorator Related Classes
70
+ "Runtimer",
45
71
  # Meta Classes
46
72
  "aliased",
47
73
  "shadowproof",
48
74
  # Exception Classes
75
+ "AliasError",
76
+ "AnnotationError",
49
77
  "AttributeShadowingError",
50
78
  ]
@@ -4,6 +4,7 @@ from classicist.decorators.classproperty import classproperty
4
4
  from classicist.decorators.deprecated import deprecated, is_deprecated
5
5
  from classicist.decorators.hybridmethod import hybridmethod
6
6
  from classicist.decorators.nocache import nocache
7
+ from classicist.decorators.runtimer import Runtimer, runtimer, runtime, has_runtimer
7
8
 
8
9
  __all__ = [
9
10
  "alias",
@@ -17,4 +18,8 @@ __all__ = [
17
18
  "is_deprecated",
18
19
  "hybridmethod",
19
20
  "nocache",
21
+ "Runtimer",
22
+ "runtimer",
23
+ "runtime",
24
+ "has_runtimer",
20
25
  ]
@@ -0,0 +1,165 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timedelta
4
+ from functools import wraps
5
+ from inspect import unwrap
6
+
7
+ from classicist.logging import logger
8
+
9
+ logger = logger.getChild(__name__)
10
+
11
+
12
+ class Runtimer(object):
13
+ """The Runtimer class times and tracks the runtime of function calls."""
14
+
15
+ _funcobj: callable = None
16
+ _started: datetime = None
17
+ _stopped: datetime = None
18
+
19
+ def __init__(self, function: callable):
20
+ """Supports instantiating an instance of the Runtimer class."""
21
+
22
+ if not callable(function):
23
+ raise TypeError("The 'function' argument must reference a callable!")
24
+
25
+ self._funcobj = function
26
+
27
+ def __str__(self) -> str:
28
+ """Returns a string representation of the current Runtimer instance."""
29
+
30
+ return f"<{self.__class__.__name__}(started: {self.started}, stopped: {self.stopped}, duration: {self.duration})>"
31
+
32
+ def __repr__(self) -> str:
33
+ """Returns a debug string representation of the current Runtimer instance."""
34
+
35
+ return f"<{self.__class__.__name__}(started: {self.started}, stopped: {self.stopped}, duration: {self.duration}) @ {hex(id(self))}>"
36
+
37
+ def reset(self) -> Runtimer:
38
+ """Supports resetting the Runtimer timing information."""
39
+
40
+ self._started = None
41
+ self._stopped = None
42
+
43
+ return self
44
+
45
+ def start(self) -> Runtimer:
46
+ """Supports starting the Runtimer timer by recording the current datetime."""
47
+
48
+ self._started = datetime.now()
49
+ self._stopped = None
50
+
51
+ return self
52
+
53
+ def stop(self) -> Runtimer:
54
+ """Supports stopping the Runtimer timer by recording the current datetime."""
55
+
56
+ if self._started is None:
57
+ self._started = datetime.now()
58
+ self._stopped = datetime.now()
59
+
60
+ return self
61
+
62
+ @property
63
+ def function(self) -> callable:
64
+ """Supports returning the Runtimer instance's associated function/method."""
65
+
66
+ return self._funcobj
67
+
68
+ @property
69
+ def started(self) -> datetime:
70
+ """Supports returning the started datetime or the current time as a fallback."""
71
+
72
+ return self._started or datetime.now()
73
+
74
+ @property
75
+ def stopped(self) -> datetime:
76
+ """Supports returning the stopped datetime or the current time as a fallback."""
77
+
78
+ return self._stopped or datetime.now()
79
+
80
+ @property
81
+ def timedelta(self) -> timedelta:
82
+ """Supports returning the timedelta for the decorated function's call time."""
83
+
84
+ if isinstance(self._started, datetime) and isinstance(self._stopped, datetime):
85
+ return self._stopped - self._started
86
+ else:
87
+ return timedelta(0)
88
+
89
+ @property
90
+ def duration(self) -> float:
91
+ """Supports returning the duration of the decorated function's call time."""
92
+
93
+ return self.timedelta.total_seconds()
94
+
95
+
96
+ def runtimer(function: callable) -> callable:
97
+ """The runtimer decorator method creates an instance of the Runtimer class for the
98
+ specified function, allowing calls to the function to be timed."""
99
+
100
+ if not callable(function):
101
+ raise TypeError("The 'function' argument must reference a callable!")
102
+
103
+ logger.debug("runtimer(function: %s)", function)
104
+
105
+ # If the function already has an associated Runtimer instance, reset it
106
+ if isinstance(
107
+ _runtimer := getattr(function, "_classicist_runtimer", None), Runtimer
108
+ ):
109
+ _runtimer.reset()
110
+ else:
111
+ # Otherwise, create a new instance and associate it with the function
112
+ _runtimer = function._classicist_runtimer = Runtimer(function)
113
+
114
+ @wraps(function)
115
+ def wrapper(*args, **kwargs):
116
+ logger.debug(
117
+ "runtimer(function: %s).wrapper(args: %s, kwargs: %s)",
118
+ function,
119
+ args,
120
+ kwargs,
121
+ )
122
+
123
+ _runtimer.start()
124
+ result = function(*args, **kwargs)
125
+ _runtimer.stop()
126
+
127
+ return result
128
+
129
+ return wrapper
130
+
131
+
132
+ def runtime(function: callable) -> Runtimer | None:
133
+ """The runtime helper method can be used to obtain the Runtimer instance for the
134
+ specified function, if one is present, allowing access to the most recent function
135
+ call start and stop time stamps and call duration."""
136
+
137
+ if not callable(function):
138
+ raise TypeError("The 'function' argument must reference a callable!")
139
+
140
+ function = unwrap(function)
141
+
142
+ logger.debug("runtime(function: %s)" % (function))
143
+
144
+ if isinstance(
145
+ _runtimer := getattr(function, "_classicist_runtimer", None), Runtimer
146
+ ):
147
+ return _runtimer
148
+
149
+
150
+ def has_runtimer(function: callable) -> bool:
151
+ """The has_runtimer helper method can be used to determine if the specified function
152
+ has an associated Runtimer instance or not, returning a boolean to indicate this."""
153
+
154
+ if isinstance(getattr(function, "_classicist_runtimer", None), Runtimer):
155
+ return True
156
+ else:
157
+ return False
158
+
159
+
160
+ __all__ = [
161
+ "Runtimer",
162
+ "runtimer",
163
+ "runtime",
164
+ "hasruntimer",
165
+ ]
@@ -1,4 +1,5 @@
1
1
  from classicist.exceptions.decorators import (
2
+ AliasError,
2
3
  AnnotationError,
3
4
  )
4
5
 
@@ -7,6 +8,7 @@ from classicist.exceptions.metaclasses import (
7
8
  )
8
9
 
9
10
  __all__ = [
11
+ "AliasError",
10
12
  "AnnotationError",
11
13
  "AttributeShadowingError",
12
14
  ]
classicist/version.txt CHANGED
@@ -1 +1 @@
1
- 1.0.3
1
+ 1.0.4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: classicist
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Classy class decorators for Python.
5
5
  Author: Daniel Sissman
6
6
  License-Expression: MIT
@@ -41,6 +41,7 @@ The Classicist library provides several useful decorators and helper methods inc
41
41
  * `@deprecated` – a decorator that can be used to mark functions, classes and methods as being deprecated;
42
42
  * `@alias` – a decorator that can be used to add aliases to classes, methods defined within classes, module-level functions, and nested functions when overriding the aliasing scope;
43
43
  * `@nocache` – a decorator that can be used to mark functions and methods as not being suitable for caching;
44
+ * `@runtimer` – a decorator that can be used to time function and method calls;
44
45
  * `shadowproof` – a metaclass that can be used to protect subclasses from class-level attributes
45
46
  being overwritten (or shadowed) which can otherwise negatively affect class behaviour in some cases.
46
47
 
@@ -482,6 +483,48 @@ class Test(object):
482
483
  pass
483
484
  ```
484
485
 
486
+ #### Runtimer: Function & Method Call Timing
487
+
488
+ The `@runtimer` decorator can be used to obtain run times for function and method calls,
489
+ including the start and stop `datetime`, the `timedelta` and the duration in seconds.
490
+
491
+ To collect timing information simply import the `runtimer` decorator from the library,
492
+ and apply it to the function, class method or instance method that you wish to time, and
493
+ after the call has been made, you can obtain the run time information from the function
494
+ or method via the `classicist` library's `runtime` helper method, which provides access
495
+ to an instance of the library's `Runtimer` class which is used to track the run time:
496
+
497
+ ```python
498
+ from classicist import runtimer, runtime, Runtimer
499
+ from datetime import datetime
500
+ from time import sleep
501
+
502
+ @runtimer
503
+ def function_to_time(value: int) -> int:
504
+ sleep(0.01)
505
+ return value * 100
506
+
507
+ # Obtain a reference to the function's Runtimer (created by the @runtimer decorator)
508
+ # This reference can be obtained before or after a call to the decorated function
509
+ runtimer: Runtimer = runtime(function_to_time)
510
+ assert isinstance(runtimer, Runtimer)
511
+
512
+ # Obtain the time before the function call for illustrative purposes (not needed in use)
513
+ started: datetime = datetime.now()
514
+
515
+ # Call the method to perform its work, and its runtime will be gathered
516
+ assert function_to_time(2) == 200
517
+
518
+ # Obtain the time after the function call for illustrative purposes (not needed in use)
519
+ stopped: datetime = datetime.now()
520
+
521
+ # Use the gathered runtime information as needed
522
+ assert runtimer.started > started
523
+ assert runtimer.duration >= 0.01
524
+ assert runtimer.timedelta.total_seconds() >= 0.01
525
+ assert runtimer.stopped < stopped
526
+ ```
527
+
485
528
  #### ShadowProof: Attribute Shadowing Protection Metaclass
486
529
 
487
530
  The `shadowproof` metaclass can be used to protect classes and subclasses from attribute
@@ -1,13 +1,14 @@
1
- classicist/__init__.py,sha256=Rkm1Vx0Z-BHPH1FSzFLrAxwkFEt1xvMxZz-mC8FJSDc,834
2
- classicist/version.txt,sha256=INLLCW0atBpBQCRtEvB79rjLdD_UgSK3JTLAPUTFwUo,5
3
- classicist/decorators/__init__.py,sha256=wplcs2JnyGMOh72626-x-WyVotVoT7nvk-dpjeTXQ88,600
1
+ classicist/__init__.py,sha256=Ndg3ZbcjOpSzXUfvskrg_VC7xLbyxH7GiSmoV4Yhp2w,1486
2
+ classicist/version.txt,sha256=rqWtvvV0eFJzxPOcG3aHz4AZk-DLa_Z4GkFonk7bsgw,5
3
+ classicist/decorators/__init__.py,sha256=Cz1zXWoLV7s63pQiOQX6B0RGDOg3schTbtkq6THz3J8,752
4
4
  classicist/decorators/aliased/__init__.py,sha256=vW1P9jbGkOD_m9GvgEXF0Uni-Dt6rfVXDVF5D4wC1GY,6926
5
5
  classicist/decorators/annotation/__init__.py,sha256=20WwmrXDxT85sItpDiCdC-hZjbyDR6E2mLPMSKQgm8g,1796
6
6
  classicist/decorators/classproperty/__init__.py,sha256=ED37_20UeAGKX1ahsv16wTg0JAJT4UBLqiNRbKAp2KE,1646
7
7
  classicist/decorators/deprecated/__init__.py,sha256=aAPFQoT-pJf1nauQGiPADkPBREMvEQJaUQc3kjA48Rg,2764
8
8
  classicist/decorators/hybridmethod/__init__.py,sha256=uEsp54ZL0UESAbhIigLtzOg_DHupr9EohzZaL5VYJzY,2512
9
9
  classicist/decorators/nocache/__init__.py,sha256=0S2yToD_PjShQK8RZ_MZRBtBxGM6hegtDSIPbZlq4vw,287
10
- classicist/exceptions/__init__.py,sha256=JTo3DDfvL8eex0H5erL8Yd9pQL6B1l3nPXcCapcCWLs,219
10
+ classicist/decorators/runtimer/__init__.py,sha256=3yoWDKzpq7IHrQJG2QMkUZOQmW4rtxmlM_mMC2N5V_8,4999
11
+ classicist/exceptions/__init__.py,sha256=P4sVGQ5wn2wdNLAhKDBKfTX4Vgell6sPlAQhadcOJxw,253
11
12
  classicist/exceptions/decorators/__init__.py,sha256=YSpu4-sX0QKbLf0ZpNFS3hXA9pPOo3uoMgAFvRT3emc,192
12
13
  classicist/exceptions/decorators/aliased/__init__.py,sha256=ea8Q9TQ80r0DeRMBbJ9VX9Y3y00iTLYiuB2KEkW4ajw,43
13
14
  classicist/exceptions/decorators/annotation/__init__.py,sha256=nogEgpDo5lnEG7IC2kR4Pb_BORu6Cubik4LlcnCSg-g,48
@@ -18,9 +19,9 @@ classicist/logging/__init__.py,sha256=LJ-Nih1LacPrO2TvTT6l84So-pgw84AHJ8IhzYKl5r
18
19
  classicist/metaclasses/__init__.py,sha256=mYhR5rM7cnJlaNUlgoQWOo44RQSORteRjYd0y0BajxQ,159
19
20
  classicist/metaclasses/aliased/__init__.py,sha256=grs4Z8dMY6R2fCFn4UCPdCzEJtVaAv-tsWFwY1DCUpI,2033
20
21
  classicist/metaclasses/shadowproof/__init__.py,sha256=d55uNjcaCorD3MdDUv7aRVpk2MlE8JzMjint6Vvf_Yc,1492
21
- classicist-1.0.3.dist-info/licenses/LICENSE.md,sha256=qBmrjPmSCp0YFyaIl2G3FU3rniFD31YC0Yd3MrO1wEg,1070
22
- classicist-1.0.3.dist-info/METADATA,sha256=iZaQqjD9wd84Bb_Dx_Se-RMYgZTvvf2Hkf2idrnswgk,25128
23
- classicist-1.0.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
24
- classicist-1.0.3.dist-info/top_level.txt,sha256=beG3ZuwObnmnY_mgNSN5CaVIWpI2VKszjVdKHPgZBhc,11
25
- classicist-1.0.3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
26
- classicist-1.0.3.dist-info/RECORD,,
22
+ classicist-1.0.4.dist-info/licenses/LICENSE.md,sha256=qBmrjPmSCp0YFyaIl2G3FU3rniFD31YC0Yd3MrO1wEg,1070
23
+ classicist-1.0.4.dist-info/METADATA,sha256=u7zpR5C-SXv1blBdvr03qu-MTXHPxEiOtzx19P1yqgQ,26888
24
+ classicist-1.0.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
25
+ classicist-1.0.4.dist-info/top_level.txt,sha256=beG3ZuwObnmnY_mgNSN5CaVIWpI2VKszjVdKHPgZBhc,11
26
+ classicist-1.0.4.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
27
+ classicist-1.0.4.dist-info/RECORD,,