| 
									
										
											  
											
												bpo-40503: PEP 615: Tests and implementation for zoneinfo (GH-19909)
This is the initial implementation of PEP 615, the zoneinfo module,
ported from the standalone reference implementation (see
https://www.python.org/dev/peps/pep-0615/#reference-implementation for a
link, which has a more detailed commit history).
This includes (hopefully) all functional elements described in the PEP,
but documentation is found in a separate PR. This includes:
1. A pure python implementation of the ZoneInfo class
2. A C accelerated implementation of the ZoneInfo class
3. Tests with 100% branch coverage for the Python code (though C code
   coverage is less than 100%).
4. A compile-time configuration option on Linux (though not on Windows)
Differences from the reference implementation:
- The module is arranged slightly differently: the accelerated module is
  `_zoneinfo` rather than `zoneinfo._czoneinfo`, which also necessitates
  some changes in the test support function. (Suggested by Victor
  Stinner and Steve Dower.)
- The tests are arranged slightly differently and do not include the
  property tests. The tests live at test/test_zoneinfo/test_zoneinfo.py
  rather than test/test_zoneinfo.py or test/test_zoneinfo/__init__.py
  because we may do some refactoring in the future that would likely
  require this separation anyway; we may:
        - include the property tests
        - automatically run all the tests against both pure Python and C,
          rather than manually constructing C and Python test classes (similar
          to the way this works with test_datetime.py, which generates C
          and Python test cases from datetimetester.py).
- This includes a compile-time configuration option on Linux (though not
  on Windows); added with much help from Thomas Wouters.
- Integration into the CPython build system is obviously different from
  building a standalone zoneinfo module wheel.
- This includes configuration to install the tzdata package as part of
  CI, though only on the coverage jobs. Introducing a PyPI dependency as
  part of the CI build was controversial, and this is seen as less of a
  major change, since the coverage jobs already depend on pip and PyPI.
Additional changes that were introduced as part of this PR, most / all of
which were backported to the reference implementation:
- Fixed reference and memory leaks
    With much debugging help from Pablo Galindo
- Added smoke tests ensuring that the C and Python modules are built
    The import machinery can be somewhat fragile, and the "seamlessly falls
    back to pure Python" nature of this module makes it so that a problem
    building the C extension or a failure to import the pure Python version
    might easily go unnoticed.
- Adjustments to zoneinfo.__dir__
    Suggested by Petr Viktorin.
- Slight refactorings as suggested by Steve Dower.
- Removed unnecessary if check on std_abbr
    Discovered this because of a missing line in branch coverage.
											
										 
											2020-05-16 04:20:06 -04:00
										 |  |  | import contextlib | 
					
						
							|  |  |  | import functools | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | import threading | 
					
						
							|  |  |  | import unittest | 
					
						
							| 
									
										
										
										
											2020-08-07 23:18:38 +08:00
										 |  |  | from test.support.import_helper import import_fresh_module | 
					
						
							| 
									
										
											  
											
												bpo-40503: PEP 615: Tests and implementation for zoneinfo (GH-19909)
This is the initial implementation of PEP 615, the zoneinfo module,
ported from the standalone reference implementation (see
https://www.python.org/dev/peps/pep-0615/#reference-implementation for a
link, which has a more detailed commit history).
This includes (hopefully) all functional elements described in the PEP,
but documentation is found in a separate PR. This includes:
1. A pure python implementation of the ZoneInfo class
2. A C accelerated implementation of the ZoneInfo class
3. Tests with 100% branch coverage for the Python code (though C code
   coverage is less than 100%).
4. A compile-time configuration option on Linux (though not on Windows)
Differences from the reference implementation:
- The module is arranged slightly differently: the accelerated module is
  `_zoneinfo` rather than `zoneinfo._czoneinfo`, which also necessitates
  some changes in the test support function. (Suggested by Victor
  Stinner and Steve Dower.)
- The tests are arranged slightly differently and do not include the
  property tests. The tests live at test/test_zoneinfo/test_zoneinfo.py
  rather than test/test_zoneinfo.py or test/test_zoneinfo/__init__.py
  because we may do some refactoring in the future that would likely
  require this separation anyway; we may:
        - include the property tests
        - automatically run all the tests against both pure Python and C,
          rather than manually constructing C and Python test classes (similar
          to the way this works with test_datetime.py, which generates C
          and Python test cases from datetimetester.py).
- This includes a compile-time configuration option on Linux (though not
  on Windows); added with much help from Thomas Wouters.
