Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13']
fail-fast: false

steps:
Expand Down Expand Up @@ -44,10 +44,10 @@ jobs:
needs: test
runs-on: ubuntu-latest
steps:
- name: Set up Python 3.9
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: '3.9'
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ version: 2
build:
os: ubuntu-20.04
tools:
python: "3.9"
python: "3.10"

# Build documentation in the docs/ directory with Sphinx
sphinx:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ v3.6.0

*Release date: In development*

- Remove Python 3.9 support (Python 3.9 reached the end of its life on October 31st, 2025)
- Remove xcuitest deprecated `get_window_size` method and replaced it with `get_window_rect` in all mobile actions
- Add text comparison methods based on AI libraries. To use them, install the `ai` extra dependency:

Expand Down
8 changes: 4 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
requests~=2.27 # api tests
selenium~=4.0 # web tests
Appium-Python-Client>=2.3,<5.0 # mobile tests
Pillow~=10.1 # visual testing
Pillow~=12.0 # visual testing
screeninfo~=0.8
lxml~=5.1
Faker~=25.9
phonenumbers~=8.13
lxml~=6.0
Faker~=38.0
phonenumbers~=9.0
21 changes: 10 additions & 11 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
pytest~=7.2
coverage~=6.5
coveralls~=3.3
pytest~=9.0
coverage~=7.11
coveralls~=4.0
mock~=5.0
requests-mock~=1.10
Pygments~=2.14
flake8~=6.0
build~=0.10
flake8~=7.3
build~=1.3
wheel~=0.40
twine~=4.0
behave==1.2.6 # behave tests
importlib_metadata==7.2.1
spacy~=3.8.7
twine~=6.2
behave~=1.3 # behave tests
importlib_metadata~=8.7
spacy~=3.8
sentence-transformers~=5.1
transformers==4.56.2; python_version < '3.10' # Issue https://github.com/huggingface/transformers/issues/41339
openai~=1.108
openai~=2.7
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ def get_long_description():
'License :: OSI Approved :: Apache Software License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
Expand Down
52 changes: 26 additions & 26 deletions toolium/test/utils/test_dataset_replace_param.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,46 +183,46 @@ def test_replace_param_datetime_language_ignored():

def test_replace_param_today_spanish():
param = replace_param('[TODAY]', language='es')
assert param == datetime.datetime.utcnow().strftime('%d/%m/%Y')
assert param == datetime.datetime.now(datetime.timezone.utc).strftime('%d/%m/%Y')


def test_replace_param_today_not_spanish():
param = replace_param('[TODAY]', language='en')
assert param == datetime.datetime.utcnow().strftime('%Y/%m/%d')
assert param == datetime.datetime.now(datetime.timezone.utc).strftime('%Y/%m/%d')


def test_replace_param_today_offset():
param = replace_param('[TODAY - 1 DAYS]', language='es')
assert param == datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%d/%m/%Y')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%d/%m/%Y')


def test_replace_param_now_spanish():
param = replace_param('[NOW]', language='es')
assert param == datetime.datetime.utcnow().strftime('%d/%m/%Y %H:%M:%S')
assert param == datetime.datetime.now(datetime.timezone.utc).strftime('%d/%m/%Y %H:%M:%S')


def test_replace_param_now_not_spanish():
param = replace_param('[NOW]', language='it')
assert param == datetime.datetime.utcnow().strftime('%Y/%m/%d %H:%M:%S')
assert param == datetime.datetime.now(datetime.timezone.utc).strftime('%Y/%m/%d %H:%M:%S')


def test_replace_param_now_with_format():
param = replace_param('[NOW(%Y-%m-%dT%H:%M:%SZ)]')
assert param == datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
assert param == datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')


def test_replace_param_now_with_format_and_decimals_limit():
param = replace_param('[NOW(%Y-%m-%dT%H:%M:%S.%3fZ)]')
param_till_dot = param[:param.find('.')]
assert param_till_dot == datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')
assert param_till_dot == datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%S')
assert re.match(param_till_dot + r'\.\d{3}Z', param)


def test_replace_param_now_with_format_and_decimals_limit_beyond_microseconds():
param = replace_param('[NOW(%Y-%m-%dT%H:%M:%S.%12fZ)]')
param_till_dot = param[:param.find('.')]
assert param_till_dot == datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')
assert param_till_dot == datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%S')
assert re.match(param_till_dot + r'\.\d{12}Z', param)


