forked from beartype/beartype
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsetup.py
executable file
·388 lines (340 loc) · 17.5 KB
/
setup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2023 Beartype authors.
# See "LICENSE" for further details.
'''
**Beartype installer.**
This submodule conforms to the standard :mod:`setuptools`-based "makefile"
format, instrumenting most high-level installation tasks for this package.
'''
# ....................{ TODO }....................
#FIXME: Strongly consider migrating to Hatch:
# https://hatch.pypa.io
#We can't *STAND* poetry, but Hatch looks to be another breed entirely.
#Crucially, PyPA itself has officially adopted Hatch (which is a huge boost),
#Hatch supports dynamic retrieval of version specifiers from Python modules,
#support for PEPs denigrated by poetry authors, and probably much more.
#Basically, Hatch looks like everything we wish poetry was.
# ....................{ KLUDGES ~ path }....................
# Explicitly register all files and subdirectories of the root directory
# containing this top-level "setup.py" script to be importable modules and
# packages (respectively) for the remainder of this Python process if this
# directory has yet to be registered.
#
# Technically, this should *NOT* be required. The current build framework
# (e.g., "pip", "setuptools") should implicitly guarantee this to be the case.
# Indeed, the "setuptools"-based "easy_install" script does just that.
# Unfortunately, "pip" >= 19.0.0 does *NOT* guarantee this to be the case for
# projects defining a "pyproject.toml" file -- which, increasingly, is all of
# them. Although "pip" purports to have resolved this upstream, current stable
# releases appear to suffer the same deficiencies. See also:
# https://github.com/pypa/pip/issues/6163
# Isolate this kludge to a private function for safety.
def _register_dir() -> None:
# Avert thy eyes, purist Pythonistas!
import os, sys
# Absolute dirname of this directory inspired by this StackOverflow answer:
# https://stackoverflow.com/a/8663557/2809027
setup_dirname = os.path.dirname(os.path.realpath(__file__))
# If the current PYTHONPATH does *NOT* already contain this directory...
if setup_dirname not in sys.path:
# Print this registration.
print(
'WARNING: Registering "setup.py" directory for importation under '
'broken installer (e.g., pip >= 19.0.0)...',
file=sys.stderr)
# print('setup_dirname: {}\nsys.path: {!r}'.format(setup_dirname, sys.path))
# Append this directory to the current PYTHONPATH.
sys.path.append(setup_dirname)
# Kludge us up the bomb.
_register_dir()
# ....................{ KLUDGES ~ init }....................
# Explicitly notify the "beartype.__init__" submodule that it is being imported
# at install time from this script. Doing so prevents that submodule from
# implicitly importing from *ANY* "beartype" submodule other than the
# "beartype.meta" submodule, which is the *ONLY* "beartype" submodule guaranteed
# by sheer force of will to be safely importable at install time. All other
# "beartype" submodules should be assumed to be unsafe to import below due to
# potentially importing one or more optional runtime dependencies yet to be
# installed (e.g., the third-party "typing_extensions" package).
#
# Naturally, there exist a countably infinite number of ways to notify the
# "beartype.__init__" submodule that it is being imported at install time. To
# minimize the likelihood of a conflict with other Python subsystems or
# interpreters, we intentionally do *NOT*:
# * Monkey-patch a module or package in the standard library.
# * Detect this script in a stack frame on the call stack.
#
# Instead, we dynamically populate the standard "sys.modules" list of all
# previously imported modules with a fake beartype-specific module. The
# "beartype.__init__" submodule then decides whether to implicitly import from
# potentially unsafe "beartype" submodules by detecting that fake module.
# Isolate this kludge to a private function for safety.
def _notify_beartype() -> None:
# The acid: it burns more than I expected.
from sys import modules
from types import ModuleType
# Dynamically create a new fake module. Look. Just do it.
fake_module = ModuleType('beartype.__is_installing__', 'This is horrible.')
# Dynamically register the fake module. Don't look like that.
modules['beartype.__is_installing__'] = fake_module
# Setuptools made us do it.
_notify_beartype()
# ....................{ IMPORTS }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: To avoid race conditions during setuptools-based installation, this
# module may import *ONLY* from packages guaranteed to exist at the start of
# installation. This includes all standard Python and package submodules but
# *NOT* third-party dependencies, which if currently uninstalled will only be
# installed at some later time in the installation.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
import setuptools
from beartype import meta
# ....................{ METADATA ~ seo }....................
_KEYWORDS = [
'type checking',
'type hints',
'PEP 483',
'PEP 484',
'PEP 544',
'PEP 563',
'PEP 585',
'PEP 586',
'PEP 589',
'PEP 593',
'PEP 604',
'PEP 3141',
]
'''
List of all lowercase alphabetic keywords synopsising this package.
These keywords may be arbitrarily selected so as to pretend to improve search
engine optimization (SEO). In actuality, they do absolutely nothing.
'''
# ....................{ METADATA ~ seo : classifiers }....................
# To minimize desynchronization woes, all
# "Programming Language :: Python :: "-prefixed strings are dynamically
# appended to this list by the init() function below.
_CLASSIFIERS = [
# PyPI-specific version type. The number specified here is a magic constant
# with no relation to this package's version numbering scheme. *sigh*
'Development Status :: 5 - Production/Stable',
# Miscellaneous metadata.
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Software Development :: Code Generators',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Software Development :: Quality Assurance',
'Typing :: Typed',
]
'''
List of all PyPI-specific trove classifier strings synopsizing this project.
Each such string *must* contain either two or three ``" :: "`` substrings
delimiting human-readable capitalized English words formally recognized by the
:mod:`distutils`-specific ``register`` command.
See Also
----------
https://pypi.org/classifiers
Plaintext list of all trove classifier strings recognized by PyPI.
'''
def _sanitize_classifiers(
python_version_min_parts: tuple,
python_version_minor_max: int,
) -> list:
'''
List of all PyPI-specific trove classifier strings synopsizing this
package, manufactured by appending classifiers synopsizing this
package's support for Python major versions (e.g.,
``Programming Language :: Python :: 3.6``, a classifier implying this
package to successfully run under Python 3.6) to the global list
:data:`_CLASSIFIERS` of static classifiers.
Parameters
----------
python_version_min_parts : tuple
Minimum fully-specified version of Python required by this package
as a tuple of integers (e.g., ``(3, 5, 0)`` if this package
requires at least Python 3.5.0).
python_version_minor_max : int
Maximum minor stable version of the current Python 3.x mainline (e.g.,
``9`` if Python 3.9 is the most recent stable version of Python 3.x).
Returns
----------
list
List of all sanitized PyPI-specific trove classifier strings.
'''
assert isinstance(python_version_min_parts, tuple), (
f'"{python_version_min_parts}" not tuple.')
assert isinstance(python_version_minor_max, int), (
f'"{python_version_minor_max}" not integer.')
# Major version of Python required by this package.
PYTHON_VERSION_MAJOR = python_version_min_parts[0]
# List of classifiers to return, copied from the global list for safety.
classifiers_sane = _CLASSIFIERS[:]
# For each minor version of Python 3.x supported by this package,
# formally classify this version as such.
for python_version_minor in range(
python_version_min_parts[1], python_version_minor_max + 1):
classifiers_sane.append(
f'Programming Language :: Python :: '
f'{PYTHON_VERSION_MAJOR}.{python_version_minor}'
)
# print('classifiers: {}'.format(_CLASSIFIERS))
# Return this sanitized list of classifiers.
return classifiers_sane
# ....................{ OPTIONS }....................
# Setuptools-specific options. Keywords not explicitly recognized by either
# setuptools or distutils must be added to the above dictionary instead.
_SETUP_OPTIONS = {
# ..................{ CORE }..................
# Self-explanatory metadata. Note that the following metadata keys are
# instead specified by the "setup.cfg" file:
#
# * "license_file", for unknown reasons. We should probably reconsider.
# * "long_description", since "setup.cfg" supports convenient
# "file: ${relative_filename}" syntax for transcluding the contents of
# arbitrary project-relative files into metadata values. Attempting to do
# so here would require safely opening this file with a context manager,
# reading the contents of this file into a local variable, and passing
# that variable's value as this metadata outside of that context. (Ugh.)
'name': meta.PACKAGE_NAME,
'version': meta.VERSION,
'description': meta.SYNOPSIS,
# ..................{ AUTHORS }..................
'author': meta.AUTHORS,
'author_email': meta.AUTHOR_EMAIL,
'maintainer': meta.AUTHORS,
'maintainer_email': meta.AUTHOR_EMAIL,
# ..................{ URLS }..................
'url': meta.URL_HOMEPAGE,
'download_url': meta.URL_DOWNLOAD,
# Dictionary mapping from arbitrary human-readable terse names describing
# various package-related URLs to those URLs.
'project_urls': {
'Documentation': meta.URL_HOMEPAGE,
'Source': meta.URL_REPO,
'Issues': meta.URL_ISSUES,
'Forums': meta.URL_FORUMS,
'Releases': meta.URL_RELEASES,
},
# ..................{ PYPI }..................
# PyPi-specific meta.
'classifiers': _sanitize_classifiers(
python_version_min_parts=meta.PYTHON_VERSION_MIN_PARTS,
python_version_minor_max=meta.PYTHON_VERSION_MINOR_MAX,
),
'keywords': _KEYWORDS,
'license': meta.LICENSE,
# ..................{ DEPENDENCIES }..................
# Python dependency.
'python_requires': f'>={meta.PYTHON_VERSION_MIN}',
# Mandatory runtime dependencies. This package intentionally requires no
# such dependencies and hopefully never will.
'install_requires': (),
# Optional runtime dependencies. Whereas mandatory dependencies are defined
# as sequences, optional dependencies are defined as a dictionary mapping
# from an arbitrary alphanumeric word to a sequence containing one or more
# such dependencies. Such dependencies are then installable via "pip" by
# suffixing the name of this project by the "["- and "]"-delimited key
# defined below whose value lists the dependencies to be installed (e.g.,
# "sudo pip3 install betse[all]", installing both the package and all
# mandatory and optional dependencies required by the package).
'extras_require': {
# All optional runtime dependencies.
'all': meta.LIBS_RUNTIME_OPTIONAL,
# All mandatory developer dependencies (including all mandatory test-
# and documentation build-time dependencies) as referenced from
# external project documentation for developers.
'dev': meta.LIBS_DEVELOPER_MANDATORY,
# All mandatory Read The Docs (RTD)-specific documentation build-time
# dependencies an arbitrarily named extra. This is required *ONLY*
# for integration with the top-level ".readthedocs.yml" file. See the
# "python" key in that file for further details.
'doc-rtd': meta.LIBS_DOCTIME_MANDATORY_RTD,
# All mandatory tox-specific testing dependencies, copied from the
# "tests_require" key below into an arbitrarily named extra. This is
# required *ONLY* for integration with the top-level "tox.ini" file.
# See the "extras" key in that file for further details.
'test-tox': meta.LIBS_TESTTIME_MANDATORY_TOX,
# All mandatory coverage-specific testing dependencies as an
# arbitrarily named extra, required *ONLY* for integration with the
# top-level "tox.ini" file. See the "extras" key in that file.
'test-tox-coverage': meta.LIBS_TESTTIME_MANDATORY_COVERAGE,
},
# Mandatory testing dependencies.
'tests_require': meta.LIBS_TESTTIME_MANDATORY_TOX,
# ..................{ PACKAGES }..................
# List of the fully-qualified names of all Python packages (i.e.,
# directories containing zero or more Python modules) to be installed,
# including the top-level package and all subpackages of that package. This
# thus excludes:
#
# * The top-level test package and all subpackages of that package,
# defining only test functionality *NOT* intended to be installed with
# this package.
# * "build", caching both setuptools metadata and a complete copy of this
# package, required only by a prior package installation.
#
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: This inspection intentionally omits subdirectories containing no
# "__init__.py" file, despite the remainder of the Python ecosystem
# commonly accepting such subdirectories as subpackages.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
'packages': setuptools.find_packages(exclude=(
f'{meta.PACKAGE_NAME}_test',
f'{meta.PACKAGE_NAME}_test.*',
'build',
)),
# ..................{ PACKAGES ~ data }..................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: *ALL DATA FILES TO BE INSTALLED MUST BE EXPLICITLY MATCHED IN
# THE TOP-LEVEL "MANIFEST.in" FILE.*
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#
# Install all data files (i.e., non-Python files) embedded in the Python
# package tree for this project which are also explicitly matched by the
# top-level "MANIFEST.in" file.
#
# Unlike Python packages, undesirable data files are includable and
# excludable from installation *ONLY* via the external "MANIFEST.in" file.
# This is terrible, of course. (Did you expect otherwise?)
#
# Data files are *NOT* Python modules and hence should *NOT* be embedded in
# the Python package tree. Sadly, the "data_files" key supported by
# setuptools for this purpose is *NOT* cross-platform-portable and is thus
# inherently broken. Why? Because that key either requires usage of
# absolute paths *OR* relative paths relative to absolute paths defined by
# "setup.cfg"; in either case, those paths are absolute. While the current
# platform could be detected and the corresponding absolute path embedded
# in 'data_files', that implementation would be inherently fragile. (That's
# bad.) In lieu of sane setuptools support, we defer to the methodology
# employed by everyone. Setuptools, your death is coming.
#
# See also:
# * "Data Files Support", official documentation for this abomination at:
# https://setuptools.readthedocs.io/en/latest/userguide/datafiles.html
'include_package_data': True,
# Install to an uncompressed directory rather than a compressed archive.
#
# While nothing technically precludes the latter, doing so substantially
# complicates runtime access of data files compressed into this archive
# (e.g., with the pkg_resources.resource_filename() function). How so? By
# decompressing this archive's contents into a temporary directory on
# program startup and removing these contents on program shutdown. Since
# there exists no guarantee this removal will actually be performed (e.g.,
# due to preemptive SIGKILLs), compressed archives are inherently fragile.
#
# Note that MyPy requires upstream PEP 561-compliant dependencies (like
# this project) to explicitly prohibit archival. See also:
# https://mypy.readthedocs.io/en/stable/installed_packages.html
'zip_safe': False,
}
'''
Dictionary unpacked as keyword arguments into the subsequent call of the
:func:`setuptools.setup` function, signifying the set of all package-specific
:mod:`setuptools` options.
'''
# print('extras: {}'.format(setup_options['extras_require']))
# ....................{ SETUP }....................
setuptools.setup(**_SETUP_OPTIONS)