reacton 1.1.1__tar.gz → 1.2.1__tar.gz
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.
- {reacton-1.1.1 → reacton-1.2.1}/PKG-INFO +1 -1
- reacton-1.2.1/reacton/_version.py +1 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/core.py +81 -6
- {reacton-1.1.1 → reacton-1.2.1}/reacton/core_test.py +79 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/find.py +5 -2
- {reacton-1.1.1 → reacton-1.2.1}/reacton/generate.py +4 -4
- {reacton-1.1.1 → reacton-1.2.1}/reacton/patch.py +3 -3
- reacton-1.2.1/reacton/patch_display.py +45 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/utils.py +1 -1
- {reacton-1.1.1 → reacton-1.2.1}/reacton/utils_test.py +5 -5
- reacton-1.1.1/reacton/_version.py +0 -1
- {reacton-1.1.1 → reacton-1.2.1}/.gitignore +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/LICENSE +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/pyproject.toml +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/react_ipywidgets/__init__.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/react_ipywidgets/core.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/__init__.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/bqplot.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/find_test.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/generate_test.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/ipycanvas.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/ipyvue.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/ipyvuetify.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/ipywidgets.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/logging.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/py.typed +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/rx.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/test.vue +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/test_rx.py +0 -0
- {reacton-1.1.1 → reacton-1.2.1}/reacton/work.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.1"
|
|
@@ -37,10 +37,12 @@ from warnings import warn
|
|
|
37
37
|
|
|
38
38
|
import ipywidgets
|
|
39
39
|
import ipywidgets as widgets
|
|
40
|
+
import traitlets
|
|
40
41
|
import typing_extensions
|
|
41
42
|
from typing_extensions import Literal, Protocol
|
|
42
43
|
|
|
43
44
|
import reacton.logging # noqa: F401 # has sidefx
|
|
45
|
+
import reacton.patch_display # noqa: F401 # has sidefx
|
|
44
46
|
|
|
45
47
|
from . import _version, patch, utils # noqa: F401
|
|
46
48
|
|
|
@@ -529,6 +531,70 @@ class ComponentFunction(Component):
|
|
|
529
531
|
self.value_name = value_name
|
|
530
532
|
functools.update_wrapper(self, f)
|
|
531
533
|
|
|
534
|
+
def widget(self, **kwargs):
|
|
535
|
+
return self.widget_class()(**kwargs)
|
|
536
|
+
|
|
537
|
+
def widget_class(self):
|
|
538
|
+
parameters = inspect.signature(self.f).parameters
|
|
539
|
+
args = list(parameters.keys())
|
|
540
|
+
listeners = set()
|
|
541
|
+
normal_kwargs = set()
|
|
542
|
+
for name in args:
|
|
543
|
+
if name.startswith("on_"):
|
|
544
|
+
listeners.add(name)
|
|
545
|
+
else:
|
|
546
|
+
normal_kwargs.add(name)
|
|
547
|
+
component = self
|
|
548
|
+
|
|
549
|
+
class Container(widgets.VBox, widgets.ValueWidget):
|
|
550
|
+
description = traitlets.Unicode()
|
|
551
|
+
|
|
552
|
+
def __init__(self, **kwargs):
|
|
553
|
+
super().__init__(**kwargs)
|
|
554
|
+
self._children = []
|
|
555
|
+
traits = {}
|
|
556
|
+
|
|
557
|
+
for name in normal_kwargs:
|
|
558
|
+
if name != "children": # children is a special trait we keep
|
|
559
|
+
if name in normal_kwargs:
|
|
560
|
+
trait = traitlets.Any(default_value=parameters[name].default)
|
|
561
|
+
else:
|
|
562
|
+
trait = traitlets.Any()
|
|
563
|
+
traits[name] = trait
|
|
564
|
+
Container.add_traits(self, **traits)
|
|
565
|
+
for name, value in kwargs.items():
|
|
566
|
+
if name in normal_kwargs:
|
|
567
|
+
setattr(self, name, value)
|
|
568
|
+
for name in normal_kwargs:
|
|
569
|
+
self.observe(self._rerender, name)
|
|
570
|
+
el = self._create_el()
|
|
571
|
+
self.rc = _RenderContext(el, self)
|
|
572
|
+
self.rc.render(el, self)
|
|
573
|
+
|
|
574
|
+
def _create_el(self):
|
|
575
|
+
kwargs = {}
|
|
576
|
+
for name in normal_kwargs:
|
|
577
|
+
if name != "children":
|
|
578
|
+
kwargs[name] = getattr(self, name)
|
|
579
|
+
for name in listeners:
|
|
580
|
+
propname = name[3:]
|
|
581
|
+
|
|
582
|
+
def closure(propname=propname):
|
|
583
|
+
def setter(value):
|
|
584
|
+
setattr(self, propname, value)
|
|
585
|
+
|
|
586
|
+
return setter
|
|
587
|
+
|
|
588
|
+
kwargs[name] = closure()
|
|
589
|
+
logger.info("calling component with kwargs: %r", kwargs)
|
|
590
|
+
return component(**kwargs)
|
|
591
|
+
|
|
592
|
+
def _rerender(self, change):
|
|
593
|
+
el = self._create_el()
|
|
594
|
+
self.rc.update(el)
|
|
595
|
+
|
|
596
|
+
return Container
|
|
597
|
+
|
|
532
598
|
def __eq__(self, rhs):
|
|
533
599
|
if self is rhs:
|
|
534
600
|
return True
|
|
@@ -1115,6 +1181,14 @@ class _RenderContext:
|
|
|
1115
1181
|
self.context.effects[self.context.effect_index] = Effect(effect, dependencies)
|
|
1116
1182
|
self.context.effect_index += 1
|
|
1117
1183
|
|
|
1184
|
+
def update(self, element: Element):
|
|
1185
|
+
if self._is_rendering:
|
|
1186
|
+
self.element = element
|
|
1187
|
+
self._rerender_needed = True
|
|
1188
|
+
self._rerender_needed_reason = "root element changed"
|
|
1189
|
+
else:
|
|
1190
|
+
self.render(element, self.container)
|
|
1191
|
+
|
|
1118
1192
|
def render(self, element: Element, container: widgets.Widget = None):
|
|
1119
1193
|
# render + consolidate
|
|
1120
1194
|
widget = None
|
|
@@ -1136,11 +1210,12 @@ class _RenderContext:
|
|
|
1136
1210
|
try:
|
|
1137
1211
|
local.rc = self
|
|
1138
1212
|
self.element = element
|
|
1213
|
+
del element
|
|
1139
1214
|
main_render_phase = not self._is_rendering
|
|
1140
1215
|
render_count = self.render_count # make a copy
|
|
1141
1216
|
self._rerender_needed = False
|
|
1142
1217
|
self._rerender_needed_reason = None
|
|
1143
|
-
logger.info("Render phase: %r %r", self.render_count, "main" if main_render_phase else "(nested)")
|
|
1218
|
+
logger.info("Render phase: %r %r of %r", self.render_count, "main" if main_render_phase else "(nested)", self.element)
|
|
1144
1219
|
self.render_count += 1
|
|
1145
1220
|
self._is_rendering = True
|
|
1146
1221
|
# if we got called recursively, self.context is not the root context
|
|
@@ -1149,12 +1224,12 @@ class _RenderContext:
|
|
|
1149
1224
|
self.context.exception_handler = False
|
|
1150
1225
|
self.context.exceptions_children = []
|
|
1151
1226
|
self.context.exceptions_self = []
|
|
1152
|
-
self.context.root_element_next = element
|
|
1227
|
+
self.context.root_element_next = self.element
|
|
1153
1228
|
assert self.context is not None
|
|
1154
1229
|
|
|
1155
1230
|
try:
|
|
1156
1231
|
self._shared_elements_next = set()
|
|
1157
|
-
self._render(element, "/", parent_key=ROOT_KEY)
|
|
1232
|
+
self._render(self.element, "/", parent_key=ROOT_KEY)
|
|
1158
1233
|
self.first_render = False
|
|
1159
1234
|
except BaseException:
|
|
1160
1235
|
self._is_rendering = False
|
|
@@ -1177,7 +1252,7 @@ class _RenderContext:
|
|
|
1177
1252
|
self.context.exceptions_children = []
|
|
1178
1253
|
self.context.exceptions_self = []
|
|
1179
1254
|
|
|
1180
|
-
self._render(element, "/", parent_key=ROOT_KEY)
|
|
1255
|
+
self._render(self.element, "/", parent_key=ROOT_KEY)
|
|
1181
1256
|
logger.info("Render done: %r %r", self._rerender_needed, self._rerender_needed_reason)
|
|
1182
1257
|
assert self.context is self.context_root
|
|
1183
1258
|
render_counts += 1
|
|
@@ -1193,7 +1268,7 @@ class _RenderContext:
|
|
|
1193
1268
|
break
|
|
1194
1269
|
|
|
1195
1270
|
logger.info("Render reconsolidate...")
|
|
1196
|
-
widget = self._reconsolidate(element, default_key="/", parent_key=ROOT_KEY)
|
|
1271
|
+
widget = self._reconsolidate(self.element, default_key="/", parent_key=ROOT_KEY)
|
|
1197
1272
|
logger.info("Render reconsolidate done")
|
|
1198
1273
|
self.context.root_element = self.context.root_element_next
|
|
1199
1274
|
self.context.root_element_next = None
|
|
@@ -1205,7 +1280,7 @@ class _RenderContext:
|
|
|
1205
1280
|
logger.debug("\t%r %x", el, id(el))
|
|
1206
1281
|
# RESET
|
|
1207
1282
|
assert self.context is self.context_root
|
|
1208
|
-
if element.is_shared:
|
|
1283
|
+
if self.element.is_shared:
|
|
1209
1284
|
assert widget in self._shared_widgets.values()
|
|
1210
1285
|
else:
|
|
1211
1286
|
assert widget in self.context_root.widgets.values()
|
|
@@ -11,6 +11,7 @@ import numpy as np
|
|
|
11
11
|
import pandas as pd
|
|
12
12
|
import pytest
|
|
13
13
|
import traitlets
|
|
14
|
+
from IPython.display import display
|
|
14
15
|
|
|
15
16
|
import reacton as react
|
|
16
17
|
import reacton.core
|
|
@@ -2719,3 +2720,81 @@ def test_implicit_component():
|
|
|
2719
2720
|
assert rc.find(widgets.Button)[0].widget.description == "hoeba: 1"
|
|
2720
2721
|
assert rc.find(widgets.Button)[1].widget.description == "hoeba: 2"
|
|
2721
2722
|
rc.close()
|
|
2723
|
+
|
|
2724
|
+
|
|
2725
|
+
def test_create_widget():
|
|
2726
|
+
@react.component
|
|
2727
|
+
def Test(a: int = 1, label="lala", on_a=None, children=[]):
|
|
2728
|
+
# adding children shouldn't change the children trait
|
|
2729
|
+
assert children == []
|
|
2730
|
+
return w.IntSlider(value=a, on_value=on_a, description=label)
|
|
2731
|
+
|
|
2732
|
+
widget: widgets.Widget = Test.widget(a=1) # type: ignore
|
|
2733
|
+
assert widget.trait_metadata("children", "to_json") is not None
|
|
2734
|
+
mock = unittest.mock.Mock()
|
|
2735
|
+
widget.trait_names() == ["a", "label"]
|
|
2736
|
+
widget.observe(mock, "a")
|
|
2737
|
+
assert widget.label == "lala"
|
|
2738
|
+
widget.a = 2
|
|
2739
|
+
mock.assert_called_once_with({"name": "a", "old": 1, "new": 2, "owner": widget, "type": "change"})
|
|
2740
|
+
slider = widget.children[0]
|
|
2741
|
+
assert isinstance(slider, widgets.IntSlider)
|
|
2742
|
+
slider.value = 3
|
|
2743
|
+
mock.assert_called_with({"name": "a", "old": 2, "new": 3, "owner": widget, "type": "change"})
|
|
2744
|
+
|
|
2745
|
+
widget.rc.close()
|
|
2746
|
+
|
|
2747
|
+
@reacton.component
|
|
2748
|
+
def BiSlider(value, on_value):
|
|
2749
|
+
x, y = value
|
|
2750
|
+
with w.VBox() as main:
|
|
2751
|
+
w.IntSlider(min=0, max=10, value=x, on_value=lambda x: on_value((x, y)))
|
|
2752
|
+
w.IntSlider(min=0, max=x**2, value=y, on_value=lambda y: on_value((x, y)))
|
|
2753
|
+
return main
|
|
2754
|
+
|
|
2755
|
+
widget: widgets.Widget = BiSlider.widget(value=(3, 4)) # type: ignore
|
|
2756
|
+
slider1 = widget.children[0].children[0]
|
|
2757
|
+
slider2 = widget.children[0].children[1]
|
|
2758
|
+
assert slider1.value == 3
|
|
2759
|
+
assert slider2.value == 4
|
|
2760
|
+
widget.value = (2, 3)
|
|
2761
|
+
assert slider1.value == 2
|
|
2762
|
+
assert slider2.value == 3
|
|
2763
|
+
|
|
2764
|
+
slider1.value = 3
|
|
2765
|
+
assert slider2.max == 9
|
|
2766
|
+
widget.rc.close()
|
|
2767
|
+
|
|
2768
|
+
|
|
2769
|
+
def test_display_element():
|
|
2770
|
+
button = w.Button(description="hoeba")
|
|
2771
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
2772
|
+
|
|
2773
|
+
store = InteractiveShell.instance().display_pub.publish
|
|
2774
|
+
InteractiveShell.instance().display_pub.publish = unittest.mock.Mock()
|
|
2775
|
+
|
|
2776
|
+
@react.component
|
|
2777
|
+
def Test():
|
|
2778
|
+
with w.VBox() as box:
|
|
2779
|
+
display(button)
|
|
2780
|
+
return box
|
|
2781
|
+
|
|
2782
|
+
box, rc = react.render(Test(), handle_error=False)
|
|
2783
|
+
assert rc.find(widgets.Button).widget.description == "hoeba"
|
|
2784
|
+
InteractiveShell.instance().display_pub.publish.assert_not_called()
|
|
2785
|
+
rc.close()
|
|
2786
|
+
InteractiveShell.instance().display_pub.publish = store
|
|
2787
|
+
|
|
2788
|
+
|
|
2789
|
+
def test_display_dataframe():
|
|
2790
|
+
df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
|
2791
|
+
|
|
2792
|
+
@react.component
|
|
2793
|
+
def Test():
|
|
2794
|
+
with w.VBox() as box:
|
|
2795
|
+
display(df)
|
|
2796
|
+
return box
|
|
2797
|
+
|
|
2798
|
+
box, rc = react.render(Test(), handle_error=False)
|
|
2799
|
+
# assert rc.find(widgets.Button).widget.description == "hoeba"
|
|
2800
|
+
rc.close()
|
|
@@ -50,6 +50,9 @@ class Finder(collections.abc.Sequence, Generic[W]):
|
|
|
50
50
|
parent = parent.parent
|
|
51
51
|
return parent
|
|
52
52
|
|
|
53
|
+
def __repr__(self):
|
|
54
|
+
return "Finder structure:\n%r" % self._current_structure()
|
|
55
|
+
|
|
53
56
|
def _reexecute_find(self):
|
|
54
57
|
finder = self._root()
|
|
55
58
|
for query in self.queries:
|
|
@@ -86,7 +89,7 @@ class Finder(collections.abc.Sequence, Generic[W]):
|
|
|
86
89
|
def matches(self, **matches):
|
|
87
90
|
self.assert_matches(**matches)
|
|
88
91
|
|
|
89
|
-
def wait_for(self, state="attached", timeout=1):
|
|
92
|
+
def wait_for(self, state="attached", timeout=1, iteration_delay=0.01):
|
|
90
93
|
start = time.time()
|
|
91
94
|
finder = self
|
|
92
95
|
while time.time() - start < timeout:
|
|
@@ -98,7 +101,7 @@ class Finder(collections.abc.Sequence, Generic[W]):
|
|
|
98
101
|
return finder
|
|
99
102
|
else:
|
|
100
103
|
raise ValueError(f"Unknown state {state}, expected 'attached' or 'detached'")
|
|
101
|
-
time.sleep(
|
|
104
|
+
time.sleep(iteration_delay)
|
|
102
105
|
finder = self._reexecute_find()
|
|
103
106
|
raise TimeoutError(f"Timeout waiting for {self}, current structure:\n{self._current_structure()}")
|
|
104
107
|
|
|
@@ -173,16 +173,16 @@ class CodeGen(Generic[W]):
|
|
|
173
173
|
# TODO: we can special case this (Tuple is subclass of Instance)
|
|
174
174
|
# but it's fixed length
|
|
175
175
|
if isinstance(trait, traitlets.traitlets.Instance):
|
|
176
|
-
if trait.klass.__name__ == "array":
|
|
176
|
+
if trait.klass.__name__ == "array": # type: ignore
|
|
177
177
|
import pdb
|
|
178
178
|
|
|
179
179
|
pdb.set_trace()
|
|
180
180
|
if trait.klass.__module__ == "builtins":
|
|
181
|
-
return trait.klass.__name__
|
|
181
|
+
return trait.klass.__name__ # type: ignore
|
|
182
182
|
else:
|
|
183
|
-
type_name = str(trait.klass.__module__) + "." + trait.klass.__name__
|
|
183
|
+
type_name = str(trait.klass.__module__) + "." + trait.klass.__name__ # type: ignore
|
|
184
184
|
type_name = type_name_alias.get(type_name, type_name)
|
|
185
|
-
if issubclass(trait.klass, widgets.Widget):
|
|
185
|
+
if issubclass(trait.klass, widgets.Widget): # type: ignore
|
|
186
186
|
element_class_name_type = "Element" # self.get_element_class
|
|
187
187
|
type_name = f"{element_class_name_type}[{type_name}]"
|
|
188
188
|
return type_name
|
|
@@ -13,8 +13,8 @@ class Callable(traitlets.traitlets.TraitType):
|
|
|
13
13
|
|
|
14
14
|
# for py36 we are stuck with a version that does not have this
|
|
15
15
|
if not hasattr(traitlets.traitlets, "Callable"):
|
|
16
|
-
traitlets.traitlets.Callable = Callable
|
|
17
|
-
traitlets.Callable = Callable
|
|
16
|
+
traitlets.traitlets.Callable = Callable # type: ignore
|
|
17
|
+
traitlets.Callable = Callable # type: ignore
|
|
18
18
|
|
|
19
19
|
if not hasattr(traitlets.traitlets.TraitType, "default"):
|
|
20
20
|
|
|
@@ -33,4 +33,4 @@ if not hasattr(traitlets.traitlets.TraitType, "default"):
|
|
|
33
33
|
# Undefined will raise in TraitType.get
|
|
34
34
|
return self.default_value
|
|
35
35
|
|
|
36
|
-
traitlets.traitlets.TraitType.default = default
|
|
36
|
+
traitlets.traitlets.TraitType.default = default # type: ignore
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from IPython.core.formatters import BaseFormatter
|
|
2
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
3
|
+
|
|
4
|
+
ipython_display_formatter_original = InteractiveShell.instance().display_formatter.ipython_display_formatter
|
|
5
|
+
original_display_publisher_publish = InteractiveShell.instance().display_pub.publish
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def publish(data, metadata=None, *args, **kwargs):
|
|
9
|
+
"""Will intercept a display call and add the display data to an output widget when in a reacton context/render function."""
|
|
10
|
+
from .core import get_render_context
|
|
11
|
+
|
|
12
|
+
rc = get_render_context(required=False)
|
|
13
|
+
if rc is not None:
|
|
14
|
+
from .ipywidgets import Output
|
|
15
|
+
|
|
16
|
+
Output(outputs=[{"output_type": "display_data", "data": data, "metadata": metadata}])
|
|
17
|
+
else:
|
|
18
|
+
return original_display_publisher_publish(data, metadata, *args, **kwargs)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ReactonDisplayFormatter(BaseFormatter):
|
|
22
|
+
"""Add direct support for adding elements to a container.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
|
|
26
|
+
with w.VBox():
|
|
27
|
+
display(button)
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __call__(self, obj):
|
|
32
|
+
from .core import Element, get_render_context # noqa
|
|
33
|
+
|
|
34
|
+
rc = get_render_context(required=False)
|
|
35
|
+
if rc is not None:
|
|
36
|
+
if rc.container_adders:
|
|
37
|
+
if isinstance(obj, Element):
|
|
38
|
+
# add directly as a child
|
|
39
|
+
rc.container_adders[-1].add(obj)
|
|
40
|
+
return True # we handled it
|
|
41
|
+
return ipython_display_formatter_original(obj)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
InteractiveShell.instance().display_formatter.ipython_display_formatter = ReactonDisplayFormatter()
|
|
45
|
+
InteractiveShell.instance().display_pub.publish = publish
|
|
@@ -45,7 +45,7 @@ def equals(a, b):
|
|
|
45
45
|
return same_component(a.component, b.component) and equals(a.args, b.args) and equals(a.kwargs, b.kwargs)
|
|
46
46
|
elif isinstance(a, types.FunctionType):
|
|
47
47
|
return a.__code__ == b.__code__ and (
|
|
48
|
-
(a.__closure__
|
|
48
|
+
(equals(a.__closure__, b.__closure__)) or equals([c.cell_contents for c in a.__closure__ or ()], [c.cell_contents for c in b.__closure__ or ()])
|
|
49
49
|
)
|
|
50
50
|
elif isinstance(a, dict) and isinstance(b, dict):
|
|
51
51
|
if len(a) != len(b):
|
|
@@ -51,15 +51,15 @@ def test_equals():
|
|
|
51
51
|
assert equals(dar1, dar2)
|
|
52
52
|
assert not equals(dar1, dar3)
|
|
53
53
|
|
|
54
|
-
def make_function(a):
|
|
54
|
+
def make_function(a, ar1, ar2):
|
|
55
55
|
def func(x):
|
|
56
|
-
return x**a
|
|
56
|
+
return x**a + (ar1 == ar2)
|
|
57
57
|
|
|
58
58
|
return func
|
|
59
59
|
|
|
60
|
-
f1 = make_function(1)
|
|
61
|
-
f2 = make_function(1)
|
|
62
|
-
f3 = make_function(2)
|
|
60
|
+
f1 = make_function(1, ar1, ar2)
|
|
61
|
+
f2 = make_function(1, ar1, ar2)
|
|
62
|
+
f3 = make_function(2, ar2, ar2)
|
|
63
63
|
assert f1 == f1
|
|
64
64
|
assert f1 != f2
|
|
65
65
|
assert equals(f1, f1)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.1.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|