Skip to content
This repository was archived by the owner on Aug 10, 2022. It is now read-only.

Commit 7915b32

Browse files
authored
Merge pull request #26 from applitools/develop
Merge develop to Master
2 parents c523d3f + 667dc1a commit 7915b32

File tree

28 files changed

+376
-309
lines changed

28 files changed

+376
-309
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 4.0.3
2+
current_version = 4.0.4
33
commit = True
44
tag = True
55

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.0.3"
1+
__version__ = "4.0.4"

eyes_common/applitools/common/config/configuration.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from applitools.common.geometry import RectangleSize
99
from applitools.common.match import ImageMatchSettings, MatchLevel
1010
from applitools.common.server import FailureReports, SessionType
11-
from applitools.common.utils import general_utils
11+
from applitools.common.utils import general_utils, argument_guard
1212
from applitools.common.utils.json_utils import JsonInclude
1313

1414
__all__ = ("BatchInfo", "Configuration")
@@ -28,6 +28,10 @@ class BatchInfo(object):
2828
factory=lambda: os.environ.get("APPLITOOLS_BATCH_NAME"),
2929
metadata={JsonInclude.THIS: True},
3030
) # type: Optional[Text]
31+
sequence_name = attr.ib(
32+
factory=lambda: os.environ.get("APPLITOOLS_BATCH_SEQUENCE"),
33+
metadata={JsonInclude.NAME: "batchSequenceName"},
34+
) # type: Optional[Text]
3135
started_at = attr.ib(
3236
factory=lambda: datetime.now(general_utils.UTC),
3337
metadata={JsonInclude.THIS: True},
@@ -46,10 +50,10 @@ def id_(self):
4650
def id_(self, value):
4751
self.id = value
4852

49-
50-
def _to_rectangle(d):
51-
# type: (dict) -> RectangleSize
52-
return RectangleSize.from_(d)
53+
def with_batch_id(self, id):
54+
argument_guard.not_none(id)
55+
self.id = id
56+
return self
5357

5458

5559
@attr.s
@@ -73,7 +77,7 @@ class Configuration(object):
7377
app_name = attr.ib(default=None) # type: Optional[Text]
7478
test_name = attr.ib(default=None) # type: Optional[Text]
7579
viewport_size = attr.ib(
76-
default=None, converter=attr.converters.optional(_to_rectangle)
80+
default=None, converter=attr.converters.optional(RectangleSize.from_)
7781
) # type: Optional[RectangleSize]
7882
session_type = attr.ib(default=SessionType.SEQUENTIAL) # type: SessionType
7983
ignore_baseline = attr.ib(default=None) # type: Optional[bool]

eyes_common/applitools/common/geometry.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from enum import Enum
66

77
import attr
8+
from PIL import Image
89

910
from .utils import argument_guard
1011
from .utils.converters import round_converter
1112
from .utils.json_utils import JsonInclude
1213

1314
if typing.TYPE_CHECKING:
14-
from PIL.Image import Image
1515
from typing import List, Dict, Union, Optional
1616
from .utils.custom_types import ViewPort, Num
1717
from .visual_grid import EmulationDevice
@@ -62,7 +62,7 @@ def __eq__(self, other):
6262

6363
@classmethod
6464
def from_(cls, obj):
65-
# type: (Union[dict, Image, EmulationDevice, RectangleSize]) -> RectangleSize
65+
# type: (Union[dict, Image.Image, EmulationDevice, RectangleSize]) -> RectangleSize
6666
if isinstance(obj, dict):
6767
return cls(width=obj["width"], height=obj["height"])
6868
return cls(width=obj.width, height=obj.height)
@@ -254,12 +254,12 @@ def create_empty_region(cls):
254254

255255
@classmethod
256256
def from_(cls, obj, size=None):
257-
# type: (Union[Image,Region, dict], Optional[dict]) -> Region
257+
# type: (Union[Image.Image,Region, dict], Optional[dict]) -> Region
258258
if size:
259259
return cls(obj["x"], obj["y"], size["width"], size["height"])
260260
elif isinstance(obj, Region):
261261
return cls(obj.left, obj.top, obj.width, obj.height, obj.coordinates_type)
262-
elif isinstance(obj, Image):
262+
elif isinstance(obj, Image.Image):
263263
return cls(0, 0, obj.width, obj.height)
264264
else:
265265
raise ValueError("Wrong parameters passed")

eyes_common/applitools/common/match.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class ImageMatchSettings(object):
9797
send_dom = attr.ib(default=False, metadata={JsonInclude.THIS: True})
9898
use_dom = attr.ib(default=False, metadata={JsonInclude.THIS: True})
9999
enable_patterns = attr.ib(default=False, metadata={JsonInclude.THIS: True})
100+
ignore_displacement = attr.ib(default=False, metadata={JsonInclude.THIS: True})
100101

101102
ignore = attr.ib(
102103
factory=list, type=typing.List[Region], metadata={JsonInclude.THIS: True}

eyes_common/applitools/common/utils/general_utils.py

Lines changed: 33 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import hashlib
44
import itertools
55
import time
6-
import types
76
import typing
87
from datetime import timedelta, tzinfo
98

@@ -17,13 +16,7 @@
1716

1817

1918
if typing.TYPE_CHECKING:
20-
from typing import Union, Callable, Any, Dict, List
21-
from selenium.webdriver.remote.webdriver import WebDriver
22-
from selenium.webdriver.remote.webelement import WebElement
23-
from selenium.webdriver.remote.switch_to import SwitchTo
24-
25-
from applitools.selenium.webdriver import EyesWebDriver, _EyesSwitchTo
26-
from applitools.selenium.webelement import EyesWebElement
19+
from typing import Callable, Any, List, Text
2720

2821
T = typing.TypeVar("T")
2922

@@ -59,82 +52,6 @@ def default(attr_name):
5952
return default
6053

6154

62-
def create_proxy_property(property_name, target_name, is_settable=False):
63-
# type: (str, str, bool) -> property
64-
"""
65-
Returns a property object which forwards "name" to target.
66-
67-
:param property_name: The name of the property.
68-
:param target_name: The target to forward to.
69-
"""
70-
71-
# noinspection PyUnusedLocal
72-
def _proxy_get(self):
73-
# type: (Any) -> Dict[str, float]
74-
return getattr(getattr(self, target_name), property_name)
75-
76-
# noinspection PyUnusedLocal
77-
def _proxy_set(self, val):
78-
return setattr(getattr(self, target_name), property_name, val)
79-
80-
if not is_settable:
81-
return property(_proxy_get)
82-
else:
83-
return property(_proxy_get, _proxy_set)
84-
85-
86-
def create_forwarded_method(
87-
from_, # type: Union[EyesWebDriver, EyesWebElement, _EyesSwitchTo]
88-
to, # type: Union[WebDriver, WebElement, SwitchTo]
89-
func_name, # type: str
90-
):
91-
# type: (...) -> Callable
92-
"""
93-
Returns a method(!) to be set on 'from_', which activates 'func_name' on 'to'.
94-
95-
:param from_: Source.
96-
:param to: Destination.
97-
:param func_name: The name of function to activate.
98-
:return: Relevant method.
99-
"""
100-
101-
# noinspection PyUnusedLocal
102-
def forwarded_method(self_, *args, **kwargs):
103-
# type: (EyesWebDriver, *Any, **Any) -> Callable
104-
return getattr(to, func_name)(*args, **kwargs)
105-
106-
return types.MethodType(forwarded_method, from_)
107-
108-
109-
def create_proxy_interface(
110-
from_, # type: Union[EyesWebDriver, EyesWebElement, _EyesSwitchTo]
111-
to, # type: Union[WebDriver, WebElement, SwitchTo]
112-
ignore_list=None, # type: List[str]
113-
override_existing=False, # type: bool
114-
):
115-
# type: (...) -> None
116-
"""
117-
Copies the public interface of the destination object, excluding names in the
118-
ignore_list, and creates an identical interface in 'eyes_core',
119-
which forwards calls to dst.
120-
121-
:param from_: Source.
122-
:param to: Destination.
123-
:param ignore_list: List of names to ignore while copying.
124-
:param override_existing: If False, attributes already existing in 'eyes_core'
125-
will not be overridden.
126-
"""
127-
if not ignore_list:
128-
ignore_list = []
129-
for attr_name in dir(to):
130-
if not attr_name.startswith("_") and attr_name not in ignore_list:
131-
if callable(getattr(to, attr_name)):
132-
if override_existing or not hasattr(from_, attr_name):
133-
setattr(
134-
from_, attr_name, create_forwarded_method(from_, to, attr_name)
135-
)
136-
137-
13855
def cached_property(f):
13956
# type: (Callable) -> Any
14057
"""
@@ -229,3 +146,35 @@ def set_query_parameter(url, param_name, param_value):
229146
new_query_string = urlencode(query_params, doseq=True)
230147

231148
return urlunsplit((scheme, netloc, path, new_query_string, fragment))
149+
150+
151+
def proxy_to(proxy_obj_name, fields):
152+
# type: (Text, List[Text]) -> Callable
153+
"""
154+
Adds to decorated class __getter__ and __setter__ methods that allow to access
155+
attributes from proxy_object in the parent class
156+
157+
:param proxy_obj_name: The name of the proxy object that has decorated class.
158+
:param fields:
159+
Fields which should be accessible in parent object from the proxy object.
160+
"""
161+
162+
def __getattr__(self, name):
163+
if name in fields:
164+
proxy_obj = getattr(self, proxy_obj_name)
165+
return getattr(proxy_obj, name)
166+
raise AttributeError("{} has not attr {}".format(self.__class__.__name__, name))
167+
168+
def __setattr__(self, key, value):
169+
if key in fields:
170+
proxy_obj = getattr(self, proxy_obj_name)
171+
setattr(proxy_obj, key, value)
172+
else:
173+
super(self.__class__, self).__setattr__(key, value)
174+
175+
def dec(cls):
176+
cls.__getattr__ = __getattr__
177+
cls.__setattr__ = __setattr__
178+
return cls
179+
180+
return dec

eyes_common/applitools/common/utils/image_utils.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
import base64
77
import io
88
import math
9-
import typing as tp
109

1110
from PIL import Image
1211

1312
from applitools.common import logger
13+
from applitools.common.geometry import Region
1414
from applitools.common.errors import EyesError
1515

16-
if tp.TYPE_CHECKING:
17-
from applitools.common.geometry import Region
16+
from . import argument_guard
17+
1818

1919
__all__ = (
2020
"image_from_file",
@@ -118,6 +118,9 @@ def save_image(image, filename):
118118

119119

120120
def crop_image(image, region_to_crop):
121+
argument_guard.is_a(image, Image.Image)
122+
argument_guard.is_a(region_to_crop, Region)
123+
121124
image_region = Region.from_(image)
122125
image_region.intersect(region_to_crop)
123126
if image_region.is_size_empty:
@@ -130,7 +133,7 @@ def crop_image(image, region_to_crop):
130133
if image_region != region_to_crop:
131134
logger.warning("requested cropped area overflows image boundaries.")
132135

133-
cropped_image = image.cut(
136+
cropped_image = image.crop(
134137
box=(
135138
image_region.left,
136139
image_region.top,

eyes_common/applitools/common/utils/json_utils.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import attr
77

8-
from applitools.common.utils import iteritems
8+
from .compat import iteritems
99

1010

1111
def to_json(val):
@@ -63,9 +63,21 @@ def attr_from_response(response, cls):
6363
return attr_from_json(response.text, cls)
6464

6565

66+
# Uses for replacing of regular attr.name to specified in metadata
67+
REPLACE_TO_DICT = dict()
68+
69+
6670
class _CamelCasedDict(dict):
6771
def __setitem__(self, key, value):
68-
key = underscore_to_camelcase(key)
72+
if key in REPLACE_TO_DICT:
73+
# use key specified in metadata
74+
old_key = key
75+
key = REPLACE_TO_DICT[old_key]
76+
del REPLACE_TO_DICT[old_key]
77+
else:
78+
# convert key into camel case format
79+
key = underscore_to_camelcase(key)
80+
# process Enum's
6981
if hasattr(value, "value"):
7082
value = value.value
7183
super(_CamelCasedDict, self).__setitem__(key, value)
@@ -78,7 +90,11 @@ def _filter(attr_, value):
7890
if value is None:
7991
return False
8092
return True
81-
if attr_.metadata.get(JsonInclude.THIS) or attr_.metadata.get(JsonInclude.NAME):
93+
if attr_.metadata.get(JsonInclude.THIS):
94+
return True
95+
if attr_.metadata.get(JsonInclude.NAME):
96+
# set key from metadata which would be used by default
97+
REPLACE_TO_DICT[attr_.name] = attr_.metadata[JsonInclude.NAME]
8298
return True
8399
return False
84100

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.0.3"
1+
__version__ = "4.0.4"

eyes_core/applitools/core/capture.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
if typing.TYPE_CHECKING:
1010
from typing import Optional
11-
from applitools.core import CheckSettings
11+
from applitools.core import CheckSettings, EyesBase
1212

1313
T = typing.TypeVar("T", bound=CheckSettings)
1414

@@ -46,13 +46,13 @@ def make_screenshot(self, image):
4646
pass
4747

4848

49+
@attr.s
4950
class ImageProvider(ABC):
5051
"""
5152
Encapsulates image retrieval.
5253
"""
5354

54-
def __init__(self, eyes):
55-
self._eyes = eyes
55+
_eyes = attr.ib() # type: EyesBase
5656

5757
@abstractmethod
5858
def get_image(self):

0 commit comments

Comments
 (0)