├── .github
└── workflows
│ └── tests.yaml
├── .gitignore
├── .gitmodules
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── benchmark.py
├── conftest.py
├── libfaketime
├── __init__.py
└── _version.py
├── pyproject.toml
├── requirements.txt
├── setup.cfg
├── setup.py
└── test
├── __init__.py
├── test_faketime.py
├── test_freezegun.py
└── test_tz.py
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: tests
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | tests:
9 | name: ${{ matrix.os }} - python${{ matrix.python }} - ${{ matrix.tz }}
10 | runs-on: ${{ matrix.os }}
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | os:
15 | - ubuntu-24.04
16 | - ubuntu-22.04
17 | - macos-14
18 | - macos-13
19 | python:
20 | - '3.13'
21 | - '3.12'
22 | - '3.11'
23 | - '3.10'
24 | - '3.9'
25 | tz:
26 | - 'utc'
27 | - 'cest'
28 | steps:
29 | - uses: actions/checkout@v4
30 | - uses: actions/setup-python@v5
31 | with:
32 | python-version: ${{ matrix.python }}
33 | - uses: actions/cache@v4
34 | with:
35 | path: ~/.cache/pip
36 | key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('setup.cfg') }}
37 | - run: pip install tox
38 | - run: git submodule update --init --force
39 | - run: env FAKETIME_COMPILE_CFLAGS="-UFAKE_STAT -UFAKE_UTIME -UFAKE_SLEEP" make -C libfaketime/vendor/libfaketime
40 | if: runner.os == 'Linux'
41 | - run: make -C libfaketime/vendor/libfaketime
42 | if: runner.os == 'macOS'
43 | - run: tox -e ${{ matrix.python }}-${{ matrix.tz }} --recreate
44 |
45 | style:
46 | runs-on: ubuntu-latest
47 | steps:
48 | - uses: actions/checkout@v4
49 | - uses: pre-commit/action@v3.0.1
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .pytest_cache/
2 |
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | env/
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 |
45 | # Translations
46 | *.mo
47 | *.pot
48 |
49 | # Django stuff:
50 | *.log
51 |
52 | # Sphinx documentation
53 | docs/_build/
54 |
55 | # PyBuilder
56 | target/
57 |
58 | # IntelliJ/PyCharm IDE config
59 | /.idea
60 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vendor/libfaketime"]
2 | path = libfaketime/vendor/libfaketime
3 | url = https://github.com/wolfcw/libfaketime.git
4 | branch = python-libfaketime
5 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | repos:
3 | - repo: https://github.com/astral-sh/ruff-pre-commit
4 | rev: 'v0.8.5'
5 | hooks:
6 | - id: ruff
7 | args: [--fix, --exit-non-zero-on-fix]
8 | - id: ruff-format
9 | - repo: https://github.com/pre-commit/pre-commit-hooks
10 | rev: v5.0.0
11 | hooks:
12 | - id: fix-byte-order-marker
13 | - id: trailing-whitespace
14 | - id: end-of-file-fixer
15 | - id: check-toml
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | [Semantic versioning](http://semver.org/) is used.
5 |
6 | 3.0.0
7 | -----
8 | released 2025-01-20
9 |
10 | Thanks for @azmeuk for their contributions to this release.
11 |
12 | - breaking: drop support for python 3.8
13 | - disable FAKETIME_FORCE_MONOTONIC_FIX to attempt to fix a performance regression in 2.1.0: [#81](https://github.com/simon-weber/python-libfaketime/issues/81)
14 | - add support for python 3.13
15 |
16 | 2.1.0
17 | -----
18 | released 2024-05-17
19 |
20 | Thanks for @azmeuk for all their contributions to this release!
21 |
22 | - add support for timestamp files, which enables freezing time across subprocesses: [#78](https://github.com/simon-weber/python-libfaketime/pull/78)
23 | - upgrade underlying libfaketime to 0.9.10 without modifications: [#75](https://github.com/simon-weber/python-libfaketime/pull/75)
24 | - add a quiet param to rexec_if_needed: [#72](https://github.com/simon-weber/python-libfaketime/pull/72)
25 |
26 | 2.0.0
27 | -----
28 | released 2020-04-17
29 |
30 | - breaking: drop python 2.7 support
31 | - set LD_LIBRARY_PATH on linux to support paths containing spaces: [#57](https://github.com/simon-weber/python-libfaketime/pull/57)
32 | - fix compatibility with non-pytz tzinfo objects: [#58](https://github.com/simon-weber/python-libfaketime/pull/58)
33 |
34 | 1.2.1
35 | -----
36 | released 2019-01-20
37 |
38 | - fix a deadlock on python 3.7+
39 |
40 | 1.2.0
41 | -----
42 | released 2018-10-28
43 |
44 | - offset-aware datetimes now properly fake the timezone as well: [#49](https://github.com/simon-weber/python-libfaketime/pull/49)
45 |
46 | 1.1.0
47 | -----
48 | released 2018-10-07
49 |
50 | - decorated classes can access the fake_time object with ``self._faked_time``: [#47](https://github.com/simon-weber/python-libfaketime/pull/47)
51 |
52 | 1.0.0
53 | -----
54 | released 2018-06-16
55 |
56 | - **backwards incompatible**: the monotonic clock is no longer mocked: [#45](https://github.com/simon-weber/python-libfaketime/pull/45)
57 | - ensure TZ is set to a valid timezone: [#46](https://github.com/simon-weber/python-libfaketime/pull/46)
58 |
59 | 0.5.2
60 | -----
61 | released 2018-05-19
62 |
63 | - fix a bug causing incorrect times after unpatching under python 3.6+: [#43](https://github.com/simon-weber/python-libfaketime/pull/43)
64 | - fix compilation under gcc8: [#44](https://github.com/simon-weber/python-libfaketime/pull/44)
65 |
66 | 0.5.1
67 | -----
68 | released 2018-01-19
69 |
70 | - fix usage as a class decorator : [#41](https://github.com/simon-weber/python-libfaketime/pull/41)
71 |
72 | 0.5.0
73 | -----
74 | released 2017-09-10
75 |
76 | - alias fake_time for freeze_time: [#31](https://github.com/simon-weber/python-libfaketime/pull/31)
77 | - add tz_offset parameter: [#36](https://github.com/simon-weber/python-libfaketime/pull/36)
78 |
79 | 0.4.4
80 | -----
81 | released 2017-07-16
82 |
83 | - allow contextlib2 as an alternative to contextdecorator: [#30](https://github.com/simon-weber/python-libfaketime/pull/30)
84 |
85 | 0.4.3
86 | -----
87 | released 2017-07-07
88 |
89 | - add macOS Sierra compatibility: [#29](https://github.com/simon-weber/python-libfaketime/pull/29)
90 |
91 | 0.4.2
92 | -----
93 | released 2016-06-30
94 |
95 | - fix only_main_thread=False: [#24](https://github.com/simon-weber/python-libfaketime/pull/24)
96 |
97 | 0.4.1
98 | -----
99 | released 2016-05-02
100 |
101 | - fix deadlocks from uuid.uuid1 when faking time: [#14](https://github.com/simon-weber/python-libfaketime/pull/14)
102 | - remove contextdecorator dependency on python3: [#15](https://github.com/simon-weber/python-libfaketime/pull/15)
103 |
104 | 0.4.0
105 | -----
106 | released 2016-04-02
107 |
108 | - freezegun's tick() is now supported; see [their docs](https://github.com/spulec/freezegun/blob/f1f5148720dd715cfd6dc03bf1861dbedfaad493/README.rst#manual-ticks) for usage.
109 |
110 | 0.3.0
111 | -----
112 | released 2016-03-04
113 |
114 | - invoking ``libfaketime`` from the command line will now print the necessary environment to avoid a re-exec.
115 |
116 | 0.2.1
117 | -----
118 | released 2016-03-01
119 |
120 | - python 3 support
121 |
122 | 0.1.1
123 | -----
124 | released 2015-09-11
125 |
126 | - prevent distribution of test directory: https://github.com/simon-weber/python-libfaketime/pull/4
127 |
128 | 0.1.0
129 | -----
130 | released 2015-06-23
131 |
132 | - add global start/stop callbacks
133 |
134 | 0.0.3
135 | -----
136 | released 2015-03-28
137 |
138 | - initial packaged release
139 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CHANGELOG.md LICENSE README.md
2 | recursive-include libfaketime *.py
3 | include libfaketime/vendor/libfaketime/Makefile
4 | include libfaketime/vendor/libfaketime/src/Makefile
5 | include libfaketime/vendor/libfaketime/src/Makefile.OSX
6 | include libfaketime/vendor/libfaketime/COPYING
7 | recursive-include libfaketime/vendor/libfaketime/src *.c *.h *.map
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | python-libfaketime: fast date/time mocking
2 | ==========================================
3 |
4 | [](https://github.com/simon-weber/python-libfaketime/actions/workflows/tests.yaml)
5 | [](https://pypi.python.org/pypi/libfaketime)
6 | [](https://www.repominder.com)
7 |
8 | python-libfaketime is a wrapper of [libfaketime](https://github.com/wolfcw/libfaketime) for python.
9 | Some brief details:
10 |
11 | * Linux and OS X, Pythons 3.8 through 3.12, pypy and pypy3
12 | * Mostly compatible with [freezegun](https://github.com/spulec/freezegun).
13 | * Microsecond resolution.
14 | * Accepts datetimes and strings that can be parsed by dateutil.
15 | * Not threadsafe.
16 | * Will break profiling. A workaround: use ``libfaketime.{begin, end}_callback`` to disable/enable your profiler ([nosetest example](https://gist.github.com/simon-weber/8d43e33448684f85718417ce1a072bc8)).
17 |
18 |
19 | Installation
20 | ------------
21 |
22 | ```sh
23 | $ pip install libfaketime
24 | ```
25 |
26 | Usage
27 | -----
28 |
29 | ```python
30 | import datetime
31 | from libfaketime import fake_time, reexec_if_needed
32 |
33 | # libfaketime needs to be preloaded by the dynamic linker.
34 | # This will exec the same command, but with the proper environment variables set.
35 | # You can also skip this and manually manage your env (see "How to avoid re-exec").
36 | reexec_if_needed()
37 |
38 | def test_datetime_now():
39 |
40 | # fake_time can be used as a context_manager
41 | with fake_time('1970-01-01 00:00:01'):
42 |
43 | # Every calls to a date or datetime function returns the mocked date
44 | assert datetime.datetime.utcnow() == datetime.datetime(1970, 1, 1, 0, 0, 1)
45 | assert datetime.datetime.now() == datetime.datetime(1970, 1, 1, 0, 0, 1)
46 | assert time.time() == 1
47 |
48 |
49 | # fake_time can also be used as a decorator
50 | @fake_time('1970-01-01 00:00:01', tz_offset=12)
51 | def test_datetime_now_with_offset():
52 |
53 | # datetime.utcnow returns the mocked datetime without offset
54 | assert datetime.datetime.utcnow() == datetime.datetime(1970, 1, 1, 0, 0, 1)
55 |
56 | # datetime.now returns the mocked datetime with the offset passed to fake_time
57 | assert datetime.datetime.now() == datetime.datetime(1970, 1, 1, 12, 0, 1)
58 | ```
59 |
60 | ### remove_vars
61 |
62 | By default, ``reexec_if_needed`` removes the ``LD_PRELOAD`` variable after the
63 | re-execution, to keep your environment as clean as possible. You might want it
64 | to stick around, for example when using parallelized tests that use subprocess
65 | like ``pytest-xdist``, and simply for tests where subprocess is called. To
66 | keep them around, pass ``remove_vars=False`` like:
67 |
68 | ```python
69 | reexec_if_needed(remove_vars=False)
70 | ```
71 |
72 | ### quiet
73 |
74 | To avoid displaying the informative text when re-executing, you can set the
75 | `quiet` parameter:
76 |
77 | ```python
78 | reexec_if_needed(quiet=True)
79 | ```
80 |
81 | ### timestamp_file
82 |
83 | A common time can be shared between several execution contexts by using a file
84 | to store the time to mock, instead of environment variables. This is useful
85 | to control the time of a running process for instance. Here is a schematized
86 | use case:
87 |
88 | ```python
89 | reexec_if_needed(remove_vars=False)
90 |
91 | with fake_time("1970-01-01 00:00:00", timestamp_file="/tmp/timestamp"):
92 | subprocess.run("/some/server/process")
93 |
94 | with fake_time("2000-01-01 00:00:00", timestamp_file="/tmp/timestamp"):
95 | assert request_the_server_process_date() == "2000-01-01 00:00:00"
96 | ```
97 |
98 | Performance
99 | -----------
100 |
101 | libfaketime tends to be significantly faster than [freezegun](https://github.com/spulec/freezegun).
102 | Here's the output of a [totally unscientific benchmark](https://github.com/simon-weber/python-libfaketime/blob/master/benchmark.py) on my laptop:
103 |
104 | ```sh
105 | $ python benchmark.py
106 | re-exec with libfaketime dependencies
107 | timing 1000 executions of
108 | 0.021755 seconds
109 |
110 | $ python benchmark.py freezegun
111 | timing 1000 executions of
112 | 6.561472 seconds
113 | ```
114 |
115 | Use with py.test
116 | ----------------
117 |
118 | The [pytest-libfaketime](https://github.com/pytest-dev/pytest-libfaketime) plugin will automatically configure python-libfaketime for you:
119 |
120 | ```sh
121 | $ pip install pytest-libfaketime
122 | ```
123 |
124 | Alternatively, you can reexec manually from inside the pytest_configure hook:
125 |
126 | ```python
127 | # conftest.py
128 | import os
129 | import libfaketime
130 |
131 | def pytest_configure():
132 | libfaketime.reexec_if_needed()
133 | _, env_additions = libfaketime.get_reload_information()
134 | os.environ.update(env_additions)
135 | ```
136 |
137 | Use with tox
138 | ------------
139 |
140 | In your tox configuration file, under the ``testenv`` bloc, add the libfaketime environment variables to avoid re-execution:
141 |
142 | ```ini
143 | setenv =
144 | LD_PRELOAD = {envsitepackagesdir}/libfaketime/vendor/libfaketime/src/libfaketime.so.1
145 | DONT_FAKE_MONOTONIC = 1
146 | FAKETIME_DID_REEXEC = true
147 | ```
148 |
149 | Migration from freezegun
150 | ------------------------
151 |
152 | python-libfaketime should have the same behavior as freezegun when running on supported code. To migrate to it, you can run:
153 |
154 | ```bash
155 | find . -type f -name "*.py" -exec sed -i 's/freezegun/libfaketime/g' "{}" \;
156 | ```
157 |
158 | How to avoid re-exec
159 | --------------------
160 |
161 | In some cases - especially when your tests start other processes - re-execing can cause unexpected problems. To avoid this, you can preload the necessary environment variables yourself. The necessary environment for your system can be found by running ``python-libfaketime`` on the command line:
162 |
163 | ```sh
164 | $ python-libfaketime
165 | export LD_PRELOAD="/home/foo//vendor/libfaketime/src/libfaketime.so.1"
166 | export DONT_FAKE_MONOTONIC="1"
167 | export FAKETIME_NO_CACHE="1"
168 | export FAKETIME_DID_REEXEC=true
169 | ```
170 |
171 | You can easily put this in a script like:
172 |
173 | ```sh
174 | $ eval $(python-libfaketime)
175 | $ pytest # ...or any other code that imports libfaketime
176 | ```
177 |
178 | Contributing and testing
179 | ------------------------
180 |
181 | Contributions are welcome! You should compile libfaketime before running tests:
182 |
183 | ```bash
184 | git submodule init --update
185 | # For Linux:
186 | env FAKETIME_COMPILE_CFLAGS="-UFAKE_STAT -UFAKE_UTIME -UFAKE_SLEEP" make -C libfaketime/vendor/libfaketime
187 | # For macOS
188 | env make -C libfaketime/vendor/libfaketime
189 | ```
190 |
191 | Then you can install requirements with ``pip install -r requirements.txt`` and use ``pytest`` and ``tox`` to run the tests.
192 |
193 | uuid1 deadlock
194 | --------------
195 |
196 | Calling ``uuid.uuid1()`` multiple times while in a fake_time context can result in a deadlock when an OS-level uuid library is available.
197 | To avoid this, python-libtaketime will monkeypatch uuid._uuid_generate_time (or similar, it varies by version) to None inside a fake_time context.
198 | This may slow down uuid1 generation but should not affect correctness.
199 |
--------------------------------------------------------------------------------
/benchmark.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import sys
3 | import time
4 |
5 | from freezegun import freeze_time as freezegun_fake_time
6 |
7 | from libfaketime import fake_time as lft_fake_time
8 | from libfaketime import reexec_if_needed
9 |
10 |
11 | def sample(faker):
12 | start = time.perf_counter()
13 |
14 | with faker(datetime.datetime.now()):
15 | datetime.datetime.now()
16 |
17 | datetime.datetime.now()
18 |
19 | return time.perf_counter() - start
20 |
21 |
22 | if __name__ == "__main__":
23 | if len(sys.argv) > 1 and sys.argv[1] == "freezegun":
24 | faker = freezegun_fake_time
25 | else:
26 | faker = lft_fake_time
27 | reexec_if_needed()
28 |
29 | iterations = 1000
30 |
31 | print(f"timing {iterations} executions of {faker}")
32 |
33 | sum = 0
34 | for _ in range(iterations):
35 | sum += sample(faker)
36 |
37 | print(sum, "seconds")
38 |
--------------------------------------------------------------------------------
/conftest.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import libfaketime
4 |
5 |
6 | def pytest_configure():
7 | libfaketime.reexec_if_needed()
8 | _, env_additions = libfaketime.get_reload_information()
9 | os.environ.update(env_additions)
10 |
--------------------------------------------------------------------------------
/libfaketime/__init__.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import functools
3 | import inspect
4 | import os
5 | import sys
6 | import threading
7 | import time
8 | import unittest
9 | import uuid
10 | from copy import deepcopy
11 |
12 | import dateutil.parser
13 | from pytz import timezone
14 | from pytz import utc
15 |
16 | # When using reexec_if_needed, remove_vars=True and a test loader that purges
17 | # sys.modules (like nose), it can be tough to run reexec_if_needed only once.
18 | # This env var is set by reexec to ensure we don't reload more than once.
19 |
20 | _DID_REEXEC_VAR = "FAKETIME_DID_REEXEC"
21 | _FAKETIME_FMT = "%Y-%m-%d %T.%f"
22 |
23 |
24 | def _get_lib_path():
25 | vendor_dir = "libfaketime"
26 |
27 | return os.path.join(
28 | os.path.dirname(__file__), os.path.join("vendor", vendor_dir, "src")
29 | )
30 |
31 |
32 | def _get_shared_lib(basename):
33 | return os.path.join(_get_lib_path(), basename)
34 |
35 |
36 | def _setup_ld_preload(soname):
37 | if "LD_PRELOAD" in os.environ:
38 | preload = "{}:{}".format(soname, os.environ["LD_PRELOAD"])
39 | else:
40 | preload = soname
41 |
42 | return preload
43 |
44 |
45 | # keys are the first 5 chars since we don't care about the version.
46 | _lib_addition = {
47 | "linux": {
48 | "LD_LIBRARY_PATH": _get_lib_path(),
49 | "LD_PRELOAD": _setup_ld_preload("libfaketime.so.1"),
50 | },
51 | "darwi": {
52 | "DYLD_INSERT_LIBRARIES": _get_shared_lib("libfaketime.1.dylib"),
53 | },
54 | }
55 |
56 | _other_additions = {
57 | "linux": {
58 | "DONT_FAKE_MONOTONIC": "1",
59 | "FAKETIME_NO_CACHE": "1",
60 | "FAKETIME_FORCE_MONOTONIC_FIX": "0",
61 | },
62 | "darwi": {
63 | "DONT_FAKE_MONOTONIC": "1",
64 | "DYLD_FORCE_FLAT_NAMESPACE": "1",
65 | "FAKETIME_NO_CACHE": "1",
66 | "FAKETIME_FORCE_MONOTONIC_FIX": "0",
67 | },
68 | }
69 |
70 | _env_additions = deepcopy(_lib_addition)
71 | for platform_name, d in list(_other_additions.items()):
72 | # Just doing a .update wouldn't merge the sub dictionaries.
73 | _env_additions[platform_name].update(d)
74 |
75 |
76 | def get_reload_information():
77 | try:
78 | env_additions = _env_additions[sys.platform[:5]]
79 | except KeyError:
80 | raise RuntimeError(f"libfaketime does not support platform {sys.platform}")
81 |
82 | needs_reload = os.environ.get(_DID_REEXEC_VAR) != "true"
83 |
84 | return needs_reload, env_additions
85 |
86 |
87 | def main(): # pragma: nocover
88 | """Print the necessary environment to stdout."""
89 | _, _env_additions = get_reload_information()
90 | for key, value in _env_additions.items():
91 | print(f'export {key}="{value}"')
92 | print(f"export {_DID_REEXEC_VAR}=true")
93 |
94 |
95 | def reexec_if_needed(remove_vars=True, quiet=False):
96 | needs_reload, env_additions = get_reload_information()
97 | if needs_reload:
98 | new_environ = os.environ.copy()
99 | new_environ.update(env_additions)
100 | new_environ[_DID_REEXEC_VAR] = "true"
101 | args = [sys.executable, [sys.executable] + sys.argv, new_environ]
102 | if not quiet:
103 | print("re-exec with libfaketime dependencies")
104 | os.execve(*args)
105 |
106 | if remove_vars:
107 | for key in env_additions:
108 | if key in os.environ:
109 | del os.environ[key]
110 |
111 |
112 | def begin_callback(instance):
113 | """Execute custom code just before faking the time."""
114 | pass
115 |
116 |
117 | def end_callback(instance):
118 | """Execute custom code after finished faking the time."""
119 | pass
120 |
121 |
122 | class fake_time:
123 | def __init__(
124 | self,
125 | datetime_spec=None,
126 | only_main_thread=True,
127 | tz_offset=None,
128 | timestamp_file=None,
129 | ):
130 | self.only_main_thread = only_main_thread
131 | self.timezone_str = "UTC"
132 | if tz_offset is not None:
133 | self.timezone_str = f"Etc/GMT{-tz_offset:+}"
134 |
135 | if not datetime_spec and not timestamp_file:
136 | raise ValueError(
137 | "Either 'datetime_spec' or 'timestamp_file' must be passed."
138 | )
139 |
140 | self.time_to_freeze = datetime_spec
141 | self.timestamp_file = timestamp_file
142 |
143 | if isinstance(datetime_spec, str):
144 | self.time_to_freeze = utc.localize(
145 | dateutil.parser.parse(datetime_spec)
146 | ).astimezone(timezone(self.timezone_str))
147 | elif isinstance(datetime_spec, datetime.datetime):
148 | if datetime_spec.tzinfo:
149 | if tz_offset is not None:
150 | raise Exception(
151 | "Cannot set tz_offset when datetime already has timezone"
152 | )
153 | self.timezone_str = datetime_spec.tzinfo.tzname(datetime_spec)
154 |
155 | def _should_fake(self):
156 | return (
157 | not self.only_main_thread or threading.current_thread().name == "MainThread"
158 | )
159 |
160 | _uuid_func_names = (
161 | # < 3.7
162 | "_uuid_generate_time",
163 | # 3.7+
164 | "_generate_time_safe",
165 | "_generate_time",
166 | )
167 |
168 | def _should_patch_uuid(self):
169 | # Return the name of the uuid time generate function, or None if not present.
170 | # This must be patched to avoid uuid1 deadlocks in OS uuid libraries.
171 | if self._should_fake() and not self._prev_spec:
172 | for func_name in self._uuid_func_names:
173 | if hasattr(uuid, func_name):
174 | return func_name
175 |
176 | return None
177 |
178 | def _format_datetime(self, _datetime):
179 | return _datetime.strftime(_FAKETIME_FMT)
180 |
181 | def _update_time(self, time):
182 | if not self.timestamp_file:
183 | os.environ["FAKETIME"] = self._format_datetime(time)
184 | else:
185 | if time:
186 | with open(self.timestamp_file, "w") as fd:
187 | fd.write(self._format_datetime(time))
188 | os.environ["FAKETIME_TIMESTAMP_FILE"] = self.timestamp_file
189 |
190 | def tick(self, delta=datetime.timedelta(seconds=1)):
191 | self.time_to_freeze += delta
192 | self._update_time(self.time_to_freeze)
193 |
194 | def __enter__(self):
195 | if self._should_fake():
196 | begin_callback(self)
197 | self._prev_spec = os.environ.get("FAKETIME")
198 | self._prev_tz = os.environ.get("TZ")
199 | self._prev_fmt = os.environ.get("FAKETIME_FMT")
200 | self._prev_timestamp_file = os.environ.get("FAKETIME_TIMESTAMP_FILE")
201 |
202 | os.environ["TZ"] = self.timezone_str
203 |
204 | time.tzset()
205 | self._update_time(self.time_to_freeze)
206 | os.environ["FAKETIME_FMT"] = _FAKETIME_FMT
207 |
208 | func_name = self._should_patch_uuid()
209 | if func_name:
210 | self._backup_uuid_generate_time = getattr(uuid, func_name)
211 | setattr(uuid, func_name, None)
212 |
213 | return self
214 |
215 | def __exit__(self, *exc):
216 | func_name = self._should_patch_uuid()
217 | if func_name:
218 | setattr(uuid, func_name, self._backup_uuid_generate_time)
219 |
220 | if self._should_fake():
221 | if self._prev_tz is not None:
222 | os.environ["TZ"] = self._prev_tz
223 | else:
224 | del os.environ["TZ"]
225 | time.tzset()
226 |
227 | if self.timestamp_file:
228 | if self._prev_timestamp_file is not None:
229 | os.environ["FAKETIME_TIMESTAMP_FILE"] = self._prev_timestamp_file
230 | elif "FAKETIME_TIMESTAMP_FILE" in os.environ:
231 | del os.environ["FAKETIME_TIMESTAMP_FILE"]
232 |
233 | else:
234 | if self._prev_spec is not None:
235 | os.environ["FAKETIME"] = self._prev_spec
236 | else:
237 | del os.environ["FAKETIME"]
238 |
239 | if self._prev_fmt is not None:
240 | os.environ["FAKETIME_FMT"] = self._prev_spec
241 | else:
242 | del os.environ["FAKETIME_FMT"]
243 |
244 | end_callback(self)
245 |
246 | return False
247 |
248 | # Freezegun compatibility.
249 | start = __enter__
250 | stop = __exit__
251 |
252 | # Decorator-style use support (shamelessly taken from freezegun, see
253 | # https://github.com/spulec/freezegun/blob/7ad16a5579b28fc939a69cc04f0e99ba5e87b206/freezegun/api.py#L323)
254 |
255 | def __call__(self, func):
256 | if inspect.isclass(func):
257 | return self.decorate_class(func)
258 | return self.decorate_callable(func)
259 |
260 | def decorate_class(self, klass):
261 | if issubclass(klass, unittest.TestCase):
262 | # If it's a TestCase, we assume you want to freeze the time for the
263 | # tests, from setUpClass to tearDownClass
264 |
265 | orig_setUpClass = getattr(klass, "setUpClass", None)
266 | orig_tearDownClass = getattr(klass, "tearDownClass", None)
267 |
268 | @classmethod
269 | def setUpClass(cls):
270 | self.start()
271 | if orig_setUpClass is not None:
272 | orig_setUpClass()
273 |
274 | @classmethod
275 | def tearDownClass(cls):
276 | if orig_tearDownClass is not None:
277 | orig_tearDownClass()
278 | self.stop()
279 |
280 | klass.setUpClass = setUpClass
281 | klass.tearDownClass = tearDownClass
282 |
283 | else:
284 | seen = set()
285 |
286 | klasses = (
287 | klass.mro()
288 | if hasattr(klass, "mro")
289 | else [klass] + list(klass.__bases__)
290 | )
291 | for base_klass in klasses:
292 | for attr, attr_value in base_klass.__dict__.items():
293 | if attr.startswith("_") or attr in seen:
294 | continue
295 | seen.add(attr)
296 |
297 | if not callable(attr_value) or inspect.isclass(attr_value):
298 | continue
299 |
300 | try:
301 | setattr(klass, attr, self(attr_value))
302 | except (AttributeError, TypeError):
303 | # Sometimes we can't set this for built-in types and
304 | # custom callables
305 | continue
306 |
307 | klass._faked_time = self
308 | return klass
309 |
310 | def decorate_callable(self, func):
311 | def wrapper(*args, **kwargs):
312 | with self:
313 | result = func(*args, **kwargs)
314 | return result
315 |
316 | functools.update_wrapper(wrapper, func)
317 |
318 | return wrapper
319 |
320 |
321 | freeze_time = fake_time
322 |
--------------------------------------------------------------------------------
/libfaketime/_version.py:
--------------------------------------------------------------------------------
1 | __version__ = "3.0.0"
2 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.ruff.lint]
2 | select = [
3 | "D", # pydocstyle
4 | "E", # pycodestyle
5 | "F", # pyflakes
6 | "I", # isort
7 | "UP", # pyupgrade
8 | ]
9 | ignore = [
10 | "D100", # public module
11 | "D101", # public class
12 | "D102", # public method
13 | "D103", # public function
14 | "D104", # public package
15 | "D105", # magic method
16 | "D106", # nested class
17 | "D107", # public init
18 | "D203", # no-blank-line-before-class
19 | "D213", # multi-line-summary-second-line
20 | ]
21 |
22 | [tool.ruff.lint.isort]
23 | force-single-line = true
24 |
25 | [tool.ruff.format]
26 | docstring-code-format = true
27 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | contextdecorator==0.10.0
2 | freezegun==1.5.0
3 | mock==5.1.0
4 | pytest==8.2.0
5 | python-dateutil==2.9.0post0
6 | pytz==2024.1
7 | tox==4.15.0
8 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [tox:tox]
2 | envlist = py{39,310,311,312,313,py,py3}-{utc,cest}
3 |
4 | [testenv]
5 | setenv =
6 | PYTHONDONTWRITEBYTECODE=1
7 | utc: TZ=UTC
8 | cest: TZ=CEST
9 |
10 | deps = -rrequirements.txt
11 | commands = pytest -vs --showlocals {posargs}
12 |
13 | [testenv:style]
14 | commands =
15 | pip install pre-commit
16 | pre-commit run --all-files
17 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 | import re
5 | import subprocess
6 | import sys
7 |
8 | from setuptools import find_packages
9 | from setuptools import setup
10 | from setuptools.command.install import install
11 |
12 | # This hack is from http://stackoverflow.com/a/7071358/1231454;
13 | # the version is kept in a seperate file and gets parsed - this
14 | # way, setup.py doesn't have to import the package.
15 |
16 | VERSIONFILE = "libfaketime/_version.py"
17 |
18 | version_line = open(VERSIONFILE).read()
19 | version_re = r"^__version__ = ['\"]([^'\"]*)['\"]"
20 | match = re.search(version_re, version_line, re.M)
21 | if match:
22 | version = match.group(1)
23 | else:
24 | raise RuntimeError(f"Could not find version in '{VERSIONFILE}'")
25 |
26 |
27 | _vendor_path = "libfaketime/vendor/libfaketime"
28 | if sys.platform == "linux" or sys.platform == "linux2":
29 | libname = "libfaketime.so.1"
30 | elif sys.platform == "darwin":
31 | libname = "libfaketime.1.dylib"
32 |
33 | else:
34 | raise RuntimeError("libfaketime does not support platform {sys.platform}")
35 |
36 | faketime_lib = os.path.join(_vendor_path, "src", libname)
37 |
38 |
39 | class CustomInstall(install):
40 | def run(self):
41 | self.my_outputs = []
42 | if sys.platform in ("linux", "linux2"):
43 | subprocess.check_call(
44 | [
45 | "env",
46 | "FAKETIME_COMPILE_CFLAGS=-UFAKE_STAT -UFAKE_UTIME -UFAKE_SLEEP",
47 | "make",
48 | "-C",
49 | _vendor_path,
50 | ]
51 | )
52 | elif sys.platform == "darwin":
53 | subprocess.check_call(["make", "-C", _vendor_path])
54 |
55 | dest = os.path.join(self.install_purelib, os.path.dirname(faketime_lib))
56 | try:
57 | os.makedirs(dest)
58 | except OSError as e:
59 | if e.errno != 17:
60 | raise
61 | print(faketime_lib, "->", dest)
62 | self.copy_file(faketime_lib, dest)
63 | self.my_outputs.append(os.path.join(dest, libname))
64 |
65 | install.run(self)
66 |
67 | def get_outputs(self):
68 | outputs = install.get_outputs(self)
69 | outputs.extend(self.my_outputs)
70 | return outputs
71 |
72 |
73 | setup(
74 | name="libfaketime",
75 | version=version,
76 | author="Simon Weber",
77 | author_email="simon@simonmweber.com",
78 | url="http://pypi.python.org/pypi/libfaketime/",
79 | packages=find_packages(exclude=["test"]),
80 | scripts=[],
81 | license="GPLv2",
82 | description="A fast alternative to freezegun that wraps libfaketime.",
83 | long_description=(open("README.md").read() + "\n\n" + open("CHANGELOG.md").read()),
84 | long_description_content_type="text/markdown",
85 | install_requires=[
86 | "python-dateutil >= 1.3",
87 | "pytz", # for pytz.timezone and pytz.utc
88 | ],
89 | classifiers=[
90 | "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
91 | "Development Status :: 5 - Production/Stable",
92 | "Intended Audience :: Developers",
93 | "Operating System :: OS Independent",
94 | "Programming Language :: Python",
95 | "Programming Language :: Python :: 3.9",
96 | "Programming Language :: Python :: 3.10",
97 | "Programming Language :: Python :: 3.11",
98 | "Programming Language :: Python :: 3.12",
99 | "Programming Language :: Python :: 3.13",
100 | "Programming Language :: Python :: Implementation :: CPython",
101 | "Programming Language :: Python :: Implementation :: PyPy",
102 | "Topic :: Software Development :: Libraries :: Python Modules",
103 | ],
104 | include_package_data=True,
105 | zip_safe=False,
106 | cmdclass={"install": CustomInstall},
107 | entry_points={
108 | "console_scripts": [
109 | "python-libfaketime = libfaketime:main",
110 | ]
111 | },
112 | )
113 |
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simon-weber/python-libfaketime/ab69b95087efd103026467a34ffb6f92951733a1/test/__init__.py
--------------------------------------------------------------------------------
/test/test_faketime.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import os
3 | import time
4 | import uuid
5 | from unittest.mock import patch
6 |
7 | import pytest
8 |
9 | import libfaketime
10 | from libfaketime import fake_time
11 | from libfaketime import freeze_time
12 |
13 |
14 | class TestReexec:
15 | @patch("os.execve")
16 | @patch("sys.platform", "win32")
17 | def test_reexec_windows_fails(self, exec_patch):
18 | with pytest.raises(RuntimeError):
19 | libfaketime.reexec_if_needed()
20 |
21 |
22 | class TestFaketime:
23 | def _assert_time_not_faked(self):
24 | # This just makes sure that non-faked time is dynamic;
25 | # I can't think of a good way to check that the non-faked time is "real".
26 |
27 | first = datetime.datetime.now().microsecond
28 | time.sleep(0.000001)
29 | second = datetime.datetime.now().microsecond
30 |
31 | assert second > first
32 |
33 | def test_fake_time_tick(self):
34 | with fake_time("2000-01-01 10:00:05") as fake:
35 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.now()
36 | fake.tick(delta=datetime.timedelta(hours=1))
37 | assert datetime.datetime(2000, 1, 1, 11, 0, 5) == datetime.datetime.now()
38 |
39 | def test_nonfake_time_is_dynamic(self):
40 | self._assert_time_not_faked()
41 |
42 | @fake_time(datetime.datetime.now())
43 | def test_fake_time_is_static(self):
44 | first = datetime.datetime.now().microsecond
45 | second = datetime.datetime.now().microsecond
46 |
47 | assert second == first
48 |
49 | @fake_time("2000-01-01 10:00:05")
50 | def test_fake_time_parses_easy_strings(self):
51 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.now()
52 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.utcnow()
53 |
54 | def test_fake_time_parses_easy_strings_with_timezones(self):
55 | with fake_time("2000-01-01 10:00:05", tz_offset=3):
56 | assert datetime.datetime(2000, 1, 1, 13, 0, 5) == datetime.datetime.now()
57 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.utcnow()
58 |
59 | with fake_time("2000-01-01 10:00:05", tz_offset=-3):
60 | assert datetime.datetime(2000, 1, 1, 7, 0, 5) == datetime.datetime.now()
61 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.utcnow()
62 |
63 | @fake_time("march 1st, 2014 at 1:59pm")
64 | def test_fake_time_parses_tough_strings(self):
65 | assert datetime.datetime(2014, 3, 1, 13, 59) == datetime.datetime.now()
66 |
67 | @fake_time(datetime.datetime(2014, 1, 1, microsecond=123456))
68 | def test_fake_time_has_microsecond_granularity(self):
69 | assert (
70 | datetime.datetime(2014, 1, 1, microsecond=123456) == datetime.datetime.now()
71 | )
72 |
73 | def test_nested_fake_time(self):
74 | self._assert_time_not_faked()
75 |
76 | with fake_time("1/1/2000"):
77 | assert datetime.datetime(2000, 1, 1) == datetime.datetime.now()
78 |
79 | with fake_time("1/1/2001"):
80 | assert datetime.datetime(2001, 1, 1) == datetime.datetime.now()
81 |
82 | assert datetime.datetime(2000, 1, 1) == datetime.datetime.now()
83 |
84 | self._assert_time_not_faked()
85 |
86 | def test_freeze_time_alias(self):
87 | with freeze_time("2000-01-01 10:00:05"):
88 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.now()
89 |
90 | def test_monotonic_not_mocked(self):
91 | assert os.environ["DONT_FAKE_MONOTONIC"] == "1"
92 |
93 | def test_timestmap_file(self, tmpdir):
94 | file_path = str(tmpdir / "faketime.rc")
95 |
96 | with fake_time("2000-01-01 10:00:05", timestamp_file=file_path) as fake:
97 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.now()
98 | with open(file_path) as fd:
99 | assert fd.read() == "2000-01-01 10:00:05.000000"
100 |
101 | fake.tick(delta=datetime.timedelta(hours=1))
102 | assert datetime.datetime(2000, 1, 1, 11, 0, 5) == datetime.datetime.now()
103 | with open(file_path) as fd:
104 | assert fd.read() == "2000-01-01 11:00:05.000000"
105 |
106 | with fake_time(timestamp_file=file_path):
107 | assert datetime.datetime(2000, 1, 1, 11, 0, 5) == datetime.datetime.now()
108 |
109 |
110 | class TestUUID1Deadlock:
111 | @fake_time(datetime.datetime.now())
112 | def test_uuid1_does_not_deadlock(self):
113 | """Check the compatibility of uuid1.
114 |
115 | This test will deadlock if we fail to patch a system level uuid library.
116 | """
117 | for i in range(100):
118 | uuid.uuid1()
119 |
120 |
121 | @fake_time("2000-01-01")
122 | class TestClassDecorator:
123 | def test_simple(self):
124 | assert datetime.datetime(2000, 1, 1) == datetime.datetime.now()
125 | self._faked_time.tick()
126 | assert datetime.datetime(2000, 1, 1, 0, 0, 1) == datetime.datetime.now()
127 |
128 | @fake_time("2001-01-01")
129 | def test_overwrite_with_func_decorator(self):
130 | assert datetime.datetime(2001, 1, 1) == datetime.datetime.now()
131 |
--------------------------------------------------------------------------------
/test/test_freezegun.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import time
3 |
4 | import pytest
5 | from freezegun import freeze_time
6 |
7 | from libfaketime import fake_time
8 |
9 | # TODO
10 | # - Fix time.localtime
11 | # - Fix time.strftime
12 |
13 | # mark these as being used (via eval)
14 | assert time
15 | assert datetime
16 |
17 | test_functions = [
18 | ("datetime.datetime.now", (), {}),
19 | ("datetime.datetime.utcnow", (), {}),
20 | ("time.time", (), {}),
21 | # ("time.localtime", (), {}),
22 | # ("time.strftime", ("%Y-%m-%d %H:%M:%S %Z",), {}),
23 | ("datetime.date", (), {"year": 1970, "month": 1, "day": 1}),
24 | ("datetime.datetime", (), {"year": 1970, "month": 1, "day": 1}),
25 | ]
26 |
27 |
28 | @pytest.mark.parametrize("test_function", test_functions)
29 | @pytest.mark.parametrize("tz_offset", [0, 12])
30 | @pytest.mark.parametrize("date_to_freeze", ["1970-01-01 00:00:01"])
31 | def test_compare_against_freezegun_results(test_function, tz_offset, date_to_freeze):
32 | func_name, args, kwargs = test_function
33 |
34 | with fake_time(date_to_freeze, tz_offset=tz_offset):
35 | libfaketime_result = eval(func_name)(*args, **kwargs)
36 |
37 | with freeze_time(date_to_freeze, tz_offset=tz_offset):
38 | freezegun_result = eval(func_name)(*args, **kwargs)
39 |
40 | assert freezegun_result == libfaketime_result
41 |
--------------------------------------------------------------------------------
/test/test_tz.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import os
3 |
4 | import dateutil.tz
5 | import pytest
6 | from pytz import timezone
7 |
8 | from libfaketime import fake_time
9 |
10 |
11 | def test_timezone_is_restored_after_context_manager_usage():
12 | """Check that timezones are restored when faketime context manager are closed.
13 |
14 | https://github.com/simon-weber/python-libfaketime/issues/43
15 | """
16 | now1 = datetime.datetime.now()
17 | utcnow1 = datetime.datetime.utcnow()
18 |
19 | with fake_time(now1):
20 | datetime.datetime.now()
21 |
22 | now2 = datetime.datetime.now()
23 | utcnow2 = datetime.datetime.utcnow()
24 |
25 | assert abs((now2 - now1).total_seconds()) < 10
26 | assert abs((utcnow2 - utcnow1).total_seconds()) < 10
27 |
28 |
29 | def test_tzinfo_is_normalized():
30 | """Ensure utcnow() behaves correctly when faking non-UTC timestamps."""
31 | timezone_to_test_with = timezone("Europe/Brussels")
32 | time_to_freeze = timezone_to_test_with.localize(
33 | datetime.datetime(2017, 1, 2, 15, 2)
34 | )
35 |
36 | with fake_time(time_to_freeze):
37 | # The timeshift of Europe/Brussels is UTC+1 in January
38 | assert datetime.datetime.now() == datetime.datetime(2017, 1, 2, 15, 2)
39 | assert datetime.datetime.utcnow() == datetime.datetime(2017, 1, 2, 14, 2)
40 |
41 |
42 | def test_block_setting_of_conflicting_tz_info():
43 | """Cannot pass in tz_offset when the timestamp already carries a timezone."""
44 | with pytest.raises(Exception) as exc_info:
45 | timezone_to_test_with = timezone("America/Havana")
46 | time_to_freeze = timezone_to_test_with.localize(
47 | datetime.datetime(2012, 10, 2, 21, 38)
48 | )
49 |
50 | with fake_time(time_to_freeze, tz_offset=5):
51 | pass
52 |
53 | assert (
54 | str(exc_info.value) == "Cannot set tz_offset when datetime already has timezone"
55 | )
56 |
57 |
58 | @pytest.mark.parametrize("offset", range(-2, 3))
59 | def test_generated_tz_is_valid(offset):
60 | """Check that generated timezones are valid.
61 |
62 | https://github.com/simon-weber/python-libfaketime/issues/46
63 | """
64 | now = datetime.datetime.now()
65 |
66 | with fake_time(now, tz_offset=offset):
67 | fake_tz = os.environ["TZ"]
68 | timezone(fake_tz) # should not raise pytzdata.exceptions.TimezoneNotFound
69 |
70 |
71 | def test_dateutil_tz_is_valid():
72 | test_dt = datetime.datetime(2017, 1, 2, 15, 2)
73 | dateutil_tzinfo = dateutil.tz.gettz("UTC")
74 | dt_dateutil_tzinfo = test_dt.replace(tzinfo=dateutil_tzinfo)
75 |
76 | # Should be compatible with a dateutil tzinfo object, not just pytz
77 | with fake_time(dt_dateutil_tzinfo):
78 | assert datetime.datetime.now(tz=dateutil_tzinfo) == dt_dateutil_tzinfo
79 |
--------------------------------------------------------------------------------