- Integration into the CPython build system is obviously different from
  building a standalone zoneinfo module wheel.
- This includes configuration to install the tzdata package as part of
  CI, though only on the coverage jobs. Introducing a PyPI dependency as
  part of the CI build was controversial, and this is seen as less of a
  major change, since the coverage jobs already depend on pip and PyPI.
Additional changes that were introduced as part of this PR, most / all of
which were backported to the reference implementation:
- Fixed reference and memory leaks
    With much debugging help from Pablo Galindo
- Added smoke tests ensuring that the C and Python modules are built
    The import machinery can be somewhat fragile, and the "seamlessly falls
    back to pure Python" nature of this module makes it so that a problem
    building the C extension or a failure to import the pure Python version
    might easily go unnoticed.
- Adjustments to zoneinfo.__dir__
    Suggested by Petr Viktorin.
- Slight refactorings as suggested by Steve Dower.
- Removed unnecessary if check on std_abbr
    Discovered this because of a missing line in branch coverage.
											
										 
											2020-05-16 04:20:06 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | OS_ENV_LOCK = threading.Lock() | 
					
						
							|  |  |  | TZPATH_LOCK = threading.Lock() | 
					
						
							|  |  |  | TZPATH_TEST_LOCK = threading.Lock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def call_once(f): | 
					
						
							|  |  |  |     """Decorator that ensures a function is only ever called once.""" | 
					
						
							|  |  |  |     lock = threading.Lock() | 
					
						
							|  |  |  |     cached = functools.lru_cache(None)(f) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @functools.wraps(f) | 
					
						
							|  |  |  |     def inner(): | 
					
						
							|  |  |  |         with lock: | 
					
						
							|  |  |  |             return cached() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return inner | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @call_once | 
					
						
							|  |  |  | def get_modules(): | 
					
						
							|  |  |  |     """Retrieve two copies of zoneinfo: pure Python and C accelerated.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Because this function manipulates the import system in a way that might | 
					
						
							|  |  |  |     be fragile or do unexpected things if it is run many times, it uses a | 
					
						
							|  |  |  |     `call_once` decorator to ensure that this is only ever called exactly | 
					
						
							|  |  |  |     one time — in other words, when using this function you will only ever | 
					
						
							|  |  |  |     get one copy of each module rather than a fresh import each time. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     import zoneinfo as c_module | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     py_module = import_fresh_module("zoneinfo", blocked=["_zoneinfo"]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return py_module, c_module | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @contextlib.contextmanager | 
					
						
							|  |  |  | def set_zoneinfo_module(module): | 
					
						
							|  |  |  |     """Make sure sys.modules["zoneinfo"] refers to `module`.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     This is necessary because `pickle` will refuse to serialize | 
					
						
							|  |  |  |     an type calling itself `zoneinfo.ZoneInfo` unless `zoneinfo.ZoneInfo` | 
					
						
							|  |  |  |     refers to the same object. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     NOT_PRESENT = object() | 
					
						
							|  |  |  |     old_zoneinfo = sys.modules.get("zoneinfo", NOT_PRESENT) | 
					
						
							|  |  |  |     sys.modules["zoneinfo"] = module | 
					
						
							|  |  |  |     yield | 
					
						
							|  |  |  |     if old_zoneinfo is not NOT_PRESENT: | 
					
						
							|  |  |  |         sys.modules["zoneinfo"] = old_zoneinfo | 
					
						
							|  |  |  |     else:  # pragma: nocover | 
					
						
							|  |  |  |         sys.modules.pop("zoneinfo") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ZoneInfoTestBase(unittest.TestCase): | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def setUpClass(cls): | 
					
						
							|  |  |  |         cls.klass = cls.module.ZoneInfo | 
					
						
							|  |  |  |         super().setUpClass() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @contextlib.contextmanager | 
					
						
							| 
									
										
										
										
											2020-05-17 21:55:11 -04:00
										 |  |  |     def tzpath_context(self, tzpath, block_tzdata=True, lock=TZPATH_LOCK): | 
					
						
							|  |  |  |         def pop_tzdata_modules(): | 
					
						
							|  |  |  |             tzdata_modules = {} | 
					
						
							|  |  |  |             for modname in list(sys.modules): | 
					
						
							|  |  |  |                 if modname.split(".", 1)[0] != "tzdata":  # pragma: nocover | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 tzdata_modules[modname] = sys.modules.pop(modname) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return tzdata_modules | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												bpo-40503: PEP 615: Tests and implementation for zoneinfo (GH-19909)
