Skip to content

Commit

Permalink
REFACTOR: build actual & by logic in Condition init
Browse files Browse the repository at this point in the history
DOCS:
+ document condition.py
  + add macro mkdocs plugin to reuse variables
  + add include mkdocs plugin to reuse "this is latest dev version" warning

CHORE:
+ update deps

TODOs left:
- override inherited init in Match (remove test param, make description optional)
- get rid of _ConditionRaisingIfNotActual
  • Loading branch information
yashaka committed Jun 2, 2024
1 parent 045cd66 commit ff256c6
Show file tree
Hide file tree
Showing 25 changed files with 1,771 additions and 387 deletions.
1 change: 1 addition & 0 deletions .pylint-disabled-rules
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ unnecessary-dunder-call,
unnecessary-ellipsis,
unused-private-member,
typevar-name-incorrect-variance,
anomalous-backslash-in-string,
2 changes: 1 addition & 1 deletion .run/lint_pycodestyle.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

pycodestyle $(pwd) --ignore=E501,W503,E402,E731,E203,E704 --exclude=.venv
pycodestyle $(pwd) --ignore=E501,W503,E402,E731,E203,E704,W605 --exclude=.venv
2 changes: 2 additions & 0 deletions .run/serve_docs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
mkdocs serve
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ TODOs:

### TODO: implement multi entity conditions

### TODO: doc «How to extend Selene» – from my_project_tests.extensions.selene import browser, have

### TODO: what about such style of filtering: balanced[balanced > 1]

### TODO: consider making description in Condition optional, and support tuple queries

## 2.0.0rc10: «copy&paste, frames, shadow & texts_like» (to be released on DD.05.2024)

### TODO: should we help users do not shoot their legs when using browser.all(selector) in for loops? #534
Expand All @@ -113,15 +119,19 @@ TODOs:

### TODO: ensure sub-classed condition == condition.not_.not_

### TODO: clean docstrings for Condition and ConditionMismatch

### TODO: ENSURE ALL Condition.as_not USAGES ARE NOW CORRECT

...

### TODO: deprecate and/or rename condition.not_ to condition.negated or condition.inverted

### TODO: ENSURE composed conditions work as expected (or/and, etc.)

...

### TODO: decide on Match fate (alias or subclass, or subclass + match* 2 in 1)

### Text related conditions now accepts int and floats as text item

