alexwlchan-chives 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.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2025 Alex Chan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the Software
8
+ is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
17
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
18
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
19
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: alexwlchan-chives
3
+ Version: 1
4
+ Summary: Utility functions for working with my local media archives
5
+ Author-email: Alex Chan <alex@alexwlchan.net>
6
+ Maintainer-email: Alex Chan <alex@alexwlchan.net>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/alexwlchan/chives
9
+ Project-URL: Changelog, https://github.com/alexwlchan/chives/blob/main/CHANGELOG.md
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Requires-Python: >=3.13
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ # chives
18
+
19
+ chives is a collection of Python functions for working with my local
20
+ media archives.
21
+
22
+ I store a lot of media archives as [static websites][static-sites], and I use Python scripts to manage the sites.
23
+ This includes:
24
+
25
+ * Verifying every file that's described in the metadata is stored correctly
26
+ * Downloading pages from sites I want to bookmark
27
+ * Checking the quality and consistency of my metadata
28
+
29
+ This package has some functions I share across multiple archives/sites.
30
+
31
+ [static-sites]: https://alexwlchan.net/2024/static-websites/
32
+
33
+ ## References
34
+
35
+ I've written blog posts about some of the code in this repo:
36
+
37
+ * [Cleaning up messy dates in JSON](https://alexwlchan.net/2025/messy-dates-in-json/)
38
+
39
+ ## Versioning
40
+
41
+ This library is monotically versioned.
42
+ I'll try not to break anything between releases, but I make no guarantees of back-compatibility.
43
+
44
+ I'm making this public because it's convenient for me, and you might find useful code here, but be aware this may not be entirely stable.
45
+
46
+ ## Usage
47
+
48
+ All the functions are available in the `chives` namespace.
49
+
50
+ See the docstrings on individual functions for usage descriptions.
51
+
52
+ ## Installation
53
+
54
+ If you want to use this in your project, I recommend copying the relevant function and test into your codebase (with a link back to this repo).
55
+
56
+ Alternatively, you can install the package from PyPI:
57
+
58
+ ```console
59
+ $ pip install alexwlchan-chives
60
+ ```
61
+
62
+ ## Development
63
+
64
+ If you want to make changes to the library, there are instructions in [CONTRIBUTING.md](./CONTRIBUTING.md).
65
+
66
+ ## License
67
+
68
+ MIT.
@@ -0,0 +1,52 @@
1
+ # chives
2
+
3
+ chives is a collection of Python functions for working with my local
4
+ media archives.
5
+
6
+ I store a lot of media archives as [static websites][static-sites], and I use Python scripts to manage the sites.
7
+ This includes:
8
+
9
+ * Verifying every file that's described in the metadata is stored correctly
10
+ * Downloading pages from sites I want to bookmark
11
+ * Checking the quality and consistency of my metadata
12
+
13
+ This package has some functions I share across multiple archives/sites.
14
+
15
+ [static-sites]: https://alexwlchan.net/2024/static-websites/
16
+
17
+ ## References
18
+
19
+ I've written blog posts about some of the code in this repo:
20
+
21
+ * [Cleaning up messy dates in JSON](https://alexwlchan.net/2025/messy-dates-in-json/)
22
+
23
+ ## Versioning
24
+
25
+ This library is monotically versioned.
26
+ I'll try not to break anything between releases, but I make no guarantees of back-compatibility.
27
+
28
+ I'm making this public because it's convenient for me, and you might find useful code here, but be aware this may not be entirely stable.
29
+
30
+ ## Usage
31
+
32
+ All the functions are available in the `chives` namespace.
33
+
34
+ See the docstrings on individual functions for usage descriptions.
35
+
36
+ ## Installation
37
+
38
+ If you want to use this in your project, I recommend copying the relevant function and test into your codebase (with a link back to this repo).
39
+
40
+ Alternatively, you can install the package from PyPI:
41
+
42
+ ```console
43
+ $ pip install alexwlchan-chives
44
+ ```
45
+
46
+ ## Development
47
+
48
+ If you want to make changes to the library, there are instructions in [CONTRIBUTING.md](./CONTRIBUTING.md).
49
+
50
+ ## License
51
+
52
+ MIT.
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools >= 65",
4
+ ]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "alexwlchan-chives"
9
+ description = "Utility functions for working with my local media archives"
10
+ readme = "README.md"
11
+ authors = [
12
+ {name = "Alex Chan", email = "alex@alexwlchan.net"},
13
+ ]
14
+ maintainers = [
15
+ {name = "Alex Chan", email="alex@alexwlchan.net"},
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Programming Language :: Python :: 3.13",
20
+ ]
21
+ requires-python = ">=3.13"
22
+ dependencies = []
23
+ dynamic = ["version"]
24
+ license = "MIT"
25
+
26
+ [project.urls]
27
+ "Homepage" = "https://github.com/alexwlchan/chives"
28
+ "Changelog" = "https://github.com/alexwlchan/chives/blob/main/CHANGELOG.md"
29
+
30
+ [tool.setuptools.dynamic]
31
+ version = {attr = "chives.__version__"}
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+
36
+ [tool.setuptools.package-data]
37
+ nitrate = ["py.typed"]
38
+
39
+ [tool.coverage.run]
40
+ branch = true
41
+ source = ["chives", "tests",]
42
+
43
+ [tool.coverage.report]
44
+ show_missing = true
45
+ skip_covered = true
46
+ fail_under = 100
47
+
48
+ [tool.pytest.ini_options]
49
+ filterwarnings = ["error"]
50
+
51
+ [tool.mypy]
52
+ mypy_path = "src"
53
+ strict = true
54
+
55
+ [tool.ruff.lint]
56
+ select = ["D"]
57
+ ignore = ["D200", "D203", "D204", "D205", "D212", "D401"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: alexwlchan-chives
3
+ Version: 1
4
+ Summary: Utility functions for working with my local media archives
5
+ Author-email: Alex Chan <alex@alexwlchan.net>
6
+ Maintainer-email: Alex Chan <alex@alexwlchan.net>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/alexwlchan/chives
9
+ Project-URL: Changelog, https://github.com/alexwlchan/chives/blob/main/CHANGELOG.md
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Requires-Python: >=3.13
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ # chives
18
+
19
+ chives is a collection of Python functions for working with my local
20
+ media archives.
21
+
22
+ I store a lot of media archives as [static websites][static-sites], and I use Python scripts to manage the sites.
23
+ This includes:
24
+
25
+ * Verifying every file that's described in the metadata is stored correctly
26
+ * Downloading pages from sites I want to bookmark
27
+ * Checking the quality and consistency of my metadata
28
+
29
+ This package has some functions I share across multiple archives/sites.
30
+
31
+ [static-sites]: https://alexwlchan.net/2024/static-websites/
32
+
33
+ ## References
34
+
35
+ I've written blog posts about some of the code in this repo:
36
+
37
+ * [Cleaning up messy dates in JSON](https://alexwlchan.net/2025/messy-dates-in-json/)
38
+
39
+ ## Versioning
40
+
41
+ This library is monotically versioned.
42
+ I'll try not to break anything between releases, but I make no guarantees of back-compatibility.
43
+
44
+ I'm making this public because it's convenient for me, and you might find useful code here, but be aware this may not be entirely stable.
45
+
46
+ ## Usage
47
+
48
+ All the functions are available in the `chives` namespace.
49
+
50
+ See the docstrings on individual functions for usage descriptions.
51
+
52
+ ## Installation
53
+
54
+ If you want to use this in your project, I recommend copying the relevant function and test into your codebase (with a link back to this repo).
55
+
56
+ Alternatively, you can install the package from PyPI:
57
+
58
+ ```console
59
+ $ pip install alexwlchan-chives
60
+ ```
61
+
62
+ ## Development
63
+
64
+ If you want to make changes to the library, there are instructions in [CONTRIBUTING.md](./CONTRIBUTING.md).
65
+
66
+ ## License
67
+
68
+ MIT.
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/alexwlchan_chives.egg-info/PKG-INFO
5
+ src/alexwlchan_chives.egg-info/SOURCES.txt
6
+ src/alexwlchan_chives.egg-info/dependency_links.txt
7
+ src/alexwlchan_chives.egg-info/top_level.txt
8
+ src/chives/__init__.py
9
+ src/chives/py.typed
10
+ src/chives/timestamps.py
11
+ tests/test_timestamps.py
@@ -0,0 +1,28 @@
1
+ """
2
+ chives is a collection of Python functions for working with my local
3
+ media archives.
4
+
5
+ I store a lot of media archives as static websites [1][2], and I use
6
+ Python scripts to manage my media. This package has some functions
7
+ I share across multiple sites.
8
+
9
+ [1]: https://alexwlchan.net/2024/static-websites/
10
+ [2]: https://alexwlchan.net/2025/mildly-dynamic-websites/
11
+
12
+ """
13
+
14
+ from .timestamps import (
15
+ find_all_dates,
16
+ date_matches_format,
17
+ date_matches_any_format,
18
+ reformat_date,
19
+ )
20
+
21
+ __version__ = "1"
22
+
23
+ __all__ = [
24
+ "date_matches_any_format",
25
+ "date_matches_format",
26
+ "find_all_dates",
27
+ "reformat_date",
28
+ ]
File without changes
@@ -0,0 +1,73 @@
1
+ """
2
+ Functions for interacting with timestamps and date strings.
3
+
4
+ References:
5
+ * https://alexwlchan.net/2025/messy-dates-in-json/
6
+
7
+ """
8
+
9
+ from collections.abc import Iterable, Iterator
10
+ from datetime import datetime, timezone
11
+ from typing import Any
12
+
13
+
14
+ def find_all_dates(json_value: Any) -> Iterator[tuple[dict[str, Any], str, str]]:
15
+ """
16
+ Find all the timestamps in a heavily nested JSON object.
17
+
18
+ This function looks for any JSON objects with a key-value pair
19
+ where the key starts with `date_` and the value is a string, and
20
+ emits a 3-tuple:
21
+
22
+ * the JSON object
23
+ * the key
24
+ * the value
25
+
26
+ """
27
+ if isinstance(json_value, dict):
28
+ for key, value in json_value.items():
29
+ if (
30
+ isinstance(key, str)
31
+ and key.startswith("date_")
32
+ and isinstance(value, str)
33
+ ):
34
+ yield json_value, key, value
35
+ else:
36
+ yield from find_all_dates(value)
37
+ elif isinstance(json_value, list):
38
+ for value in json_value:
39
+ yield from find_all_dates(value)
40
+
41
+
42
+ def date_matches_format(date_string: str, format: str) -> bool:
43
+ """
44
+ Returns True if `date_string` can be parsed as a datetime
45
+ using `format`, False otherwise.
46
+ """
47
+ try:
48
+ datetime.strptime(date_string, format)
49
+ return True
50
+ except ValueError:
51
+ return False
52
+
53
+
54
+ def date_matches_any_format(date_string: str, formats: Iterable[str]) -> bool:
55
+ """
56
+ Returns True if `date_string` can be parsed as a datetime
57
+ with any of the `formats`, False otherwise.
58
+ """
59
+ return any(date_matches_format(date_string, fmt) for fmt in formats)
60
+
61
+
62
+ def reformat_date(s: str, /, orig_fmt: str) -> str:
63
+ """
64
+ Reformat a date to one of my desired formats.
65
+ """
66
+ if "%Z" in orig_fmt:
67
+ d = datetime.strptime(s, orig_fmt)
68
+ else:
69
+ d = datetime.strptime(s.replace("Z", "+0000"), orig_fmt.replace("Z", "%z"))
70
+ d = d.replace(microsecond=0)
71
+ if d.tzinfo is None:
72
+ d = d.replace(tzinfo=timezone.utc)
73
+ return d.strftime("%Y-%m-%dT%H:%M:%S%z").replace("+0000", "Z")
@@ -0,0 +1,62 @@
1
+ """Tests for `chives.timestamps`."""
2
+
3
+ import json
4
+
5
+ import pytest
6
+
7
+ from chives import date_matches_any_format, find_all_dates, reformat_date
8
+
9
+
10
+ def test_find_all_dates() -> None:
11
+ """find_all_dates finds all the nested dates in a JSON object."""
12
+ json_value = json.loads("""{
13
+ "doc1": {"id": "1", "date_created": "2025-10-14T05:34:07+0000"},
14
+ "shapes": [
15
+ {"color": "blue", "date_saved": "2015-03-01 23:34:39 +00:00"},
16
+ {"color": "yellow", "date_saved": "2013-9-21 13:43:00Z", "is_square": true},
17
+ {"color": "green", "date_saved": null}
18
+ ],
19
+ "date_verified": "2024-08-30"
20
+ }""")
21
+
22
+ assert list(find_all_dates(json_value)) == [
23
+ (
24
+ {"id": "1", "date_created": "2025-10-14T05:34:07+0000"},
25
+ "date_created",
26
+ "2025-10-14T05:34:07+0000",
27
+ ),
28
+ (
29
+ {"color": "blue", "date_saved": "2015-03-01 23:34:39 +00:00"},
30
+ "date_saved",
31
+ "2015-03-01 23:34:39 +00:00",
32
+ ),
33
+ (
34
+ {"color": "yellow", "date_saved": "2013-9-21 13:43:00Z", "is_square": True},
35
+ "date_saved",
36
+ "2013-9-21 13:43:00Z",
37
+ ),
38
+ (json_value, "date_verified", "2024-08-30"),
39
+ ]
40
+
41
+
42
+ def test_date_matches_any_format() -> None:
43
+ """
44
+ Tests for `date_matches_any_format`.
45
+ """
46
+ assert date_matches_any_format(
47
+ "2001-01-01", formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S%z"]
48
+ )
49
+ assert not date_matches_any_format("2001-01-01", formats=["%Y-%m-%dT%H:%M:%S%z"])
50
+
51
+
52
+ @pytest.mark.parametrize(
53
+ "s, orig_fmt, formatted_date",
54
+ [
55
+ ("2025-11-12T15:34:39.570Z", "%Y-%m-%dT%H:%M:%S.%fZ", "2025-11-12T15:34:39Z"),
56
+ ("2025-03-12 09:57:03", "%Y-%m-%d %H:%M:%S", "2025-03-12T09:57:03Z"),
57
+ ("2016-02-25 05:28:35 GMT", "%Y-%m-%d %H:%M:%S %Z", "2016-02-25T05:28:35Z"),
58
+ ],
59
+ )
60
+ def test_reformat_date(s: str, orig_fmt: str, formatted_date: str) -> None:
61
+ """Tests for `reformat_date`."""
62
+ assert reformat_date(s, orig_fmt) == formatted_date