Expand All @@ -239,92 +239,92 @@ def test_not_replace_param_now_with_invalid_closing_parenthesis_in_format():
def test_replace_param_now_offset():
param = replace_param('[NOW + 5 MINUTES]', language='es')
assert param == datetime.datetime.strftime(
datetime.datetime.utcnow() + datetime.timedelta(minutes=5), '%d/%m/%Y %H:%M:%S')
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=5), '%d/%m/%Y %H:%M:%S')


def test_replace_param_now_offset_with_format():
param = replace_param('[NOW(%Y-%m-%dT%H:%M:%SZ) + 5 MINUTES]')
assert param == datetime.datetime.strftime(
datetime.datetime.utcnow() + datetime.timedelta(minutes=5), '%Y-%m-%dT%H:%M:%SZ')
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=5), '%Y-%m-%dT%H:%M:%SZ')


def test_replace_param_today_offset_and_more():
param = replace_param('The day [TODAY - 1 DAYS] was yesterday', language='es')
offset_date = datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%d/%m/%Y')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%d/%m/%Y')
assert param == f'The day {offset_date} was yesterday'


def test_replace_param_today_offset_and_more_not_spanish():
param = replace_param('The day [TODAY - 1 DAYS] was yesterday', language='it')
offset_date = datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%Y/%m/%d')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%Y/%m/%d')
assert param == f'The day {offset_date} was yesterday'


def test_replace_param_now_offset_and_more():
param = replace_param('I will arrive at [NOW + 10 MINUTES] tomorrow', language='es')
offset_datetime = datetime.datetime.strftime(
datetime.datetime.utcnow() + datetime.timedelta(minutes=10), '%d/%m/%Y %H:%M:%S')
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=10), '%d/%m/%Y %H:%M:%S')
assert param == f'I will arrive at {offset_datetime} tomorrow'


def test_replace_param_now_offset_and_more_not_spanish():
param = replace_param('I will arrive at [NOW + 10 MINUTES] tomorrow', language='it')
offset_datetime = datetime.datetime.strftime(
datetime.datetime.utcnow() + datetime.timedelta(minutes=10), '%Y/%m/%d %H:%M:%S')
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=10), '%Y/%m/%d %H:%M:%S')
assert param == f'I will arrive at {offset_datetime} tomorrow'


def test_replace_param_now_offset_with_format_and_more():
param = replace_param('I will arrive at [NOW(%Y-%m-%dT%H:%M:%SZ) + 10 MINUTES] tomorrow')
offset_datetime = datetime.datetime.strftime(
datetime.datetime.utcnow() + datetime.timedelta(minutes=10), '%Y-%m-%dT%H:%M:%SZ')
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=10), '%Y-%m-%dT%H:%M:%SZ')
assert param == f'I will arrive at {offset_datetime} tomorrow'


def test_replace_param_now_offset_with_format_and_more_language_is_irrelevant():
param = replace_param('I will arrive at [NOW(%Y-%m-%dT%H:%M:%SZ) + 10 MINUTES] tomorrow', language='ru')
offset_datetime = datetime.datetime.strftime(
datetime.datetime.utcnow() + datetime.timedelta(minutes=10), '%Y-%m-%dT%H:%M:%SZ')
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=10), '%Y-%m-%dT%H:%M:%SZ')
assert param == f'I will arrive at {offset_datetime} tomorrow'


def test_replace_param_today_offset_with_format_and_more_with_extra_spaces():
param = replace_param('The date [NOW(%Y-%m-%dT%H:%M:%SZ) - 1 DAYS ] was yesterday')
offset_date = datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%Y-%m-%dT%H:%M:%SZ')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%Y-%m-%dT%H:%M:%SZ')
assert param == f'The date {offset_date} was yesterday'


def test_replace_param_today_offset_and_more_with_extra_spaces():
param = replace_param('The day [TODAY - 1 DAYS ] was yesterday', language='es')
offset_date = datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%d/%m/%Y')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%d/%m/%Y')
assert param == f'The day {offset_date} was yesterday'


def test_replace_param_today_offset_and_more_at_the_end():
param = replace_param('Yesterday was [TODAY - 1 DAYS]', language='es')
offset_date = datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%d/%m/%Y')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%d/%m/%Y')
assert param == f'Yesterday was {offset_date}'


def test_replace_param_today_offset_and_more_at_the_beginning():
param = replace_param('[TODAY - 1 DAYS] is yesterday', language='es')
offset_date = datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%d/%m/%Y')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%d/%m/%Y')
assert param == f'{offset_date} is yesterday'


