batutils 2.1.0__tar.gz → 2.2.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.
Files changed (81) hide show
  1. {batutils-2.1.0 → batutils-2.2.1}/LICENSE +1 -1
  2. batutils-2.2.1/PKG-INFO +147 -0
  3. batutils-2.2.1/README.md +95 -0
  4. {batutils-2.1.0 → batutils-2.2.1}/batutils/__init__.py +2 -2
  5. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/casting/_any.py +1 -1
  6. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/casting/_instant.py +6 -1
  7. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/casting/_timestamp.py +5 -12
  8. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/casting/_evals.py +62 -7
  9. batutils-2.2.1/batutils.egg-info/PKG-INFO +147 -0
  10. {batutils-2.1.0 → batutils-2.2.1}/batutils.egg-info/top_level.txt +1 -0
  11. {batutils-2.1.0 → batutils-2.2.1}/pyproject.toml +2 -2
  12. batutils-2.1.0/PKG-INFO +0 -125
  13. batutils-2.1.0/README.md +0 -74
  14. batutils-2.1.0/batutils.egg-info/PKG-INFO +0 -125
  15. {batutils-2.1.0 → batutils-2.2.1}/batutils/_common/__init__.py +0 -0
  16. {batutils-2.1.0 → batutils-2.2.1}/batutils/_common/_decode.py +0 -0
  17. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/__init__.py +0 -0
  18. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/casting/__init__.py +0 -0
  19. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/casting/_filetime.py +0 -0
  20. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/casting/_string.py +0 -0
  21. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/formatting/__init__.py +0 -0
  22. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/formatting/_delta.py +0 -0
  23. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/formatting/_serialize.py +0 -0
  24. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/parsers/__init__.py +0 -0
  25. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/parsers/_string.py +0 -0
  26. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/parsers/_timedelta.py +0 -0
  27. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/shortcuts/__init__.py +0 -0
  28. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/shortcuts/_aliases.py +0 -0
  29. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/shortcuts/_checkers.py +0 -0
  30. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/shortcuts/_finders.py +0 -0
  31. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/structs/__init__.py +0 -0
  32. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/structs/_base.py +0 -0
  33. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/structs/_deltasplit.py +0 -0
  34. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/structs/_types.py +0 -0
  35. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/structs/aliases.py +0 -0
  36. {batutils-2.1.0 → batutils-2.2.1}/batutils/dtu/structs/consts.py +0 -0
  37. {batutils-2.1.0 → batutils-2.2.1}/batutils/structs/__init__.py +0 -0
  38. {batutils-2.1.0 → batutils-2.2.1}/batutils/structs/_base.py +0 -0
  39. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/__init__.py +0 -0
  40. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/casting/__init__.py +0 -0
  41. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/casting/_currency.py +0 -0
  42. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/casting/_ip.py +0 -0
  43. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/casting/_mask.py +0 -0
  44. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/casting/_params.py +0 -0
  45. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/casting/_serialize.py +0 -0
  46. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/comparison/__init__.py +0 -0
  47. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/comparison/_mixin.py +0 -0
  48. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/insp/__init__.py +0 -0
  49. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/insp/_insp_old.py +0 -0
  50. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/insp/_typecheck.py +0 -0
  51. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/iters/__init__.py +0 -0
  52. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/iters/_attrs.py +0 -0
  53. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/iters/_dicts.py +0 -0
  54. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/iters/_group.py +0 -0
  55. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/iters/_lists.py +0 -0
  56. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/size/__init__.py +0 -0
  57. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/size/_total.py +0 -0
  58. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/strimp/__init__.py +0 -0
  59. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/strimp/_getters.py +0 -0
  60. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/string/__init__.py +0 -0
  61. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/string/_string.py +0 -0
  62. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/__init__.py +0 -0
  63. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/_base.py +0 -0
  64. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/empty/__init__.py +0 -0
  65. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/empty/_empty.py +0 -0
  66. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/enumex/__init__.py +0 -0
  67. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/enumex/_enumextra.py +0 -0
  68. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/fluid/__init__.py +0 -0
  69. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/fluid/_attrfluid.py +0 -0
  70. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/sentience/__init__.py +0 -0
  71. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/sentience/_builder.py +0 -0
  72. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/serializers/__init__.py +0 -0
  73. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/serializers/_json.py +0 -0
  74. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/serializers/_jsonsafe.py +0 -0
  75. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/serializers/_universal.py +0 -0
  76. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/singleton/__init__.py +0 -0
  77. {batutils-2.1.0 → batutils-2.2.1}/batutils/tpu/structs/singleton/_singleton.py +0 -0
  78. {batutils-2.1.0 → batutils-2.2.1}/batutils.egg-info/SOURCES.txt +0 -0
  79. {batutils-2.1.0 → batutils-2.2.1}/batutils.egg-info/dependency_links.txt +0 -0
  80. {batutils-2.1.0 → batutils-2.2.1}/setup.cfg +0 -0
  81. {batutils-2.1.0 → batutils-2.2.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024-2025 Thordur Matthiasson
3
+ Copyright (c) 2024-2026 Thordur Matthiasson
4
4
  Copyright (c) 2013-2024 CCP Games
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: batutils
3
+ Version: 2.2.1
4
+ Summary: Team Batcave's utilities for date and/or time objects and values and messing with types and so much more.
5
+ Author-email: Thordur Matthiasson <doddi79@gmail.com>, John Aldis <johnaldis@ccpgames.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024-2026 Thordur Matthiasson
9
+ Copyright (c) 2013-2024 CCP Games
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Homepage, https://github.com/doddi79/batutils
30
+ Project-URL: Documentation, https://github.com/doddi79/batutils/blob/main/README.md
31
+ Project-URL: Repository, https://github.com/doddi79/batutils.git
32
+ Project-URL: Issues, https://github.com/doddi79/batutils/issues
33
+ Project-URL: Changelog, https://github.com/doddi79/batutils/blob/main/CHANGELOG.md
34
+ Keywords: datetimeutils,datetime,typeutils,type,singleton,date,time,timespan,tools,batcave,utils
35
+ Classifier: Development Status :: 5 - Production/Stable
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Intended Audience :: Developers
38
+ Classifier: Programming Language :: Python
39
+ Classifier: Programming Language :: Python :: 3
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Programming Language :: Python :: 3.14
45
+ Classifier: Topic :: Software Development
46
+ Classifier: Topic :: Software Development :: Libraries
47
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
48
+ Classifier: Topic :: Utilities
49
+ Description-Content-Type: text/markdown
50
+ License-File: LICENSE
51
+ Dynamic: license-file
52
+
53
+ # batutils - Team Batcave Python Toolkit
54
+
55
+ [![PyPI](https://img.shields.io/pypi/v/batutils)](https://pypi.org/project/batutils/)
56
+
57
+ A grab-bag of small, dependency-free Python utilities for working with dates
58
+ and times and for everyday type juggling, originally written by Team Batcave
59
+ at CCP Games.
60
+
61
+ This is a fork of [`ccptools`](https://github.com/ccpgames/ccptools), kept alive
62
+ beyond the original team's lifespan. Our two most commonly used internal packages,
63
+ `datetimeutils` and `typeutils`, had names that were too generic for PyPI and were
64
+ used in 80-90% of our other projects, so they were smooshed together into one
65
+ package.
66
+
67
+ ```bash
68
+ pip install batutils
69
+ ```
70
+
71
+ Requires Python 3.10 or newer.
72
+
73
+ ## What's inside
74
+
75
+ | Submodule | Formerly | What it is | Docs |
76
+ |-----------|----------|------------|------|
77
+ | `batutils.dtu` | `datetimeutils` | Casting, parsing, formatting and shortcuts for date/time values | [Date/Time Utils](batutils/dtu/README.md) |
78
+ | `batutils.tpu` | `typeutils` | Safe type evaluation, nested dict/list helpers, enums, singletons, serializers and more | [Type Utils](batutils/tpu/README.md) |
79
+ | `batutils.structs` | | One-line "usual imports" for your modules | [below](#common-imports) |
80
+
81
+ **Looking for a specific function?** Use the task-oriented
82
+ [Public API index](docs/api-index.md).
83
+
84
+ ## Quick taste
85
+
86
+ ```python
87
+ from batutils import dtu
88
+ from batutils.tpu.casting import int_eval, bool_eval
89
+ from batutils.tpu.iters import nested_get
90
+
91
+ dtu.any_to_datetime('2024-04-02 13:47:25') # datetime(2024, 4, 2, 13, 47, 25)
92
+ dtu.agostr(dtu.ago(days=5)) # '5 days'
93
+ int_eval('0x10') # 16
94
+ bool_eval('Yes') # True
95
+ nested_get({'a': {'b': [1, 2, 3]}}, ('a', 'b', -1)) # 3
96
+ ```
97
+
98
+ ## Common imports
99
+
100
+ `from batutils.structs import *` pulls in the imports that nearly every file in
101
+ our projects needs:
102
+
103
+ ```python
104
+ from typing import * # for type annotations
105
+ import abc # interfaces (Abstract Base Classes)
106
+ import dataclasses # dataclass structs
107
+ import decimal # anything involving money
108
+ import enum # also used for struct creation
109
+ import logging # used pretty much everywhere
110
+ import re # used surprisingly frequently
111
+ import time # very commonly used
112
+ ```
113
+
114
+ `datetime` itself is not included. Instead you get the date/time aliases from
115
+ `batutils.dtu.structs.aliases`:
116
+
117
+ ```python
118
+ Date = datetime.date
119
+ Time = datetime.time
120
+ Datetime = datetime.datetime
121
+ TimeDelta = datetime.timedelta
122
+ TzInfo = datetime.tzinfo
123
+ TimeZone = datetime.timezone
124
+ ```
125
+
126
+ Plus a few of the most commonly used helpers from the type utils:
127
+
128
+ - the `Singleton` metaclass
129
+ - `Empty`, `EmptyDict` and `if_empty`
130
+ - the `EnumEx` base class for enums (adds the `from_any` class method)
131
+
132
+ So one line covers roughly 90% of the imports we need in any given file:
133
+
134
+ ```python
135
+ from batutils.structs import *
136
+ ```
137
+
138
+ ## Changes from `ccptools`
139
+
140
+ The `legacyapi` submodule has been removed. See the [changelog](CHANGELOG.md)
141
+ for the full history.
142
+
143
+ ## Contributing and license
144
+
145
+ Issues and pull requests are welcome at
146
+ [github.com/doddi79/batutils](https://github.com/doddi79/batutils). Released
147
+ under the [MIT License](LICENSE).
@@ -0,0 +1,95 @@
1
+ # batutils - Team Batcave Python Toolkit
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/batutils)](https://pypi.org/project/batutils/)
4
+
5
+ A grab-bag of small, dependency-free Python utilities for working with dates
6
+ and times and for everyday type juggling, originally written by Team Batcave
7
+ at CCP Games.
8
+
9
+ This is a fork of [`ccptools`](https://github.com/ccpgames/ccptools), kept alive
10
+ beyond the original team's lifespan. Our two most commonly used internal packages,
11
+ `datetimeutils` and `typeutils`, had names that were too generic for PyPI and were
12
+ used in 80-90% of our other projects, so they were smooshed together into one
13
+ package.
14
+
15
+ ```bash
16
+ pip install batutils
17
+ ```
18
+
19
+ Requires Python 3.10 or newer.
20
+
21
+ ## What's inside
22
+
23
+ | Submodule | Formerly | What it is | Docs |
24
+ |-----------|----------|------------|------|
25
+ | `batutils.dtu` | `datetimeutils` | Casting, parsing, formatting and shortcuts for date/time values | [Date/Time Utils](batutils/dtu/README.md) |
26
+ | `batutils.tpu` | `typeutils` | Safe type evaluation, nested dict/list helpers, enums, singletons, serializers and more | [Type Utils](batutils/tpu/README.md) |
27
+ | `batutils.structs` | | One-line "usual imports" for your modules | [below](#common-imports) |
28
+
29
+ **Looking for a specific function?** Use the task-oriented
30
+ [Public API index](docs/api-index.md).
31
+
32
+ ## Quick taste
33
+
34
+ ```python
35
+ from batutils import dtu
36
+ from batutils.tpu.casting import int_eval, bool_eval
37
+ from batutils.tpu.iters import nested_get
38
+
39
+ dtu.any_to_datetime('2024-04-02 13:47:25') # datetime(2024, 4, 2, 13, 47, 25)
40
+ dtu.agostr(dtu.ago(days=5)) # '5 days'
41
+ int_eval('0x10') # 16
42
+ bool_eval('Yes') # True
43
+ nested_get({'a': {'b': [1, 2, 3]}}, ('a', 'b', -1)) # 3
44
+ ```
45
+
46
+ ## Common imports
47
+
48
+ `from batutils.structs import *` pulls in the imports that nearly every file in
49
+ our projects needs:
50
+
51
+ ```python
52
+ from typing import * # for type annotations
53
+ import abc # interfaces (Abstract Base Classes)
54
+ import dataclasses # dataclass structs
55
+ import decimal # anything involving money
56
+ import enum # also used for struct creation
57
+ import logging # used pretty much everywhere
58
+ import re # used surprisingly frequently
59
+ import time # very commonly used
60
+ ```
61
+
62
+ `datetime` itself is not included. Instead you get the date/time aliases from
63
+ `batutils.dtu.structs.aliases`:
64
+
65
+ ```python
66
+ Date = datetime.date
67
+ Time = datetime.time
68
+ Datetime = datetime.datetime
69
+ TimeDelta = datetime.timedelta
70
+ TzInfo = datetime.tzinfo
71
+ TimeZone = datetime.timezone
72
+ ```
73
+
74
+ Plus a few of the most commonly used helpers from the type utils:
75
+
76
+ - the `Singleton` metaclass
77
+ - `Empty`, `EmptyDict` and `if_empty`
78
+ - the `EnumEx` base class for enums (adds the `from_any` class method)
79
+
80
+ So one line covers roughly 90% of the imports we need in any given file:
81
+
82
+ ```python
83
+ from batutils.structs import *
84
+ ```
85
+
86
+ ## Changes from `ccptools`
87
+
88
+ The `legacyapi` submodule has been removed. See the [changelog](CHANGELOG.md)
89
+ for the full history.
90
+
91
+ ## Contributing and license
92
+
93
+ Issues and pull requests are welcome at
94
+ [github.com/doddi79/batutils](https://github.com/doddi79/batutils). Released
95
+ under the [MIT License](LICENSE).
@@ -1,5 +1,5 @@
1
- __version__ = '2.1.0'
1
+ __version__ = '2.2.1'
2
2
 
3
3
  __author__ = 'Thordur Matthiasson <doddi79@gmail.com>'
4
4
  __license__ = 'MIT License'
5
- __copyright__ = 'Copyright 2024-2025 - Thordur Matthiasson, Copyright 2013-2024 - CCP Games ehf'
5
+ __copyright__ = 'Copyright 2024-2026 - Thordur Matthiasson, Copyright 2013-2024 - CCP Games ehf'
@@ -90,7 +90,7 @@ def any_to_datetime(temporal_object: T_TEMPORAL_VALUE,
90
90
  # This range means that the number, if treated as an instant,
91
91
  # represents a datetime within 1000 years to/from now so it's the
92
92
  # second most likely bet!
93
- return timestamp_to_datetime(temporal_object)
93
+ return instant_to_datetime(temporal_object)
94
94
 
95
95
  # This number is so large that it's most likely a filetime!
96
96
  return filetime_to_datetime(temporal_object)
@@ -16,7 +16,12 @@ def instant_to_datetime(milliseconds_since_epoch: T_NUMBER, minmax_on_fail: bool
16
16
  negative)
17
17
  :return: A Python Datetime
18
18
  """
19
- return timestamp_to_datetime(milliseconds_since_epoch / 1000., minmax_on_fail)
19
+ try:
20
+ return Datetime(1970, 1, 1) + TimeDelta(milliseconds=milliseconds_since_epoch)
21
+ except OverflowError:
22
+ if minmax_on_fail:
23
+ return Datetime.max if milliseconds_since_epoch > 0 else Datetime.min
24
+ raise
20
25
 
21
26
 
22
27
  def datetime_to_instant(dt: T_DATE_VALUE) -> int:
@@ -17,18 +17,11 @@ def timestamp_to_datetime(seconds_since_epoch: T_NUMBER, minmax_on_fail: bool =
17
17
  :return: A Python Datetime
18
18
  """
19
19
  try:
20
- return Datetime.fromtimestamp(seconds_since_epoch)
21
- except OSError:
22
- try:
23
- return Datetime(1970, 1, 1, 0, 0, 0, 0) + TimeDelta(seconds=seconds_since_epoch)
24
- except OverflowError:
25
- if minmax_on_fail:
26
- if seconds_since_epoch > 0:
27
- return Datetime.max
28
- else:
29
- return Datetime.min
30
- else:
31
- raise
20
+ return Datetime(1970, 1, 1) + TimeDelta(seconds=seconds_since_epoch)
21
+ except OverflowError:
22
+ if minmax_on_fail:
23
+ return Datetime.max if seconds_since_epoch > 0 else Datetime.min
24
+ raise
32
25
 
33
26
 
34
27
  def datetime_to_timestamp(dt: T_DATE_VALUE) -> float:
@@ -4,6 +4,8 @@ __all__ = [
4
4
  'bool_eval',
5
5
  'enum_eval',
6
6
 
7
+ 'enum_value_lookup_map',
8
+
7
9
  'decode_bytes',
8
10
  ]
9
11
 
@@ -223,9 +225,24 @@ def enum_eval(value: Any,
223
225
  return default
224
226
 
225
227
  if isinstance(value, str):
228
+ # First we check the member name (normalized)
226
229
  hit = {n.lower(): m for n, m in enum_class.__members__.items()}.get(value.strip().lower(), None)
227
230
  if hit is not None:
228
231
  return hit
232
+
233
+ try:
234
+ value_lookup = enum_value_lookup_map(enum_class, allow_collision=False)
235
+ except KeyError:
236
+ if raise_on_fail:
237
+ raise
238
+ return default
239
+
240
+ # This covers just about everything, except where a numeric value was given as a string
241
+ hit = value_lookup.get(value, None)
242
+ if hit is not None:
243
+ return hit
244
+
245
+ if isinstance(value, str):
229
246
  try:
230
247
  value = int(value, base=0)
231
248
  except ValueError:
@@ -233,15 +250,53 @@ def enum_eval(value: Any,
233
250
  raise ValueError(f'{value!r} is not a valid {enum_class.__name__}')
234
251
  return default
235
252
 
236
- if isinstance(value, int):
237
- try:
238
- return enum_class(value)
239
- except ValueError:
240
- if raise_on_fail:
241
- raise
242
- return default
253
+ # Retry after casting
254
+ hit = value_lookup.get(value, None)
255
+ if hit is not None:
256
+ return hit
243
257
 
244
258
  if raise_on_fail:
245
259
  raise ValueError(f'{value!r} is not a valid {enum_class.__name__}')
246
260
 
247
261
  return default
262
+
263
+
264
+ def enum_value_lookup_map(enum_class: Type[_T_ENUM_CLASS], allow_collision: bool = False) -> Dict[Any, _T_ENUM_CLASS]:
265
+ """Returns a dict of all the values of an enum class as keys and their respective members as values for reverse
266
+ value lookups.
267
+
268
+ This also breaks down the individual values in the value tuple of a multi-value enum and includes each of those as
269
+ a lookup key as well. By default, this will raise a `KeyError` if any individual value is repeated with a different
270
+ member as its target but this can be suppressed by setting `allow_collision=True`
271
+
272
+ Example:
273
+ ```python
274
+ import enum
275
+
276
+ class MyGoodEnum(enum.Enum):
277
+ FOO = ('one', 'one'), # This is fine
278
+ BAR = ('something', 'else'),
279
+
280
+ class MyBadEnum(enum.Enum):
281
+ FOO = ('one', 'something'),
282
+ BAR = ('one', 'else'), # This will result in a collision with FOO's 'one' and raise a KeyError by default
283
+ ```
284
+ """
285
+ d = {}
286
+
287
+ def _collision_free_add(value: Any, enum_member: _T_ENUM_CLASS):
288
+ if allow_collision:
289
+ d[value] = enum_member
290
+ else:
291
+ if value in d:
292
+ if d[value] != enum_member:
293
+ raise KeyError(f'value lookup map for {enum_class.__name__} resulted in a collision between {d[value]} and {enum_member} for the value {value!r}')
294
+ else:
295
+ d[value] = enum_member
296
+
297
+ for _, member in enum_class.__members__.items():
298
+ _collision_free_add(member.value, member)
299
+ if isinstance(member.value, tuple):
300
+ for v in member.value:
301
+ _collision_free_add(v, member)
302
+ return d
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: batutils
3
+ Version: 2.2.1
4
+ Summary: Team Batcave's utilities for date and/or time objects and values and messing with types and so much more.
5
+ Author-email: Thordur Matthiasson <doddi79@gmail.com>, John Aldis <johnaldis@ccpgames.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024-2026 Thordur Matthiasson
9
+ Copyright (c) 2013-2024 CCP Games
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Homepage, https://github.com/doddi79/batutils
30
+ Project-URL: Documentation, https://github.com/doddi79/batutils/blob/main/README.md
31
+ Project-URL: Repository, https://github.com/doddi79/batutils.git
32
+ Project-URL: Issues, https://github.com/doddi79/batutils/issues
33
+ Project-URL: Changelog, https://github.com/doddi79/batutils/blob/main/CHANGELOG.md
34
+ Keywords: datetimeutils,datetime,typeutils,type,singleton,date,time,timespan,tools,batcave,utils
35
+ Classifier: Development Status :: 5 - Production/Stable
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Intended Audience :: Developers
38
+ Classifier: Programming Language :: Python
39
+ Classifier: Programming Language :: Python :: 3
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Programming Language :: Python :: 3.14
45
+ Classifier: Topic :: Software Development
46
+ Classifier: Topic :: Software Development :: Libraries
47
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
48
+ Classifier: Topic :: Utilities
49
+ Description-Content-Type: text/markdown
50
+ License-File: LICENSE
51
+ Dynamic: license-file
52
+
53
+ # batutils - Team Batcave Python Toolkit
54
+
55
+ [![PyPI](https://img.shields.io/pypi/v/batutils)](https://pypi.org/project/batutils/)
56
+
57
+ A grab-bag of small, dependency-free Python utilities for working with dates
58
+ and times and for everyday type juggling, originally written by Team Batcave
59
+ at CCP Games.
60
+
61
+ This is a fork of [`ccptools`](https://github.com/ccpgames/ccptools), kept alive
62
+ beyond the original team's lifespan. Our two most commonly used internal packages,
63
+ `datetimeutils` and `typeutils`, had names that were too generic for PyPI and were
64
+ used in 80-90% of our other projects, so they were smooshed together into one
65
+ package.
66
+
67
+ ```bash
68
+ pip install batutils
69
+ ```
70
+
71
+ Requires Python 3.10 or newer.
72
+
73
+ ## What's inside
74
+
75
+ | Submodule | Formerly | What it is | Docs |
76
+ |-----------|----------|------------|------|
77
+ | `batutils.dtu` | `datetimeutils` | Casting, parsing, formatting and shortcuts for date/time values | [Date/Time Utils](batutils/dtu/README.md) |
78
+ | `batutils.tpu` | `typeutils` | Safe type evaluation, nested dict/list helpers, enums, singletons, serializers and more | [Type Utils](batutils/tpu/README.md) |
79
+ | `batutils.structs` | | One-line "usual imports" for your modules | [below](#common-imports) |
80
+
81
+ **Looking for a specific function?** Use the task-oriented
82
+ [Public API index](docs/api-index.md).
83
+
84
+ ## Quick taste
85
+
86
+ ```python
87
+ from batutils import dtu
88
+ from batutils.tpu.casting import int_eval, bool_eval
89
+ from batutils.tpu.iters import nested_get
90
+
91
+ dtu.any_to_datetime('2024-04-02 13:47:25') # datetime(2024, 4, 2, 13, 47, 25)
92
+ dtu.agostr(dtu.ago(days=5)) # '5 days'
93
+ int_eval('0x10') # 16
94
+ bool_eval('Yes') # True
95
+ nested_get({'a': {'b': [1, 2, 3]}}, ('a', 'b', -1)) # 3
96
+ ```
97
+
98
+ ## Common imports
99
+
100
+ `from batutils.structs import *` pulls in the imports that nearly every file in
101
+ our projects needs:
102
+
103
+ ```python
104
+ from typing import * # for type annotations
105
+ import abc # interfaces (Abstract Base Classes)
106
+ import dataclasses # dataclass structs
107
+ import decimal # anything involving money
108
+ import enum # also used for struct creation
109
+ import logging # used pretty much everywhere
110
+ import re # used surprisingly frequently
111
+ import time # very commonly used
112
+ ```
113
+
114
+ `datetime` itself is not included. Instead you get the date/time aliases from
115
+ `batutils.dtu.structs.aliases`:
116
+
117
+ ```python
118
+ Date = datetime.date
119
+ Time = datetime.time
120
+ Datetime = datetime.datetime
121
+ TimeDelta = datetime.timedelta
122
+ TzInfo = datetime.tzinfo
123
+ TimeZone = datetime.timezone
124
+ ```
125
+
126
+ Plus a few of the most commonly used helpers from the type utils:
127
+
128
+ - the `Singleton` metaclass
129
+ - `Empty`, `EmptyDict` and `if_empty`
130
+ - the `EnumEx` base class for enums (adds the `from_any` class method)
131
+
132
+ So one line covers roughly 90% of the imports we need in any given file:
133
+
134
+ ```python
135
+ from batutils.structs import *
136
+ ```
137
+
138
+ ## Changes from `ccptools`
139
+
140
+ The `legacyapi` submodule has been removed. See the [changelog](CHANGELOG.md)
141
+ for the full history.
142
+
143
+ ## Contributing and license
144
+
145
+ Issues and pull requests are welcome at
146
+ [github.com/doddi79/batutils](https://github.com/doddi79/batutils). Released
147
+ under the [MIT License](LICENSE).
@@ -22,11 +22,11 @@ classifiers = [
22
22
 
23
23
  "Programming Language :: Python",
24
24
  "Programming Language :: Python :: 3",
25
- "Programming Language :: Python :: 3.8",
26
- "Programming Language :: Python :: 3.9",
27
25
  "Programming Language :: Python :: 3.10",
28
26
  "Programming Language :: Python :: 3.11",
29
27
  "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
30
30
 
31
31
  "Topic :: Software Development",
32
32
  "Topic :: Software Development :: Libraries",
batutils-2.1.0/PKG-INFO DELETED
@@ -1,125 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: batutils
3
- Version: 2.1.0
4
- Summary: Team Batcave's utilities for date and/or time objects and values and messing with types and so much more.
5
- Author-email: Thordur Matthiasson <doddi79@gmail.com>, John Aldis <johnaldis@ccpgames.com>
6
- License: MIT License
7
-
8
- Copyright (c) 2024-2025 Thordur Matthiasson
9
- Copyright (c) 2013-2024 CCP Games
10
-
11
- Permission is hereby granted, free of charge, to any person obtaining a copy
12
- of this software and associated documentation files (the "Software"), to deal
13
- in the Software without restriction, including without limitation the rights
14
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
- copies of the Software, and to permit persons to whom the Software is
16
- furnished to do so, subject to the following conditions:
17
-
18
- The above copyright notice and this permission notice shall be included in all
19
- copies or substantial portions of the Software.
20
-
21
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
- SOFTWARE.
28
-
29
- Project-URL: Homepage, https://github.com/doddi79/batutils
30
- Project-URL: Documentation, https://github.com/doddi79/batutils/blob/main/README.md
31
- Project-URL: Repository, https://github.com/doddi79/batutils.git
32
- Project-URL: Issues, https://github.com/doddi79/batutils/issues
33
- Project-URL: Changelog, https://github.com/doddi79/batutils/blob/main/CHANGELOG.md
34
- Keywords: datetimeutils,datetime,typeutils,type,singleton,date,time,timespan,tools,batcave,utils
35
- Classifier: Development Status :: 5 - Production/Stable
36
- Classifier: License :: OSI Approved :: MIT License
37
- Classifier: Intended Audience :: Developers
38
- Classifier: Programming Language :: Python
39
- Classifier: Programming Language :: Python :: 3
40
- Classifier: Programming Language :: Python :: 3.8
41
- Classifier: Programming Language :: Python :: 3.9
42
- Classifier: Programming Language :: Python :: 3.10
43
- Classifier: Programming Language :: Python :: 3.11
44
- Classifier: Programming Language :: Python :: 3.12
45
- Classifier: Topic :: Software Development
46
- Classifier: Topic :: Software Development :: Libraries
47
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
48
- Classifier: Topic :: Utilities
49
- Description-Content-Type: text/markdown
50
- License-File: LICENSE
51
-
52
- # Team Batcave Python Toolkit
53
-
54
- The `batutils` is a fork of the `batutils` package in order to continue its
55
- development and maintenance beyond Team Batcave's lifespan.
56
-
57
- It is a smooshup of a few Python packages from the CCP Tools Team of old (Team
58
- Batcave).
59
-
60
- Our two most commonly used internal packages, called `datetimeutils` and
61
- `typeutils` were too _generically_ named for Pypi.org and since they were
62
- both used in something like 80-90% of our other projects, it made sense to
63
- just smoosh them together in one module, and thus, the `batutils` package
64
- was born.
65
-
66
- Here's the README of the [Date Time Utils](batutils/dtu/README.md) submodule.
67
-
68
- Here's the README of the [Type Utils](batutils/tpu/README.md) submodule.
69
-
70
-
71
- ### Changes from `ccptools`
72
-
73
- The `legacyapi` submodule has been removed.
74
-
75
-
76
- ## Date-Time Utils
77
-
78
- The old `datetimeutils` package is now included here as the `dtu` submodule.
79
-
80
- ```python
81
- from batutils import dtu
82
- ```
83
-
84
- ## Structs
85
-
86
- Importing `*` from the `structs` submodule will import all of the most
87
- commonly used imports in our projects:
88
- ```python
89
- from typing import * # For type annotation
90
-
91
- import abc # For interfaces (Abstract Base Classes)
92
- import dataclasses # For dataclass structs
93
- import decimal # Used whenever we're handling money
94
- import enum # Also used for struct creation
95
- import logging # Used pretty much everywhere
96
- import re # Used surprisingly frequently
97
- import time # Very commonly used
98
- ```
99
-
100
- Note that datetime is not included in this. That's because tt'll also import
101
- the aliases from the Datetime Utils (`batutils.dtu.structs.aliases`)
102
- package instead:
103
-
104
- ```python
105
- Date = datetime.date
106
- Time = datetime.time
107
- Datetime = datetime.datetime
108
- TimeDelta = datetime.timedelta
109
- TzInfo = datetime.tzinfo
110
- TimeZone = datetime.timezone
111
- ```
112
-
113
- Furthermore, it'll also include a few of the most commonly used utility
114
- classes from the Type Utils submodule:
115
-
116
- - The `Singleton` Meta Class
117
- - The `Empty` and `EmptyDict` classes as well as the `if_empty` method
118
- - The `EnumEx` base class for Enums with the `from_any` class method
119
-
120
- So in most cases we can cover something like 90% of any imports we tend to
121
- need in every Python file with a single line:
122
-
123
- ```python
124
- from batutils.structs import *
125
- ```
batutils-2.1.0/README.md DELETED
@@ -1,74 +0,0 @@
1
- # Team Batcave Python Toolkit
2
-
3
- The `batutils` is a fork of the `batutils` package in order to continue its
4
- development and maintenance beyond Team Batcave's lifespan.
5
-
6
- It is a smooshup of a few Python packages from the CCP Tools Team of old (Team
7
- Batcave).
8
-
9
- Our two most commonly used internal packages, called `datetimeutils` and
10
- `typeutils` were too _generically_ named for Pypi.org and since they were
11
- both used in something like 80-90% of our other projects, it made sense to
12
- just smoosh them together in one module, and thus, the `batutils` package
13
- was born.
14
-
15
- Here's the README of the [Date Time Utils](batutils/dtu/README.md) submodule.
16
-
17
- Here's the README of the [Type Utils](batutils/tpu/README.md) submodule.
18
-
19
-
20
- ### Changes from `ccptools`
21
-
22
- The `legacyapi` submodule has been removed.
23
-
24
-
25
- ## Date-Time Utils
26
-
27
- The old `datetimeutils` package is now included here as the `dtu` submodule.
28
-
29
- ```python
30
- from batutils import dtu
31
- ```
32
-
33
- ## Structs
34
-
35
- Importing `*` from the `structs` submodule will import all of the most
36
- commonly used imports in our projects:
37
- ```python
38
- from typing import * # For type annotation
39
-
40
- import abc # For interfaces (Abstract Base Classes)
41
- import dataclasses # For dataclass structs
42
- import decimal # Used whenever we're handling money
43
- import enum # Also used for struct creation
44
- import logging # Used pretty much everywhere
45
- import re # Used surprisingly frequently
46
- import time # Very commonly used
47
- ```
48
-
49
- Note that datetime is not included in this. That's because tt'll also import
50
- the aliases from the Datetime Utils (`batutils.dtu.structs.aliases`)
51
- package instead:
52
-
53
- ```python
54
- Date = datetime.date
55
- Time = datetime.time
56
- Datetime = datetime.datetime
57
- TimeDelta = datetime.timedelta
58
- TzInfo = datetime.tzinfo
59
- TimeZone = datetime.timezone
60
- ```
61
-
62
- Furthermore, it'll also include a few of the most commonly used utility
63
- classes from the Type Utils submodule:
64
-
65
- - The `Singleton` Meta Class
66
- - The `Empty` and `EmptyDict` classes as well as the `if_empty` method
67
- - The `EnumEx` base class for Enums with the `from_any` class method
68
-
69
- So in most cases we can cover something like 90% of any imports we tend to
70
- need in every Python file with a single line:
71
-
72
- ```python
73
- from batutils.structs import *
74
- ```
@@ -1,125 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: batutils
3
- Version: 2.1.0
4
- Summary: Team Batcave's utilities for date and/or time objects and values and messing with types and so much more.
5
- Author-email: Thordur Matthiasson <doddi79@gmail.com>, John Aldis <johnaldis@ccpgames.com>
6
- License: MIT License
7
-
8
- Copyright (c) 2024-2025 Thordur Matthiasson
9
- Copyright (c) 2013-2024 CCP Games
10
-
11
- Permission is hereby granted, free of charge, to any person obtaining a copy
12
- of this software and associated documentation files (the "Software"), to deal
13
- in the Software without restriction, including without limitation the rights
14
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
- copies of the Software, and to permit persons to whom the Software is
16
- furnished to do so, subject to the following conditions:
17
-
18
- The above copyright notice and this permission notice shall be included in all
19
- copies or substantial portions of the Software.
20
-
21
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
- SOFTWARE.
28
-
29
- Project-URL: Homepage, https://github.com/doddi79/batutils
30
- Project-URL: Documentation, https://github.com/doddi79/batutils/blob/main/README.md
31
- Project-URL: Repository, https://github.com/doddi79/batutils.git
32
- Project-URL: Issues, https://github.com/doddi79/batutils/issues
33
- Project-URL: Changelog, https://github.com/doddi79/batutils/blob/main/CHANGELOG.md
34
- Keywords: datetimeutils,datetime,typeutils,type,singleton,date,time,timespan,tools,batcave,utils
35
- Classifier: Development Status :: 5 - Production/Stable
36
- Classifier: License :: OSI Approved :: MIT License
37
- Classifier: Intended Audience :: Developers
38
- Classifier: Programming Language :: Python
39
- Classifier: Programming Language :: Python :: 3
40
- Classifier: Programming Language :: Python :: 3.8
41
- Classifier: Programming Language :: Python :: 3.9
42
- Classifier: Programming Language :: Python :: 3.10
43
- Classifier: Programming Language :: Python :: 3.11
44
- Classifier: Programming Language :: Python :: 3.12
45
- Classifier: Topic :: Software Development
46
- Classifier: Topic :: Software Development :: Libraries
47
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
48
- Classifier: Topic :: Utilities
49
- Description-Content-Type: text/markdown
50
- License-File: LICENSE
51
-
52
- # Team Batcave Python Toolkit
53
-
54
- The `batutils` is a fork of the `batutils` package in order to continue its
55
- development and maintenance beyond Team Batcave's lifespan.
56
-
57
- It is a smooshup of a few Python packages from the CCP Tools Team of old (Team
58
- Batcave).
59
-
60
- Our two most commonly used internal packages, called `datetimeutils` and
61
- `typeutils` were too _generically_ named for Pypi.org and since they were
62
- both used in something like 80-90% of our other projects, it made sense to
63
- just smoosh them together in one module, and thus, the `batutils` package
64
- was born.
65
-
66
- Here's the README of the [Date Time Utils](batutils/dtu/README.md) submodule.
67
-
68
- Here's the README of the [Type Utils](batutils/tpu/README.md) submodule.
69
-
70
-
71
- ### Changes from `ccptools`
72
-
73
- The `legacyapi` submodule has been removed.
74
-
75
-
76
- ## Date-Time Utils
77
-
78
- The old `datetimeutils` package is now included here as the `dtu` submodule.
79
-
80
- ```python
81
- from batutils import dtu
82
- ```
83
-
84
- ## Structs
85
-
86
- Importing `*` from the `structs` submodule will import all of the most
87
- commonly used imports in our projects:
88
- ```python
89
- from typing import * # For type annotation
90
-
91
- import abc # For interfaces (Abstract Base Classes)
92
- import dataclasses # For dataclass structs
93
- import decimal # Used whenever we're handling money
94
- import enum # Also used for struct creation
95
- import logging # Used pretty much everywhere
96
- import re # Used surprisingly frequently
97
- import time # Very commonly used
98
- ```
99
-
100
- Note that datetime is not included in this. That's because tt'll also import
101
- the aliases from the Datetime Utils (`batutils.dtu.structs.aliases`)
102
- package instead:
103
-
104
- ```python
105
- Date = datetime.date
106
- Time = datetime.time
107
- Datetime = datetime.datetime
108
- TimeDelta = datetime.timedelta
109
- TzInfo = datetime.tzinfo
110
- TimeZone = datetime.timezone
111
- ```
112
-
113
- Furthermore, it'll also include a few of the most commonly used utility
114
- classes from the Type Utils submodule:
115
-
116
- - The `Singleton` Meta Class
117
- - The `Empty` and `EmptyDict` classes as well as the `if_empty` method
118
- - The `EnumEx` base class for Enums with the `from_any` class method
119
-
120
- So in most cases we can cover something like 90% of any imports we tend to
121
- need in every Python file with a single line:
122
-
123
- ```python
124
- from batutils.structs import *
125
- ```
File without changes
File without changes