Move test runs into the test-run folder, add TimestampStrings, add basic tests via pytest
pytest added for dev. Move all the test run python scripts into the "test-run" folder, so we can use the tests/ folder for just tests. Setup one test for the TimestampStrings class with pytests in tests/unit/string_handling/test_timestamp_strings.py
This commit is contained in:
16
ReadMe.md
16
ReadMe.md
@@ -51,18 +51,24 @@ We must set the full index URL here because we run with "--no-project"
|
||||
uv run --with corelibs --index egra-gitea=https://git.egplusww.jp/api/packages/PyPI/pypi/simple/ --no-project --native-tls -- python -c "import corelibs"
|
||||
```
|
||||
|
||||
### Python tests
|
||||
|
||||
All python tests are the tests/ folder. They are structured by the source folder layout
|
||||
|
||||
### Other tests
|
||||
|
||||
In the test folder other tests are located.
|
||||
|
||||
At the moment only a small test for the "progress" and the "double byte string format" module is set
|
||||
In the test-run folder usage and run tests are located
|
||||
|
||||
```sh
|
||||
uv run --native-tls tests/progress/progress_test.py
|
||||
uv run --native-tls test-run/progress/progress_test.py
|
||||
```
|
||||
|
||||
```sh
|
||||
uv run --native-tls tests/double_byte_string_format/double_byte_string_format.py
|
||||
uv run --native-tls test-run/double_byte_string_format/double_byte_string_format.py
|
||||
```
|
||||
|
||||
```sh
|
||||
uv run --native-tls test-run/timestamp_strings/timestamp_strings.py
|
||||
```
|
||||
|
||||
## How to install in another project
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# MARK: Project info
|
||||
[project]
|
||||
name = "corelibs"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
description = "Collection of utils for Python scripts"
|
||||
readme = "ReadMe.md"
|
||||
requires-python = ">=3.13"
|
||||
@@ -25,6 +25,11 @@ explicit = true
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=8.4.1",
|
||||
]
|
||||
|
||||
# MARK: Python linting
|
||||
[tool.pyright]
|
||||
typeCheckingMode = "strict"
|
||||
|
||||
26
src/corelibs/string_handling/timestamp_strings.py
Normal file
26
src/corelibs/string_handling/timestamp_strings.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Current timestamp strings and time zones
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
|
||||
class TimestampStrings:
|
||||
"""
|
||||
set default time stamps
|
||||
"""
|
||||
|
||||
time_zone: str = 'Asia/Tokyo'
|
||||
|
||||
def __init__(self, time_zone: str | None = None):
|
||||
self.timestamp_now = datetime.now()
|
||||
self.time_zone = time_zone if time_zone is not None else __class__.time_zone
|
||||
try:
|
||||
self.timestamp_now_tz = datetime.now(ZoneInfo(self.time_zone))
|
||||
except ZoneInfoNotFoundError as e:
|
||||
raise ValueError(f'Zone could not be loaded [{self.time_zone}]: {e}') from e
|
||||
self.today = self.timestamp_now.strftime('%Y-%m-%d')
|
||||
self.timestamp = self.timestamp_now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.timestamp_tz = self.timestamp_now_tz.strftime("%Y-%m-%d %H:%M:%S %Z")
|
||||
self.timestamp_file = self.timestamp_now.strftime("%Y-%m-%d_%H%M%S")
|
||||
|
Can't render this file because it contains an unexpected character in line 3 and column 165.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it contains an unexpected character in line 3 and column 165.
|
23
test-run/timestamp_strings/timestamp_strings.py
Normal file
23
test-run/timestamp_strings/timestamp_strings.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env -S uv run --script
|
||||
|
||||
"""
|
||||
Test for double byte format
|
||||
"""
|
||||
|
||||
from corelibs.string_handling.timestamp_strings import TimestampStrings
|
||||
|
||||
|
||||
def main():
|
||||
ts = TimestampStrings()
|
||||
print(f"TS: {ts.timestamp_now}")
|
||||
|
||||
try:
|
||||
ts = TimestampStrings("invalid")
|
||||
except ValueError as e:
|
||||
print(f"Value error: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
# __END__
|
||||
0
tests/unit/__init__.py
Normal file
0
tests/unit/__init__.py
Normal file
155
tests/unit/string_handling/test_timestamp_strings.py
Normal file
155
tests/unit/string_handling/test_timestamp_strings.py
Normal file
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
PyTest: string_handling/timestamp_strings
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch, MagicMock
|
||||
from zoneinfo import ZoneInfo
|
||||
import pytest
|
||||
|
||||
# Assuming the class is in a file called timestamp_strings.py
|
||||
from corelibs.string_handling.timestamp_strings import TimestampStrings
|
||||
|
||||
|
||||
class TestTimestampStrings:
|
||||
"""Test suite for TimestampStrings class"""
|
||||
|
||||
def test_default_initialization(self):
|
||||
"""Test initialization with default timezone"""
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ts.time_zone == 'Asia/Tokyo'
|
||||
assert ts.timestamp_now == mock_now
|
||||
assert ts.today == '2023-12-25'
|
||||
assert ts.timestamp == '2023-12-25 15:30:45'
|
||||
assert ts.timestamp_file == '2023-12-25_153045'
|
||||
|
||||
def test_custom_timezone_initialization(self):
|
||||
"""Test initialization with custom timezone"""
|
||||
custom_tz = 'America/New_York'
|
||||
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings(time_zone=custom_tz)
|
||||
|
||||
assert ts.time_zone == custom_tz
|
||||
assert ts.timestamp_now == mock_now
|
||||
|
||||
def test_invalid_timezone_raises_error(self):
|
||||
"""Test that invalid timezone raises ValueError"""
|
||||
invalid_tz = 'Invalid/Timezone'
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
TimestampStrings(time_zone=invalid_tz)
|
||||
|
||||
assert 'Zone could not be loaded [Invalid/Timezone]' in str(exc_info.value)
|
||||
|
||||
def test_timestamp_formats(self):
|
||||
"""Test various timestamp format outputs"""
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
# Mock both datetime.now() calls
|
||||
mock_now = datetime(2023, 12, 25, 9, 5, 3)
|
||||
mock_now_tz = datetime(2023, 12, 25, 23, 5, 3, tzinfo=ZoneInfo('Asia/Tokyo'))
|
||||
|
||||
mock_datetime.now.side_effect = [mock_now, mock_now_tz]
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ts.today == '2023-12-25'
|
||||
assert ts.timestamp == '2023-12-25 09:05:03'
|
||||
assert ts.timestamp_file == '2023-12-25_090503'
|
||||
assert 'JST' in ts.timestamp_tz or 'Asia/Tokyo' in ts.timestamp_tz
|
||||
|
||||
def test_different_timezones_produce_different_results(self):
|
||||
"""Test that different timezones produce different timestamp_tz values"""
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 12, 0, 0)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
# Create instances with different timezones
|
||||
ts_tokyo = TimestampStrings(time_zone='Asia/Tokyo')
|
||||
ts_ny = TimestampStrings(time_zone='America/New_York')
|
||||
|
||||
# The timezone-aware timestamps should be different
|
||||
assert ts_tokyo.time_zone != ts_ny.time_zone
|
||||
# Note: The actual timestamp_tz values will depend on the mocked datetime
|
||||
|
||||
def test_class_default_timezone(self):
|
||||
"""Test that class default timezone is correctly set"""
|
||||
assert TimestampStrings.time_zone == 'Asia/Tokyo'
|
||||
|
||||
def test_none_timezone_uses_default(self):
|
||||
"""Test that passing None for timezone uses class default"""
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings(time_zone=None)
|
||||
|
||||
assert ts.time_zone == 'Asia/Tokyo'
|
||||
|
||||
def test_timestamp_file_format_no_colons(self):
|
||||
"""Test that timestamp_file format doesn't contain colons (safe for filenames)"""
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ':' not in ts.timestamp_file
|
||||
assert ' ' not in ts.timestamp_file
|
||||
assert ts.timestamp_file == '2023-12-25_153045'
|
||||
|
||||
def test_multiple_instances_independent(self):
|
||||
"""Test that multiple instances don't interfere with each other"""
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts1 = TimestampStrings(time_zone='Asia/Tokyo')
|
||||
ts2 = TimestampStrings(time_zone='Europe/London')
|
||||
|
||||
assert ts1.time_zone == 'Asia/Tokyo'
|
||||
assert ts2.time_zone == 'Europe/London'
|
||||
assert ts1.time_zone != ts2.time_zone
|
||||
|
||||
@patch('corelibs.string_handling.timestamp_strings.ZoneInfo')
|
||||
def test_zoneinfo_called_correctly(self, mock_zoneinfo: MagicMock):
|
||||
"""Test that ZoneInfo is called with correct timezone"""
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 15, 30, 45)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
custom_tz = 'Europe/Paris'
|
||||
ts = TimestampStrings(time_zone=custom_tz)
|
||||
assert ts.time_zone == custom_tz
|
||||
|
||||
mock_zoneinfo.assert_called_with(custom_tz)
|
||||
|
||||
def test_edge_case_midnight(self):
|
||||
"""Test timestamp formatting at midnight"""
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2023, 12, 25, 0, 0, 0)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ts.timestamp == '2023-12-25 00:00:00'
|
||||
assert ts.timestamp_file == '2023-12-25_000000'
|
||||
|
||||
def test_edge_case_new_year(self):
|
||||
"""Test timestamp formatting at new year"""
|
||||
with patch('corelibs.string_handling.timestamp_strings.datetime') as mock_datetime:
|
||||
mock_now = datetime(2024, 1, 1, 0, 0, 0)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
ts = TimestampStrings()
|
||||
|
||||
assert ts.today == '2024-01-01'
|
||||
assert ts.timestamp == '2024-01-01 00:00:00'
|
||||
71
uv.lock
generated
71
uv.lock
generated
@@ -33,9 +33,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "corelibs"
|
||||
version = "0.4.0"
|
||||
version = "0.6.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "jmespath" },
|
||||
@@ -43,6 +52,11 @@ dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "jmespath", specifier = ">=1.0.1" },
|
||||
@@ -50,6 +64,9 @@ requires-dist = [
|
||||
{ name = "requests", specifier = ">=2.32.4" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "pytest", specifier = ">=8.4.1" }]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
@@ -59,6 +76,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jmespath"
|
||||
version = "1.0.1"
|
||||
@@ -68,6 +94,24 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "7.0.0"
|
||||
@@ -83,6 +127,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.4"
|
||||
|
||||
Reference in New Issue
Block a user