This is the initial implementation of PEP 615, the zoneinfo module,
ported from the standalone reference implementation (see
https://www.python.org/dev/peps/pep-0615/#reference-implementation for a
link, which has a more detailed commit history).
This includes (hopefully) all functional elements described in the PEP,
but documentation is found in a separate PR. This includes:
1. A pure python implementation of the ZoneInfo class
2. A C accelerated implementation of the ZoneInfo class
3. Tests with 100% branch coverage for the Python code (though C code
   coverage is less than 100%).
4. A compile-time configuration option on Linux (though not on Windows)
Differences from the reference implementation:
- The module is arranged slightly differently: the accelerated module is
  `_zoneinfo` rather than `zoneinfo._czoneinfo`, which also necessitates
  some changes in the test support function. (Suggested by Victor
  Stinner and Steve Dower.)
- The tests are arranged slightly differently and do not include the
  property tests. The tests live at test/test_zoneinfo/test_zoneinfo.py
  rather than test/test_zoneinfo.py or test/test_zoneinfo/__init__.py
  because we may do some refactoring in the future that would likely
  require this separation anyway; we may:
        - include the property tests
        - automatically run all the tests against both pure Python and C,
          rather than manually constructing C and Python test classes (similar
          to the way this works with test_datetime.py, which generates C
          and Python test cases from datetimetester.py).
- This includes a compile-time configuration option on Linux (though not
  on Windows); added with much help from Thomas Wouters.
- Integration into the CPython build system is obviously different from
  building a standalone zoneinfo module wheel.
- This includes configuration to install the tzdata package as part of
  CI, though only on the coverage jobs. Introducing a PyPI dependency as
  part of the CI build was controversial, and this is seen as less of a
  major change, since the coverage jobs already depend on pip and PyPI.
Additional changes that were introduced as part of this PR, most / all of
which were backported to the reference implementation:
- Fixed reference and memory leaks
    With much debugging help from Pablo Galindo
- Added smoke tests ensuring that the C and Python modules are built
    The import machinery can be somewhat fragile, and the "seamlessly falls
    back to pure Python" nature of this module makes it so that a problem
    building the C extension or a failure to import the pure Python version
    might easily go unnoticed.
- Adjustments to zoneinfo.__dir__
    Suggested by Petr Viktorin.
- Slight refactorings as suggested by Steve Dower.
- Removed unnecessary if check on std_abbr
    Discovered this because of a missing line in branch coverage.
											
										 
											2020-05-16 04:20:06 -04:00
										 |  |  |         with lock: | 
					
						
							| 
									
										
										
										
											2020-05-17 21:55:11 -04:00
										 |  |  |             if block_tzdata: | 
					
						
							|  |  |  |                 # In order to fully exclude tzdata from the path, we need to | 
					
						
							|  |  |  |                 # clear the sys.modules cache of all its contents — setting the | 
					
						
							|  |  |  |                 # root package to None is not enough to block direct access of | 
					
						
							|  |  |  |                 # already-imported submodules (though it will prevent new | 
					
						
							|  |  |  |                 # imports of submodules). | 
					
						
							|  |  |  |                 tzdata_modules = pop_tzdata_modules() | 
					
						
							|  |  |  |                 sys.modules["tzdata"] = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												bpo-40503: PEP 615: Tests and implementation for zoneinfo (GH-19909)
This is the initial implementation of PEP 615, the zoneinfo module,
ported from the standalone reference implementation (see
https://www.python.org/dev/peps/pep-0615/#reference-implementation for a
link, which has a more detailed commit history).
This includes (hopefully) all functional elements described in the PEP,
but documentation is found in a separate PR. This includes:
1. A pure python implementation of the ZoneInfo class
2. A C accelerated implementation of the ZoneInfo class
3. Tests with 100% branch coverage for the Python code (though C code
   coverage is less than 100%).
4. A compile-time configuration option on Linux (though not on Windows)
Differences from the reference implementation:
- The module is arranged slightly differently: the accelerated module is
  `_zoneinfo` rather than `zoneinfo._czoneinfo`, which also necessitates
  some changes in the test support function. (Suggested by Victor
  Stinner and Steve Dower.)
- The tests are arranged slightly differently and do not include the
  property tests. The tests live at test/test_zoneinfo/test_zoneinfo.py
  rather than test/test_zoneinfo.py or test/test_zoneinfo/__init__.py
  because we may do some refactoring in the future that would likely
  require this separation anyway; we may:
        - include the property tests
        - automatically run all the tests against both pure Python and C,
          rather than manually constructing C and Python test classes (similar
          to the way this works with test_datetime.py, which generates C
          and Python test cases from datetimetester.py).