`.have.exact_texts(1, 2.0, '3')` is now possible, and will be treated as `['1', '2.0', '3']`
Expand Down Expand Up @@ -442,6 +452,10 @@ Thanks to [Cameron Shimmin](https://github.com/cshimm) and Edale Miguel for PR [
- there is also an experimental `condition._match`, that is actually aliased by `condition.__call__`
- `ConditionNotMatchedError` in favor of `ConditionMismatch`

### Refactorings

- moved `Query` & Co from `core/wait.py` to `common/_typing_functioins.py`

## 2.0.0rc9 (to be released on 06.03.2024)

### Click with offsets
Expand Down
2 changes: 2 additions & 0 deletions docs/faq/iframes-howto.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# How to work with iframes in Selene?

{% include-markdown 'warn-from-next-release.md' %}

You allways can work with iframes same way [as you do in pure Selenium](https://www.selenium.dev/documentation/webdriver/interactions/frames), by using `browser.driver.switch_to.*` commands:

```python
Expand Down
3 changes: 3 additions & 0 deletions docs/faq/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@

## Browser configuration

- [How to work with iFrames](iframes-howto.md)
- [How to work with Shadow DOM](shadow-dom-howto.md)
- [How to use custom profile](custom-user-profile-howto.md)
- [Ho to extend Selene](extending-selene-howto.md)
2 changes: 2 additions & 0 deletions docs/faq/shadow-dom-howto.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# How to work with Shadow DOM in Selene?

{% include-markdown 'warn-from-next-release.md' %}

– By using advanced [query.js.shadow_root][selene.core.query.js.shadow_root] and [query.js.shadow_roots][selene.core.query.js.shadow_roots] queries, as simply as:

```python
Expand Down
11 changes: 11 additions & 0 deletions docs/reference/condition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Expected conditions

{% include-markdown 'warn-from-next-release.md' %}

::: selene.core.condition
options:
show_root_toc_entry: false
show_if_no_docstring: false
members_order: alphabetical
filters:
- "!__.*"
3 changes: 3 additions & 0 deletions docs/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
## Core

- [Config](configuration.md)
- [Expected Conditions](condition.md)
- [Command](command.md)
- [Query](query.md)
2 changes: 2 additions & 0 deletions docs/reference/query.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#

{% include-markdown 'warn-from-next-release.md' %}

::: selene.core.query
options:
show_root_toc_entry: false
Expand Down
4 changes: 4 additions & 0 deletions docs/warn-from-next-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
!!! warning "This is the latest development version"

Some features documented on this page may not yet be available
in the published stable version.
4 changes: 4 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ nav:
- Config: reference/configuration.md
- command.*: reference/command.md
- query.*: reference/query.md
- Expected Conditions: reference/condition.md
- Contribution:
- How to contribute: contribution/to-source-code-guide.md
- Code conventions: contribution/code-conventions-guide.md
Expand Down Expand Up @@ -72,6 +73,7 @@ theme:
- content.action.view

extra:
version: 2.0.0rc10
social:
- icon: material/web
link: https://autotest.how/sdet-start-ru
Expand All @@ -93,7 +95,9 @@ extra:
# Plugins
plugins:
- search
- macros
- autorefs
- include-markdown
- mkdocstrings:
handlers:
python:
Expand Down
326 changes: 203 additions & 123 deletions poetry.lock

Large diffs are not rendered by default.

31 changes: 17 additions & 14 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ mkdocs-material = "^9.1.6"
mkdocstrings = {extras = ["python"], version = "^0.21.2"}
mkdocs-redirects = "^1.2.0"
mkdocs-git-revision-date-localized-plugin = "^1.2.0"
mkdocs-include-markdown-plugin = "^6.1.1"
mkdocs-macros-plugin = "^1.0.5"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down Expand Up @@ -200,29 +202,30 @@ skip-string-normalization = 1
exception-escape,
comprehension-escape
unused-private-member,
typevar-name-incorrect-variance''']
typevar-name-incorrect-variance,
anomalous-backslash-in-string''']
[tool.pylint.'REPORTS']
evaluation=['10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)']
output-format=['colorized']
reports=['yes']
score=['yes']

[tool.pylintt.'REFACTORING']
[tool.pylint.'REFACTORING']
max-nested-blocks=5
never-returning-functions=['sys.exit']

[tool.pylint.'LOGGING']
logging-format-style=['old']
logging-modules=['logging']

[tool.pylint.'SPELLING']
[tool.pylint.'SPELLING']
max-spelling-suggestions=4
spelling-store-unknown-words=['no']

[tool.pylint.'MISCELLANEOUS']
[tool.pylint.'MISCELLANEOUS']
notes=['FIXME,XXX']

[tool.pylint.'TYPECHECK']
[tool.pylint.'TYPECHECK']
contextmanager-decorators=['contextlib.contextmanager']
ignore-mixin-members=['yes']
ignore-none=['yes']
Expand All @@ -232,15 +235,15 @@ skip-string-normalization = 1
missing-member-hint-distance=1
missing-member-max-choices=1

[tool.pylint.'VARIABLES']
[tool.pylint.'VARIABLES']
allow-global-unused-variables=['yes']
callbacks=['cb_,_cb']
dummy-variables-rgx=['_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_']
ignored-argument-names=['_.*|^ignored_|^unused_']
init-import=['no']
redefining-builtins-modules=['six.moves,past.builtins,future.builtins,builtins,io']

[tool.pylint.'FORMAT']
[tool.pylint.'FORMAT']
ignore-long-lines=['^\s*(# )?<?https?://\S+>?$']
indent-after-paren=4
indent-string=' '
Expand All @@ -249,13 +252,13 @@ skip-string-normalization = 1
single-line-class-stmt=['no']
single-line-if-stmt=['no']

[tool.pylint.'SIMILARITIES']
[tool.pylint.'SIMILARITIES']
ignore-comments=['yes']
ignore-docstrings=['yes']
ignore-imports=['no']
min-similarity-lines=4

[tool.pylint.'BASIC']
[tool.pylint.'BASIC']
argument-naming-style=['snake_case']
attr-naming-style=['snake_case']
bad-names=['foo,bar,baz,toto,tutu,tata']
Expand All @@ -273,22 +276,22 @@ skip-string-normalization = 1
property-classes=['abc.abstractproperty']
variable-naming-style=['snake_case']

[tool.pylint.'STRING']
[tool.pylint.'STRING']
check-quote-consistency=['no']
check-str-concat-over-line-jumps=['no']

[tool.pylint.'IMPORTS']
[tool.pylint.'IMPORTS']
allow-wildcard-with-all=['no']
analyse-fallback-blocks=['no']
deprecated-modules=['optparse,tkinter.tix']
known-third-party=['enchant']

[tool.pylint.'CLASSES']
[tool.pylint.'CLASSES']
defining-attr-methods=['__init__,__new__,setUp,__post_init__']
valid-classmethod-first-arg=['cls']
valid-metaclass-classmethod-first-arg=['cls']

[tool.pylint.'DESIGN']
[tool.pylint.'DESIGN']
max-args=5
max-attributes=7
max-bool-expr=5
Expand All @@ -300,5 +303,5 @@ skip-string-normalization = 1
max-statements=80
min-public-methods=2

[tool.pylint.'EXCEPTIONS']
[tool.pylint.'EXCEPTIONS']
overgeneral-exceptions=['BaseException,Exception']
84 changes: 82 additions & 2 deletions selene/common/_typing_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@
# SOFTWARE.
from __future__ import annotations

from typing_extensions import TypeVar, Callable, Generic, Optional
import functools
import inspect
import re

from typing_extensions import TypeVar, Callable, Generic, Optional, overload

from selene.common.fp import thread_last

T = TypeVar('T')
E = TypeVar('E')
Expand Down Expand Up @@ -54,16 +59,91 @@ def full_name_for(callable_: Optional[Callable]) -> str | None:
if isinstance(callable_, Query):
return str(callable_)

# callable_ has its own __str__ implementation in its class
if type(callable_).__str__ != object.__str__:
return str(callable_)

# callable has its own __str__ implementation on the object itself
# TODO: do we even need to bother? And how should it property be done?
# TODO: should it override previous?
maybe_obj__str__ = getattr(callable_, '__str__', None)
if maybe_obj__str__ and 'at 0x' not in (obj_as_str := maybe_obj__str__()):
return obj_as_str

callable_ = (
callable_
if inspect.isfunction(callable_)
else (getattr(callable_, '__class__', None) or callable_)
)

qualname = getattr(callable_, '__qualname__', None)
if qualname is not None and not qualname.endswith('<lambda>'):
return (
qualname
if '<locals>.' not in qualname
else getattr(callable_, '__name__', None)
else qualname.split('<locals>.')[1]
)

return None

@staticmethod
def full_description_for(callable_: Optional[Callable]) -> str | None:
full_name = Query.full_name_for(callable_)
return (
thread_last(
full_name,
(re.sub, r'([a-z0-9])([A-Z])', r'\1 \2'),
(re.sub, r'(\w)\.(\w)', r'\1 \2'),
(re.sub, r'(^_+|_+$)', ''),
(re.sub, r'_+', ' '),
(re.sub, r'(\s)+', r'\1'),
str.lower,
)
if full_name
else None
)

@staticmethod
@overload
def _inverted(predicate: Predicate[E]) -> Predicate[E]: ...

@staticmethod
@overload
def _inverted(predicate: Query[E, bool]) -> Query[E, bool]: ...

@staticmethod
def _inverted(
predicate: Predicate[E] | Query[E, bool]
) -> Predicate[E] | Query[E, bool]:
# TODO: ensure it works correctly:) e.g. unit test it
if isinstance(predicate, Query):
return Query(
f'not {predicate}',
lambda entity: not predicate(entity),
)

def not_predicate(entity: E) -> bool:
return not predicate(entity)

not_predicate.__module__ = predicate.__module__
not_predicate.__annotations__ = predicate.__annotations__

predicate_name = getattr(predicate, '__name__', None)
predicate_qualname = getattr(predicate, '__qualname__', None)
if not predicate_name or not predicate_qualname:
return not_predicate

if '<lambda>' in predicate_name:
not_predicate.__name__ = predicate_name
not_predicate.__qualname__ = predicate_qualname
else:
not_predicate.__name__ = f'not_{predicate_name}'
not_predicate.__qualname__ = f'not_{predicate_name}'.join(
predicate_qualname.split(predicate_name)
)

return not_predicate

# TODO: should we define __name__ and __qualname__ on it?


Expand Down
Loading

0 comments on commit ff256c6

Please sign in to comment.