def test_replace_param_today_offsets_and_more():
param = replace_param(
'The day [TODAY - 1 DAYS] was yesterday and I have an appointment at [NOW + 10 MINUTES]', language='es')
offset_date = datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%d/%m/%Y')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%d/%m/%Y')
offset_datetime = datetime.datetime.strftime(
datetime.datetime.utcnow() + datetime.timedelta(minutes=10), '%d/%m/%Y %H:%M:%S')
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=10), '%d/%m/%Y %H:%M:%S')
assert param == f'The day {offset_date} was yesterday and I have an appointment at {offset_datetime}'


Expand All @@ -333,9 +333,9 @@ def test_replace_param_now_offsets_with_format_and_more():
'The date [NOW(%Y-%m-%dT%H:%M:%SZ) - 1 DAYS] was yesterday '
'and I have an appointment at [NOW(%Y-%m-%dT%H:%M:%SZ) + 10 MINUTES]')
offset_date = datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%Y-%m-%dT%H:%M:%SZ')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%Y-%m-%dT%H:%M:%SZ')
offset_datetime = datetime.datetime.strftime(
datetime.datetime.utcnow() + datetime.timedelta(minutes=10), '%Y-%m-%dT%H:%M:%SZ')
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=10), '%Y-%m-%dT%H:%M:%SZ')
assert param == f'The date {offset_date} was yesterday and I have an appointment at {offset_datetime}'


Expand All @@ -344,9 +344,9 @@ def test_replace_param_now_offsets_with_and_without_format_and_more():
'The date [NOW(%Y-%m-%dT%H:%M:%SZ) - 1 DAYS] was yesterday '
'and I have an appointment at [NOW + 10 MINUTES]', language='es')
offset_date = datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), '%Y-%m-%dT%H:%M:%SZ')
datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1), '%Y-%m-%dT%H:%M:%SZ')
offset_datetime = datetime.datetime.strftime(
datetime.datetime.utcnow() + datetime.timedelta(minutes=10), '%d/%m/%Y %H:%M:%S')
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=10), '%d/%m/%Y %H:%M:%S')
assert param == f'The date {offset_date} was yesterday and I have an appointment at {offset_datetime}'


Expand Down
11 changes: 6 additions & 5 deletions toolium/utils/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ def _replace_param_replacement(param, language):
:param language: language to configure date format for NOW and TODAY
:return: tuple with replaced value and boolean to know if replacement has been done
"""
datetime_format = '%Y-%m-%d %H:%M:%S.%f'
date_format = '%d/%m/%Y %H:%M:%S' if language == 'es' else '%Y/%m/%d %H:%M:%S'
date_day_format = '%d/%m/%Y' if language == 'es' else '%Y/%m/%d'
alphanums = ''.join([string.ascii_lowercase, string.digits]) # abcdefghijklmnopqrstuvwxyz0123456789
Expand All @@ -203,10 +204,10 @@ def _replace_param_replacement(param, language):
# make sure random is not made up of digits only, by forcing the first char to be a letter
'[RANDOM]': ''.join([r.choice(string.ascii_lowercase), *(r.choice(alphanums) for i in range(7))]),
'[RANDOM_PHONE_NUMBER]': _get_random_phone_number,
'[TIMESTAMP]': str(int(datetime.datetime.timestamp(datetime.datetime.utcnow()))),
'[DATETIME]': str(datetime.datetime.utcnow()),
'[NOW]': str(datetime.datetime.utcnow().strftime(date_format)),
'[TODAY]': str(datetime.datetime.utcnow().strftime(date_day_format)),
'[TIMESTAMP]': str(int(datetime.datetime.timestamp(datetime.datetime.now(datetime.timezone.utc)))),
'[DATETIME]': str(datetime.datetime.now(datetime.timezone.utc).strftime(datetime_format)),
'[NOW]': str(datetime.datetime.now(datetime.timezone.utc).strftime(date_format)),
'[TODAY]': str(datetime.datetime.now(datetime.timezone.utc).strftime(date_day_format)),
r'\[ROUND:(.*?)::(\d*)\]': _get_rounded_float_number
}

Expand Down Expand Up @@ -344,7 +345,7 @@ def _date_matcher():
return re.match(r'\[(NOW(?:\((?:.*)\)|)|TODAY)(?:\s*([\+|-]\s*\d+)\s*(\w+)\s*)?\]', param)

def _offset_datetime(amount, units):
now = datetime.datetime.utcnow()
now = datetime.datetime.now(datetime.timezone.utc)
if not amount or not units:
return now
the_amount = int(amount.replace(' ', ''))
Expand Down