- This includes a compile-time configuration option on Linux (though not
  on Windows); added with much help from Thomas Wouters.
- Integration into the CPython build system is obviously different from
  building a standalone zoneinfo module wheel.
- This includes configuration to install the tzdata package as part of
  CI, though only on the coverage jobs. Introducing a PyPI dependency as
  part of the CI build was controversial, and this is seen as less of a
  major change, since the coverage jobs already depend on pip and PyPI.
Additional changes that were introduced as part of this PR, most / all of
which were backported to the reference implementation:
- Fixed reference and memory leaks
    With much debugging help from Pablo Galindo
- Added smoke tests ensuring that the C and Python modules are built
    The import machinery can be somewhat fragile, and the "seamlessly falls
    back to pure Python" nature of this module makes it so that a problem
    building the C extension or a failure to import the pure Python version
    might easily go unnoticed.
- Adjustments to zoneinfo.__dir__
    Suggested by Petr Viktorin.
- Slight refactorings as suggested by Steve Dower.
- Removed unnecessary if check on std_abbr
    Discovered this because of a missing line in branch coverage.
											
										 
											2020-05-16 04:20:06 -04:00
										 |  |  |             old_path = self.module.TZPATH | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 self.module.reset_tzpath(tzpath) | 
					
						
							|  |  |  |                 yield | 
					
						
							|  |  |  |             finally: | 
					
						
							| 
									
										
										
										
											2020-05-17 21:55:11 -04:00
										 |  |  |                 if block_tzdata: | 
					
						
							|  |  |  |                     sys.modules.pop("tzdata") | 
					
						
							|  |  |  |                     for modname, module in tzdata_modules.items(): | 
					
						
							|  |  |  |                         sys.modules[modname] = module | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												bpo-40503: PEP 615: Tests and implementation for zoneinfo (GH-19909)
This is the initial implementation of PEP 615, the zoneinfo module,
ported from the standalone reference implementation (see
https://www.python.org/dev/peps/pep-0615/#reference-implementation for a
link, which has a more detailed commit history).
This includes (hopefully) all functional elements described in the PEP,
but documentation is found in a separate PR. This includes:
1. A pure python implementation of the ZoneInfo class
2. A C accelerated implementation of the ZoneInfo class
3. Tests with 100% branch coverage for the Python code (though C code
   coverage is less than 100%).
4. A compile-time configuration option on Linux (though not on Windows)
Differences from the reference implementation:
- The module is arranged slightly differently: the accelerated module is
  `_zoneinfo` rather than `zoneinfo._czoneinfo`, which also necessitates
  some changes in the test support function. (Suggested by Victor
  Stinner and Steve Dower.)
- The tests are arranged slightly differently and do not include the
  property tests. The tests live at test/test_zoneinfo/test_zoneinfo.py
  rather than test/test_zoneinfo.py or test/test_zoneinfo/__init__.py
  because we may do some refactoring in the future that would likely
  require this separation anyway; we may:
        - include the property tests
        - automatically run all the tests against both pure Python and C,
          rather than manually constructing C and Python test classes (similar
          to the way this works with test_datetime.py, which generates C
          and Python test cases from datetimetester.py).
- This includes a compile-time configuration option on Linux (though not
  on Windows); added with much help from Thomas Wouters.
- Integration into the CPython build system is obviously different from
  building a standalone zoneinfo module wheel.
- This includes configuration to install the tzdata package as part of
  CI, though only on the coverage jobs. Introducing a PyPI dependency as
  part of the CI build was controversial, and this is seen as less of a
  major change, since the coverage jobs already depend on pip and PyPI.
Additional changes that were introduced as part of this PR, most / all of
which were backported to the reference implementation:
- Fixed reference and memory leaks
    With much debugging help from Pablo Galindo
- Added smoke tests ensuring that the C and Python modules are built
    The import machinery can be somewhat fragile, and the "seamlessly falls
    back to pure Python" nature of this module makes it so that a problem
    building the C extension or a failure to import the pure Python version
    might easily go unnoticed.
- Adjustments to zoneinfo.__dir__
    Suggested by Petr Viktorin.
- Slight refactorings as suggested by Steve Dower.
- Removed unnecessary if check on std_abbr
    Discovered this because of a missing line in branch coverage.
											
										 
											2020-05-16 04:20:06 -04:00
										 |  |  |                 self.module.reset_tzpath(old_path) |