propcache 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. {propcache-0.3.0 → propcache-0.3.2}/.coveragerc +4 -1
  2. {propcache-0.3.0 → propcache-0.3.2}/CHANGES.rst +55 -0
  3. {propcache-0.3.0/src/propcache.egg-info → propcache-0.3.2}/PKG-INFO +58 -2
  4. {propcache-0.3.0 → propcache-0.3.2}/docs/api.rst +1 -1
  5. {propcache-0.3.0 → propcache-0.3.2}/packaging/pep517_backend/_backend.py +20 -21
  6. {propcache-0.3.0 → propcache-0.3.2}/packaging/pep517_backend/_compat.py +9 -14
  7. {propcache-0.3.0 → propcache-0.3.2}/packaging/pep517_backend/_cython_configuration.py +29 -6
  8. {propcache-0.3.0 → propcache-0.3.2}/packaging/pep517_backend/_transformers.py +8 -4
  9. {propcache-0.3.0 → propcache-0.3.2}/packaging/pep517_backend/cli.py +5 -4
  10. {propcache-0.3.0 → propcache-0.3.2}/packaging/pep517_backend/hooks.py +1 -1
  11. {propcache-0.3.0 → propcache-0.3.2}/pyproject.toml +0 -10
  12. propcache-0.3.2/requirements/cython.txt +1 -0
  13. {propcache-0.3.0 → propcache-0.3.2}/requirements/doc.txt +1 -1
  14. propcache-0.3.2/requirements/lint.txt +1 -0
  15. propcache-0.3.0/requirements/test-pure.txt → propcache-0.3.2/requirements/test.txt +2 -1
  16. {propcache-0.3.0 → propcache-0.3.2}/setup.cfg +2 -1
  17. {propcache-0.3.0 → propcache-0.3.2}/src/propcache/__init__.py +1 -1
  18. {propcache-0.3.0 → propcache-0.3.2}/src/propcache/_helpers.py +2 -2
  19. {propcache-0.3.0 → propcache-0.3.2}/src/propcache/_helpers_c.pyx +2 -0
  20. {propcache-0.3.0 → propcache-0.3.2}/src/propcache/_helpers_py.py +11 -7
  21. {propcache-0.3.0 → propcache-0.3.2/src/propcache.egg-info}/PKG-INFO +58 -2
  22. {propcache-0.3.0 → propcache-0.3.2}/src/propcache.egg-info/SOURCES.txt +0 -3
  23. {propcache-0.3.0 → propcache-0.3.2}/tests/conftest.py +1 -1
  24. {propcache-0.3.0 → propcache-0.3.2}/tests/test_cached_property.py +18 -19
  25. {propcache-0.3.0 → propcache-0.3.2}/tests/test_under_cached_property.py +51 -13
  26. propcache-0.3.0/requirements/cython-freethreading.txt +0 -1
  27. propcache-0.3.0/requirements/cython.txt +0 -1
  28. propcache-0.3.0/requirements/lint.txt +0 -1
  29. propcache-0.3.0/requirements/test-freethreading.txt +0 -2
  30. propcache-0.3.0/requirements/test.txt +0 -2
  31. {propcache-0.3.0 → propcache-0.3.2}/CHANGES/.TEMPLATE.rst +0 -0
  32. {propcache-0.3.0 → propcache-0.3.2}/CHANGES/.gitignore +0 -0
  33. {propcache-0.3.0 → propcache-0.3.2}/CHANGES/README.rst +0 -0
  34. {propcache-0.3.0 → propcache-0.3.2}/LICENSE +0 -0
  35. {propcache-0.3.0 → propcache-0.3.2}/MANIFEST.in +0 -0
  36. {propcache-0.3.0 → propcache-0.3.2}/NOTICE +0 -0
  37. {propcache-0.3.0 → propcache-0.3.2}/README.rst +0 -0
  38. {propcache-0.3.0 → propcache-0.3.2}/docs/Makefile +0 -0
  39. {propcache-0.3.0 → propcache-0.3.2}/docs/_static/README.txt +0 -0
  40. {propcache-0.3.0 → propcache-0.3.2}/docs/changes.rst +0 -0
  41. {propcache-0.3.0 → propcache-0.3.2}/docs/conf.py +0 -0
  42. {propcache-0.3.0 → propcache-0.3.2}/docs/contributing/guidelines.rst +0 -0
  43. {propcache-0.3.0 → propcache-0.3.2}/docs/contributing/release_guide.rst +0 -0
  44. {propcache-0.3.0 → propcache-0.3.2}/docs/index.rst +0 -0
  45. {propcache-0.3.0 → propcache-0.3.2}/docs/make.bat +0 -0
  46. {propcache-0.3.0 → propcache-0.3.2}/docs/spelling_wordlist.txt +0 -0
  47. {propcache-0.3.0 → propcache-0.3.2}/packaging/README.md +0 -0
  48. {propcache-0.3.0 → propcache-0.3.2}/packaging/pep517_backend/__init__.py +0 -0
  49. {propcache-0.3.0 → propcache-0.3.2}/packaging/pep517_backend/__main__.py +0 -0
  50. {propcache-0.3.0 → propcache-0.3.2}/pytest.ini +0 -0
  51. {propcache-0.3.0 → propcache-0.3.2}/requirements/codspeed.txt +0 -0
  52. {propcache-0.3.0 → propcache-0.3.2}/requirements/dev.txt +0 -0
  53. {propcache-0.3.0 → propcache-0.3.2}/requirements/doc-spelling.txt +0 -0
  54. {propcache-0.3.0 → propcache-0.3.2}/requirements/towncrier.txt +0 -0
  55. {propcache-0.3.0 → propcache-0.3.2}/src/propcache/api.py +0 -0
  56. {propcache-0.3.0 → propcache-0.3.2}/src/propcache/py.typed +0 -0
  57. {propcache-0.3.0 → propcache-0.3.2}/src/propcache.egg-info/dependency_links.txt +0 -0
  58. {propcache-0.3.0 → propcache-0.3.2}/src/propcache.egg-info/not-zip-safe +0 -0
  59. {propcache-0.3.0 → propcache-0.3.2}/src/propcache.egg-info/top_level.txt +0 -0
  60. {propcache-0.3.0 → propcache-0.3.2}/tests/test_api.py +0 -0
  61. {propcache-0.3.0 → propcache-0.3.2}/tests/test_benchmarks.py +0 -0
  62. {propcache-0.3.0 → propcache-0.3.2}/tests/test_init.py +0 -0
  63. {propcache-0.3.0 → propcache-0.3.2}/towncrier.toml +0 -0
@@ -13,7 +13,10 @@ _site-packages-to-src-mapping =
13
13
 
14
14
  [report]
15
15
  exclude_also =
16
- ^\s*@pytest\.mark\.xfail
16
+ if TYPE_CHECKING
17
+ assert False
18
+ : \.\.\.(\s*#.*)?$
19
+ ^ +\.\.\.$
17
20
  # small library, don't fail when running without C-extension
18
21
  fail_under = 50.00
19
22
  skip_covered = true
@@ -14,6 +14,61 @@ Changelog
14
14
 
15
15
  .. towncrier release notes start
16
16
 
17
+ 0.3.2
18
+ =====
19
+
20
+ *(2025-06-09)*
21
+
22
+
23
+ Improved documentation
24
+ ----------------------
25
+
26
+ - Fixed incorrect decorator usage in the :func:`~propcache.api.under_cached_property` example code -- by :user:`meanmail`.
27
+
28
+ *Related issues and pull requests on GitHub:*
29
+ :issue:`109`.
30
+
31
+
32
+ Packaging updates and notes for downstreams
33
+ -------------------------------------------
34
+
35
+ - Updated to use Cython 3.1 universally across the build path -- by :user:`lysnikolaou`.
36
+
37
+ *Related issues and pull requests on GitHub:*
38
+ :issue:`117`.
39
+
40
+ - Made Cython line tracing opt-in via the ``with-cython-tracing`` build config setting -- by :user:`bdraco`.
41
+
42
+ Previously, line tracing was enabled by default in :file:`pyproject.toml`, which caused build issues for some users and made wheels nearly twice as slow.
43
+
44
+ Now line tracing is only enabled when explicitly requested via ``pip install . --config-setting=with-cython-tracing=true`` or by setting the ``PROPCACHE_CYTHON_TRACING`` environment variable.
45
+
46
+ *Related issues and pull requests on GitHub:*
47
+ :issue:`118`.
48
+
49
+
50
+ ----
51
+
52
+
53
+ 0.3.1
54
+ =====
55
+
56
+ *(2025-03-25)*
57
+
58
+
59
+ Bug fixes
60
+ ---------
61
+
62
+ - Improved typing annotations, fixing some type errors under correct usage
63
+ and improving typing robustness generally -- by :user:`Dreamsorcerer`.
64
+
65
+ *Related issues and pull requests on GitHub:*
66
+ :issue:`103`.
67
+
68
+
69
+ ----
70
+
71
+
17
72
  0.3.0
18
73
  =====
19
74
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: propcache
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Accelerated property cache
5
5
  Home-page: https://github.com/aio-libs/propcache
6
6
  Author: Andrew Svetlov
@@ -35,6 +35,7 @@ Requires-Python: >=3.9
35
35
  Description-Content-Type: text/x-rst
36
36
  License-File: LICENSE
37
37
  License-File: NOTICE
38
+ Dynamic: license-file
38
39
 
39
40
  propcache
40
41
  =========
@@ -151,6 +152,61 @@ Changelog
151
152
 
152
153
  .. towncrier release notes start
153
154
 
155
+ 0.3.2
156
+ =====
157
+
158
+ *(2025-06-09)*
159
+
160
+
161
+ Improved documentation
162
+ ----------------------
163
+
164
+ - Fixed incorrect decorator usage in the ``~propcache.api.under_cached_property`` example code -- by `@meanmail <https://github.com/sponsors/meanmail>`__.
165
+
166
+ *Related issues and pull requests on GitHub:*
167
+ `#109 <https://github.com/aio-libs/propcache/issues/109>`__.
168
+
169
+
170
+ Packaging updates and notes for downstreams
171
+ -------------------------------------------
172
+
173
+ - Updated to use Cython 3.1 universally across the build path -- by `@lysnikolaou <https://github.com/sponsors/lysnikolaou>`__.
174
+
175
+ *Related issues and pull requests on GitHub:*
176
+ `#117 <https://github.com/aio-libs/propcache/issues/117>`__.
177
+
178
+ - Made Cython line tracing opt-in via the ``with-cython-tracing`` build config setting -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
179
+
180
+ Previously, line tracing was enabled by default in ``pyproject.toml``, which caused build issues for some users and made wheels nearly twice as slow.
181
+
182
+ Now line tracing is only enabled when explicitly requested via ``pip install . --config-setting=with-cython-tracing=true`` or by setting the ``PROPCACHE_CYTHON_TRACING`` environment variable.
183
+
184
+ *Related issues and pull requests on GitHub:*
185
+ `#118 <https://github.com/aio-libs/propcache/issues/118>`__.
186
+
187
+
188
+ ----
189
+
190
+
191
+ 0.3.1
192
+ =====
193
+
194
+ *(2025-03-25)*
195
+
196
+
197
+ Bug fixes
198
+ ---------
199
+
200
+ - Improved typing annotations, fixing some type errors under correct usage
201
+ and improving typing robustness generally -- by `@Dreamsorcerer <https://github.com/sponsors/Dreamsorcerer>`__.
202
+
203
+ *Related issues and pull requests on GitHub:*
204
+ `#103 <https://github.com/aio-libs/propcache/issues/103>`__.
205
+
206
+
207
+ ----
208
+
209
+
154
210
  0.3.0
155
211
  =====
156
212
 
@@ -42,7 +42,7 @@ under_cached_property
42
42
  self._data = data
43
43
  self._cache = {}
44
44
 
45
- @cached_property
45
+ @under_cached_property
46
46
  def calculated_data(self):
47
47
  return expensive_operation(self._data)
48
48
 
@@ -4,8 +4,8 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import os
7
- import sysconfig
8
7
  import typing as t
8
+ from collections.abc import Iterator
9
9
  from contextlib import contextmanager, nullcontext, suppress
10
10
  from functools import partial
11
11
  from pathlib import Path
@@ -56,7 +56,7 @@ __all__ = ( # noqa: WPS410
56
56
  'get_requires_for_build_wheel',
57
57
  'prepare_metadata_for_build_wheel',
58
58
  *(
59
- () if _setuptools_build_editable is None
59
+ () if _setuptools_build_editable is None # type: ignore[redundant-expr]
60
60
  else (
61
61
  'build_editable',
62
62
  'get_requires_for_build_editable',
@@ -87,7 +87,7 @@ PURE_PYTHON_MODE_CLI_FALLBACK = not IS_CPYTHON
87
87
  """A fallback for ``pure-python`` is not set."""
88
88
 
89
89
 
90
- def _is_truthy_setting_value(setting_value) -> bool:
90
+ def _is_truthy_setting_value(setting_value: str) -> bool:
91
91
  truthy_values = {'', None, 'true', '1', 'on'}
92
92
  return setting_value.lower() in truthy_values
93
93
 
@@ -108,7 +108,7 @@ def _get_setting_value(
108
108
  continue
109
109
 
110
110
  with suppress(lookup_errors): # type: ignore[arg-type]
111
- return _is_truthy_setting_value(src_mapping[src_key]) # type: ignore[index]
111
+ return _is_truthy_setting_value(src_mapping[src_key]) # type: ignore[arg-type,index]
112
112
 
113
113
  return default
114
114
 
@@ -125,7 +125,7 @@ def _make_pure_python(config_settings: _ConfigDict | None = None) -> bool:
125
125
  def _include_cython_line_tracing(
126
126
  config_settings: _ConfigDict | None = None,
127
127
  *,
128
- default=False,
128
+ default: bool = False,
129
129
  ) -> bool:
130
130
  return _get_setting_value(
131
131
  config_settings,
@@ -136,7 +136,7 @@ def _include_cython_line_tracing(
136
136
 
137
137
 
138
138
  @contextmanager
139
- def patched_distutils_cmd_install():
139
+ def patched_distutils_cmd_install() -> Iterator[None]:
140
140
  """Make `install_lib` of `install` cmd always use `platlib`.
141
141
 
142
142
  :yields: None
@@ -144,19 +144,19 @@ def patched_distutils_cmd_install():
144
144
  # Without this, build_lib puts stuff under `*.data/purelib/` folder
145
145
  orig_finalize = _distutils_install_cmd.finalize_options
146
146
 
147
- def new_finalize_options(self): # noqa: WPS430
147
+ def new_finalize_options(self: _distutils_install_cmd) -> None:
148
148
  self.install_lib = self.install_platlib
149
149
  orig_finalize(self)
150
150
 
151
- _distutils_install_cmd.finalize_options = new_finalize_options
151
+ _distutils_install_cmd.finalize_options = new_finalize_options # type: ignore[method-assign]
152
152
  try:
153
153
  yield
154
154
  finally:
155
- _distutils_install_cmd.finalize_options = orig_finalize
155
+ _distutils_install_cmd.finalize_options = orig_finalize # type: ignore[method-assign]
156
156
 
157
157
 
158
158
  @contextmanager
159
- def patched_dist_has_ext_modules():
159
+ def patched_dist_has_ext_modules() -> Iterator[None]:
160
160
  """Make `has_ext_modules` of `Distribution` always return `True`.
161
161
 
162
162
  :yields: None
@@ -164,15 +164,15 @@ def patched_dist_has_ext_modules():
164
164
  # Without this, build_lib puts stuff under `*.data/platlib/` folder
165
165
  orig_func = _DistutilsDistribution.has_ext_modules
166
166
 
167
- _DistutilsDistribution.has_ext_modules = lambda *args, **kwargs: True
167
+ _DistutilsDistribution.has_ext_modules = lambda *args, **kwargs: True # type: ignore[method-assign]
168
168
  try:
169
169
  yield
170
170
  finally:
171
- _DistutilsDistribution.has_ext_modules = orig_func
171
+ _DistutilsDistribution.has_ext_modules = orig_func # type: ignore[method-assign]
172
172
 
173
173
 
174
174
  @contextmanager
175
- def patched_dist_get_long_description():
175
+ def patched_dist_get_long_description() -> Iterator[None]:
176
176
  """Make `has_ext_modules` of `Distribution` always return `True`.
177
177
 
178
178
  :yields: None
@@ -180,16 +180,17 @@ def patched_dist_get_long_description():
180
180
  # Without this, build_lib puts stuff under `*.data/platlib/` folder
181
181
  _orig_func = _DistutilsDistributionMetadata.get_long_description
182
182
 
183
- def _get_sanitized_long_description(self):
183
+ def _get_sanitized_long_description(self: _DistutilsDistributionMetadata) -> str:
184
+ assert self.long_description is not None
184
185
  return sanitize_rst_roles(self.long_description)
185
186
 
186
- _DistutilsDistributionMetadata.get_long_description = (
187
+ _DistutilsDistributionMetadata.get_long_description = ( # type: ignore[method-assign]
187
188
  _get_sanitized_long_description
188
189
  )
189
190
  try:
190
191
  yield
191
192
  finally:
192
- _DistutilsDistributionMetadata.get_long_description = _orig_func
193
+ _DistutilsDistributionMetadata.get_long_description = _orig_func # type: ignore[method-assign]
193
194
 
194
195
 
195
196
  def _exclude_dir_path(
@@ -291,9 +292,9 @@ def maybe_prebuild_c_extensions(
291
292
  with build_dir_ctx:
292
293
  config = _get_local_cython_config()
293
294
 
294
- cythonize_args = _make_cythonize_cli_args_from_config(config)
295
+ cythonize_args = _make_cythonize_cli_args_from_config(config, cython_line_tracing_requested)
295
296
  with _patched_cython_env(config['env'], cython_line_tracing_requested):
296
- _cythonize_cli_cmd(cythonize_args)
297
+ _cythonize_cli_cmd(cythonize_args) # type: ignore[no-untyped-call]
297
298
  with patched_distutils_cmd_install():
298
299
  with patched_dist_has_ext_modules():
299
300
  yield
@@ -374,10 +375,8 @@ def get_requires_for_build_wheel(
374
375
 
375
376
  if is_pure_python_build:
376
377
  c_ext_build_deps = []
377
- elif sysconfig.get_config_var('Py_GIL_DISABLED'):
378
- c_ext_build_deps = ['Cython ~= 3.1.0a1']
379
378
  else:
380
- c_ext_build_deps = ['Cython ~= 3.0.12']
379
+ c_ext_build_deps = ['Cython ~= 3.1.0']
381
380
 
382
381
  return _setuptools_get_requires_for_build_wheel(
383
382
  config_settings=config_settings,
@@ -1,17 +1,20 @@
1
1
  """Cross-python stdlib shims."""
2
2
 
3
3
  import os
4
- import typing as t
4
+ import sys
5
+ from collections.abc import Iterator
5
6
  from contextlib import contextmanager
6
7
  from pathlib import Path
7
8
 
8
9
  # isort: off
9
- try:
10
- from contextlib import chdir as chdir_cm # type: ignore[attr-defined, unused-ignore] # noqa: E501
11
- except ImportError:
10
+ if sys.version_info >= (3, 11):
11
+ from contextlib import chdir as chdir_cm
12
+ from tomllib import loads as load_toml_from_string
13
+ else:
14
+ from tomli import loads as load_toml_from_string
12
15
 
13
- @contextmanager # type: ignore[no-redef, unused-ignore]
14
- def chdir_cm(path: os.PathLike) -> t.Iterator[None]:
16
+ @contextmanager
17
+ def chdir_cm(path: os.PathLike) -> Iterator[None]:
15
18
  """Temporarily change the current directory, recovering on exit."""
16
19
  original_wd = Path.cwd()
17
20
  os.chdir(path)
@@ -22,12 +25,4 @@ except ImportError:
22
25
 
23
26
 
24
27
  # isort: on
25
-
26
-
27
- try:
28
- from tomllib import loads as load_toml_from_string
29
- except ImportError:
30
- from tomli import loads as load_toml_from_string
31
-
32
-
33
28
  __all__ = ("chdir_cm", "load_toml_from_string") # noqa: WPS410
@@ -3,9 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
+ from collections.abc import Iterator
6
7
  from contextlib import contextmanager
7
8
  from pathlib import Path
8
9
  from sys import version_info as _python_version_tuple
10
+ from typing import TypedDict
9
11
 
10
12
  from expandvars import expandvars
11
13
 
@@ -13,7 +15,14 @@ from ._compat import load_toml_from_string
13
15
  from ._transformers import get_cli_kwargs_from_config, get_enabled_cli_flags_from_config
14
16
 
15
17
 
16
- def get_local_cython_config() -> dict:
18
+ class Config(TypedDict):
19
+ env: dict[str, str]
20
+ flags: dict[str, bool]
21
+ kwargs: dict[str, str | dict[str, str]]
22
+ src: list[str]
23
+
24
+
25
+ def get_local_cython_config() -> Config:
17
26
  """Grab optional build dependencies from pyproject.toml config.
18
27
 
19
28
  :returns: config section from ``pyproject.toml``
@@ -67,20 +76,34 @@ def get_local_cython_config() -> dict:
67
76
  """
68
77
  config_toml_txt = (Path.cwd().resolve() / 'pyproject.toml').read_text()
69
78
  config_mapping = load_toml_from_string(config_toml_txt)
70
- return config_mapping['tool']['local']['cythonize']
79
+ return config_mapping['tool']['local']['cythonize'] # type: ignore[no-any-return]
71
80
 
72
81
 
73
- def make_cythonize_cli_args_from_config(config) -> list[str]:
82
+ def _configure_cython_line_tracing(config_kwargs: dict[str, str | dict[str, str]], cython_line_tracing_requested: bool) -> None:
83
+ """Configure Cython line tracing directives if requested."""
84
+ # If line tracing is requested, add it to the directives
85
+ if cython_line_tracing_requested:
86
+ directives = config_kwargs.setdefault('directive', {})
87
+ assert isinstance(directives, dict) # Type narrowing for mypy
88
+ directives['linetrace'] = 'True'
89
+ directives['profile'] = 'True'
90
+
91
+
92
+ def make_cythonize_cli_args_from_config(config: Config, cython_line_tracing_requested: bool = False) -> list[str]:
74
93
  py_ver_arg = f'-{_python_version_tuple.major!s}'
75
94
 
76
95
  cli_flags = get_enabled_cli_flags_from_config(config['flags'])
77
- cli_kwargs = get_cli_kwargs_from_config(config['kwargs'])
96
+ config_kwargs = config['kwargs']
97
+
98
+ _configure_cython_line_tracing(config_kwargs, cython_line_tracing_requested)
99
+
100
+ cli_kwargs = get_cli_kwargs_from_config(config_kwargs)
78
101
 
79
102
  return cli_flags + [py_ver_arg] + cli_kwargs + ['--'] + config['src']
80
103
 
81
104
 
82
105
  @contextmanager
83
- def patched_env(env: dict[str, str], cython_line_tracing_requested: bool):
106
+ def patched_env(env: dict[str, str], cython_line_tracing_requested: bool) -> Iterator[None]:
84
107
  """Temporary set given env vars.
85
108
 
86
109
  :param env: tmp env vars to set
@@ -89,7 +112,7 @@ def patched_env(env: dict[str, str], cython_line_tracing_requested: bool):
89
112
  :yields: None
90
113
  """
91
114
  orig_env = os.environ.copy()
92
- expanded_env = {name: expandvars(var_val) for name, var_val in env.items()}
115
+ expanded_env = {name: expandvars(var_val) for name, var_val in env.items()} # type: ignore[no-untyped-call]
93
116
  os.environ.update(expanded_env)
94
117
 
95
118
  if cython_line_tracing_requested:
@@ -1,26 +1,30 @@
1
1
  """Data conversion helpers for the in-tree PEP 517 build backend."""
2
2
 
3
+ from collections.abc import Iterable, Iterator, Mapping
3
4
  from itertools import chain
4
5
  from re import sub as _substitute_with_regexp
6
+ from typing import Union
5
7
 
6
8
 
7
- def _emit_opt_pairs(opt_pair):
9
+ def _emit_opt_pairs(opt_pair: tuple[str, Union[dict[str, str], str]]) -> Iterator[str]:
8
10
  flag, flag_value = opt_pair
9
11
  flag_opt = f"--{flag!s}"
10
12
  if isinstance(flag_value, dict):
11
- sub_pairs = flag_value.items()
13
+ sub_pairs: Iterable[tuple[str, ...]] = flag_value.items()
12
14
  else:
13
15
  sub_pairs = ((flag_value,),)
14
16
 
15
17
  yield from ("=".join(map(str, (flag_opt,) + pair)) for pair in sub_pairs)
16
18
 
17
19
 
18
- def get_cli_kwargs_from_config(kwargs_map):
20
+ def get_cli_kwargs_from_config(
21
+ kwargs_map: dict[str, Union[str, dict[str, str]]],
22
+ ) -> list[str]:
19
23
  """Make a list of options with values from config."""
20
24
  return list(chain.from_iterable(map(_emit_opt_pairs, kwargs_map.items())))
21
25
 
22
26
 
23
- def get_enabled_cli_flags_from_config(flags_map):
27
+ def get_enabled_cli_flags_from_config(flags_map: Mapping[str, bool]) -> list[str]:
24
28
  """Make a list of enabled boolean flags from config."""
25
29
  return [f"--{flag}" for flag, is_enabled in flags_map.items() if is_enabled]
26
30
 
@@ -3,11 +3,12 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import sys
6
+ from collections.abc import Sequence
6
7
  from itertools import chain
7
8
  from pathlib import Path
8
9
 
10
+ from Cython.Compiler.CmdLine import parse_command_line as _split_cython_cli_args
9
11
  from Cython.Compiler.Main import compile as _translate_cython_cli_cmd
10
- from Cython.Compiler.Main import parse_command_line as _split_cython_cli_args
11
12
 
12
13
  from ._cython_configuration import get_local_cython_config as _get_local_cython_config
13
14
  from ._cython_configuration import (
@@ -18,7 +19,7 @@ from ._cython_configuration import patched_env as _patched_cython_env
18
19
  _PROJECT_PATH = Path(__file__).parents[2]
19
20
 
20
21
 
21
- def run_main_program(argv) -> int | str:
22
+ def run_main_program(argv: Sequence[str]) -> int | str:
22
23
  """Invoke ``translate-cython`` or fail."""
23
24
  if len(argv) != 2:
24
25
  return 'This program only accepts one argument -- "translate-cython"'
@@ -38,12 +39,12 @@ def run_main_program(argv) -> int | str:
38
39
  )
39
40
  translate_cython_cli_args = _make_cythonize_cli_args_from_config(config)
40
41
 
41
- cython_options, cython_sources = _split_cython_cli_args(
42
+ cython_options, cython_sources = _split_cython_cli_args( # type: ignore[no-untyped-call]
42
43
  translate_cython_cli_args,
43
44
  )
44
45
 
45
46
  with _patched_cython_env(config['env'], cython_line_tracing_requested=True):
46
- return _translate_cython_cli_cmd(
47
+ return _translate_cython_cli_cmd( # type: ignore[no-untyped-call,no-any-return]
47
48
  cython_sources,
48
49
  cython_options,
49
50
  ).num_errors
@@ -2,7 +2,7 @@
2
2
 
3
3
  from contextlib import suppress as _suppress
4
4
 
5
- from setuptools.build_meta import * # Re-exporting PEP 517 hooks # pylint: disable=unused-wildcard-import,wildcard-import # noqa: E501, F401, F403
5
+ from setuptools.build_meta import * # Re-exporting PEP 517 hooks # pylint: disable=unused-wildcard-import,wildcard-import # noqa: F401, F403
6
6
 
7
7
  # Re-exporting PEP 517 hooks
8
8
  from ._backend import ( # type: ignore[assignment]
@@ -48,7 +48,6 @@ keep-going = false
48
48
  # https://cython.rtfd.io/en/latest/src/userguide/source_files_and_compilation.html#compiler-directives
49
49
  embedsignature = "True"
50
50
  emit_code_comments = "True"
51
- linetrace = "True" # Implies `profile=True`
52
51
 
53
52
  [tool.local.cythonize.kwargs.compile-time-env]
54
53
  # This section can contain compile time env vars
@@ -95,12 +94,3 @@ before-test = [] # Windows cmd has different syntax and pip chooses wheels
95
94
 
96
95
  [tool.cibuildwheel.linux]
97
96
  before-all = "yum install -y libffi-devel || apk add --upgrade libffi-dev || apt-get install libffi-dev"
98
-
99
- # TODO: Remove this when there's a Cython 3.1 final release
100
- # Remove PIP_CONSTRAINT from the environment
101
- [[tool.cibuildwheel.overrides]]
102
- select = "cp313t-*"
103
-
104
- test-requires = "-r requirements/test-freethreading.txt"
105
- inherit.environment = "append"
106
- environment = {PIP_CONSTRAINT = "requirements/cython-freethreading.txt"}
@@ -0,0 +1 @@
1
+ cython==3.1.2
@@ -1,4 +1,4 @@
1
1
  -r towncrier.txt
2
2
  myst-parser >= 0.10.0
3
- sphinx==8.2.0
3
+ sphinx==8.2.3
4
4
  sphinxcontrib-towncrier
@@ -0,0 +1 @@
1
+ pre-commit==4.2.0
@@ -1,4 +1,5 @@
1
+ -r cython.txt
1
2
  covdefaults
2
- pytest==8.3.4
3
+ pytest==8.3.5
3
4
  pytest-cov>=2.3.1
4
5
  pytest-xdist
@@ -73,7 +73,8 @@ include_package_data = True
73
73
  max-line-length = 79
74
74
 
75
75
  [flake8]
76
- ignore = E203,E301,E302,E704,W503,W504,F811
76
+ extend-select = B950
77
+ ignore = E203,E301,E302,E501,E704,W503,W504,F811
77
78
  max-line-length = 88
78
79
  per-file-ignores =
79
80
 
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  _PUBLIC_API = ("cached_property", "under_cached_property")
6
6
 
7
- __version__ = "0.3.0"
7
+ __version__ = "0.3.2"
8
8
  __all__ = ()
9
9
 
10
10
  # Imports have moved to `propcache.api` in 0.2.0+.
@@ -19,8 +19,8 @@ if TYPE_CHECKING:
19
19
  under_cached_property = under_cached_property_py
20
20
  elif not NO_EXTENSIONS: # pragma: no branch
21
21
  try:
22
- from ._helpers_c import cached_property as cached_property_c # type: ignore[attr-defined, unused-ignore] # noqa: E501
23
- from ._helpers_c import under_cached_property as under_cached_property_c # type: ignore[attr-defined, unused-ignore] # noqa: E501
22
+ from ._helpers_c import cached_property as cached_property_c # type: ignore[attr-defined, unused-ignore]
23
+ from ._helpers_c import under_cached_property as under_cached_property_c # type: ignore[attr-defined, unused-ignore]
24
24
 
25
25
  cached_property = cached_property_c
26
26
  under_cached_property = under_cached_property_c
@@ -37,6 +37,8 @@ cdef class under_cached_property:
37
37
  def __set__(self, inst, value):
38
38
  raise AttributeError("cached property is read-only")
39
39
 
40
+ __class_getitem__ = classmethod(GenericAlias)
41
+
40
42
 
41
43
  cdef class cached_property:
42
44
  """Use as a class method decorator. It operates almost exactly like
@@ -1,6 +1,7 @@
1
1
  """Various helper functions."""
2
2
 
3
3
  import sys
4
+ from collections.abc import Mapping
4
5
  from functools import cached_property
5
6
  from typing import Any, Callable, Generic, Optional, Protocol, TypeVar, Union, overload
6
7
 
@@ -13,10 +14,13 @@ else:
13
14
  Self = Any
14
15
 
15
16
  _T = TypeVar("_T")
17
+ # We use Mapping to make it possible to use TypedDict, but this isn't
18
+ # technically type safe as we need to assign into the dict.
19
+ _Cache = TypeVar("_Cache", bound=Mapping[str, Any])
16
20
 
17
21
 
18
- class _TSelf(Protocol, Generic[_T]):
19
- _cache: dict[str, _T]
22
+ class _CacheImpl(Protocol[_Cache]):
23
+ _cache: _Cache
20
24
 
21
25
 
22
26
  class under_cached_property(Generic[_T]):
@@ -29,7 +33,7 @@ class under_cached_property(Generic[_T]):
29
33
  variable. It is, in Python parlance, a data descriptor.
30
34
  """
31
35
 
32
- def __init__(self, wrapped: Callable[..., _T]) -> None:
36
+ def __init__(self, wrapped: Callable[[Any], _T]) -> None:
33
37
  self.wrapped = wrapped
34
38
  self.__doc__ = wrapped.__doc__
35
39
  self.name = wrapped.__name__
@@ -38,19 +42,19 @@ class under_cached_property(Generic[_T]):
38
42
  def __get__(self, inst: None, owner: Optional[type[object]] = None) -> Self: ...
39
43
 
40
44
  @overload
41
- def __get__(self, inst: _TSelf[_T], owner: Optional[type[object]] = None) -> _T: ...
45
+ def __get__(self, inst: _CacheImpl[Any], owner: Optional[type[object]] = None) -> _T: ... # type: ignore[misc]
42
46
 
43
47
  def __get__(
44
- self, inst: Optional[_TSelf[_T]], owner: Optional[type[object]] = None
48
+ self, inst: Optional[_CacheImpl[Any]], owner: Optional[type[object]] = None
45
49
  ) -> Union[_T, Self]:
46
50
  if inst is None:
47
51
  return self
48
52
  try:
49
- return inst._cache[self.name]
53
+ return inst._cache[self.name] # type: ignore[no-any-return]
50
54
  except KeyError:
51
55
  val = self.wrapped(inst)
52
56
  inst._cache[self.name] = val
53
57
  return val
54
58
 
55
- def __set__(self, inst: _TSelf[_T], value: _T) -> None:
59
+ def __set__(self, inst: _CacheImpl[Any], value: _T) -> None:
56
60
  raise AttributeError("cached property is read-only")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: propcache
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Accelerated property cache
5
5
  Home-page: https://github.com/aio-libs/propcache
6
6
  Author: Andrew Svetlov
@@ -35,6 +35,7 @@ Requires-Python: >=3.9
35
35
  Description-Content-Type: text/x-rst
36
36
  License-File: LICENSE
37
37
  License-File: NOTICE
38
+ Dynamic: license-file
38
39
 
39
40
  propcache
40
41
  =========
@@ -151,6 +152,61 @@ Changelog
151
152
 
152
153
  .. towncrier release notes start
153
154
 
155
+ 0.3.2
156
+ =====
157
+
158
+ *(2025-06-09)*
159
+
160
+
161
+ Improved documentation
162
+ ----------------------
163
+
164
+ - Fixed incorrect decorator usage in the ``~propcache.api.under_cached_property`` example code -- by `@meanmail <https://github.com/sponsors/meanmail>`__.
165
+
166
+ *Related issues and pull requests on GitHub:*
167
+ `#109 <https://github.com/aio-libs/propcache/issues/109>`__.
168
+
169
+
170
+ Packaging updates and notes for downstreams
171
+ -------------------------------------------
172
+
173
+ - Updated to use Cython 3.1 universally across the build path -- by `@lysnikolaou <https://github.com/sponsors/lysnikolaou>`__.
174
+
175
+ *Related issues and pull requests on GitHub:*
176
+ `#117 <https://github.com/aio-libs/propcache/issues/117>`__.
177
+
178
+ - Made Cython line tracing opt-in via the ``with-cython-tracing`` build config setting -- by `@bdraco <https://github.com/sponsors/bdraco>`__.
179
+
180
+ Previously, line tracing was enabled by default in ``pyproject.toml``, which caused build issues for some users and made wheels nearly twice as slow.
181
+
182
+ Now line tracing is only enabled when explicitly requested via ``pip install . --config-setting=with-cython-tracing=true`` or by setting the ``PROPCACHE_CYTHON_TRACING`` environment variable.
183
+
184
+ *Related issues and pull requests on GitHub:*
185
+ `#118 <https://github.com/aio-libs/propcache/issues/118>`__.
186
+
187
+
188
+ ----
189
+
190
+
191
+ 0.3.1
192
+ =====
193
+
194
+ *(2025-03-25)*
195
+
196
+
197
+ Bug fixes
198
+ ---------
199
+
200
+ - Improved typing annotations, fixing some type errors under correct usage
201
+ and improving typing robustness generally -- by `@Dreamsorcerer <https://github.com/sponsors/Dreamsorcerer>`__.
202
+
203
+ *Related issues and pull requests on GitHub:*
204
+ `#103 <https://github.com/aio-libs/propcache/issues/103>`__.
205
+
206
+
207
+ ----
208
+
209
+
154
210
  0.3.0
155
211
  =====
156
212
 
@@ -31,14 +31,11 @@ packaging/pep517_backend/_transformers.py
31
31
  packaging/pep517_backend/cli.py
32
32
  packaging/pep517_backend/hooks.py
33
33
  requirements/codspeed.txt
34
- requirements/cython-freethreading.txt
35
34
  requirements/cython.txt
36
35
  requirements/dev.txt
37
36
  requirements/doc-spelling.txt
38
37
  requirements/doc.txt
39
38
  requirements/lint.txt
40
- requirements/test-freethreading.txt
41
- requirements/test-pure.txt
42
39
  requirements/test.txt
43
40
  requirements/towncrier.txt
44
41
  src/propcache/__init__.py
@@ -53,7 +53,7 @@ class PropcacheImplementation:
53
53
  )
54
54
  def propcache_implementation(request: pytest.FixtureRequest) -> PropcacheImplementation:
55
55
  """Return a propcache variant facade."""
56
- return request.param
56
+ return request.param # type: ignore[no-any-return]
57
57
 
58
58
 
59
59
  @pytest.fixture(scope="session")
@@ -1,14 +1,22 @@
1
+ import sys
2
+ from collections.abc import Callable
1
3
  from operator import not_
2
- from typing import Protocol
4
+ from typing import TYPE_CHECKING, Any, Protocol, TypeVar
3
5
 
4
6
  import pytest
5
7
 
6
8
  from propcache.api import cached_property
7
9
 
10
+ if sys.version_info >= (3, 11):
11
+ from typing import assert_type
12
+
13
+ _T_co = TypeVar("_T_co", covariant=True)
8
14
 
9
- class APIProtocol(Protocol):
10
15
 
11
- cached_property: type[cached_property]
16
+ class APIProtocol(Protocol):
17
+ def cached_property(
18
+ self, func: Callable[[Any], _T_co]
19
+ ) -> cached_property[_T_co]: ...
12
20
 
13
21
 
14
22
  def test_cached_property(propcache_module: APIProtocol) -> None:
@@ -21,23 +29,11 @@ def test_cached_property(propcache_module: APIProtocol) -> None:
21
29
  return 1
22
30
 
23
31
  a = A()
32
+ if sys.version_info >= (3, 11):
33
+ assert_type(a.prop, int)
24
34
  assert a.prop == 1
25
35
 
26
36
 
27
- def test_cached_property_class(propcache_module: APIProtocol) -> None:
28
- class A:
29
- def __init__(self) -> None:
30
- """Init."""
31
- # self._cache not set because its never accessed in this test
32
-
33
- @propcache_module.cached_property
34
- def prop(self) -> None:
35
- """Docstring."""
36
-
37
- assert isinstance(A.prop, propcache_module.cached_property)
38
- assert A.prop.__doc__ == "Docstring."
39
-
40
-
41
37
  def test_cached_property_without_cache(propcache_module: APIProtocol) -> None:
42
38
  class A:
43
39
 
@@ -53,7 +49,7 @@ def test_cached_property_without_cache(propcache_module: APIProtocol) -> None:
53
49
  a = A()
54
50
 
55
51
  with pytest.raises(AttributeError):
56
- a.prop = 123
52
+ a.prop = 123 # type: ignore[assignment]
57
53
 
58
54
 
59
55
  def test_cached_property_check_without_cache(propcache_module: APIProtocol) -> None:
@@ -96,7 +92,10 @@ def test_cached_property_class_docstring(propcache_module: APIProtocol) -> None:
96
92
  def prop(self) -> None:
97
93
  """Docstring."""
98
94
 
99
- assert isinstance(A.prop, propcache_module.cached_property)
95
+ if TYPE_CHECKING:
96
+ assert isinstance(A.prop, cached_property)
97
+ else:
98
+ assert isinstance(A.prop, propcache_module.cached_property)
100
99
  assert "Docstring." == A.prop.__doc__
101
100
 
102
101
 
@@ -1,13 +1,21 @@
1
- from typing import Any, Protocol
1
+ import sys
2
+ from collections.abc import Callable
3
+ from typing import TYPE_CHECKING, Any, Protocol, TypedDict, TypeVar
2
4
 
3
5
  import pytest
4
6
 
5
7
  from propcache.api import under_cached_property
6
8
 
9
+ if sys.version_info >= (3, 11):
10
+ from typing import assert_type
11
+
12
+ _T_co = TypeVar("_T_co", covariant=True)
7
13
 
8
- class APIProtocol(Protocol):
9
14
 
10
- under_cached_property: type[under_cached_property]
15
+ class APIProtocol(Protocol):
16
+ def under_cached_property(
17
+ self, func: Callable[[Any], _T_co]
18
+ ) -> under_cached_property[_T_co]: ...
11
19
 
12
20
 
13
21
  def test_under_cached_property(propcache_module: APIProtocol) -> None:
@@ -19,21 +27,45 @@ def test_under_cached_property(propcache_module: APIProtocol) -> None:
19
27
  def prop(self) -> int:
20
28
  return 1
21
29
 
30
+ @propcache_module.under_cached_property
31
+ def prop2(self) -> str:
32
+ return "foo"
33
+
22
34
  a = A()
35
+ if sys.version_info >= (3, 11):
36
+ assert_type(a.prop, int)
23
37
  assert a.prop == 1
38
+ if sys.version_info >= (3, 11):
39
+ assert_type(a.prop2, str)
40
+ assert a.prop2 == "foo"
24
41
 
25
42
 
26
- def test_under_cached_property_class(propcache_module: APIProtocol) -> None:
43
+ def test_under_cached_property_typeddict(propcache_module: APIProtocol) -> None:
44
+ """Test static typing passes with TypedDict."""
45
+
46
+ class _Cache(TypedDict, total=False):
47
+ prop: int
48
+ prop2: str
49
+
27
50
  class A:
28
51
  def __init__(self) -> None:
29
- """Init."""
52
+ self._cache: _Cache = {}
30
53
 
31
54
  @propcache_module.under_cached_property
32
- def prop(self) -> None:
33
- """Docstring."""
55
+ def prop(self) -> int:
56
+ return 1
57
+
58
+ @propcache_module.under_cached_property
59
+ def prop2(self) -> str:
60
+ return "foo"
34
61
 
35
- assert isinstance(A.prop, propcache_module.under_cached_property)
36
- assert A.prop.__doc__ == "Docstring."
62
+ a = A()
63
+ if sys.version_info >= (3, 11):
64
+ assert_type(a.prop, int)
65
+ assert a.prop == 1
66
+ if sys.version_info >= (3, 11):
67
+ assert_type(a.prop2, str)
68
+ assert a.prop2 == "foo"
37
69
 
38
70
 
39
71
  def test_under_cached_property_assignment(propcache_module: APIProtocol) -> None:
@@ -48,7 +80,7 @@ def test_under_cached_property_assignment(propcache_module: APIProtocol) -> None
48
80
  a = A()
49
81
 
50
82
  with pytest.raises(AttributeError):
51
- a.prop = 123
83
+ a.prop = 123 # type: ignore[assignment]
52
84
 
53
85
 
54
86
  def test_under_cached_property_without_cache(propcache_module: APIProtocol) -> None:
@@ -64,7 +96,7 @@ def test_under_cached_property_without_cache(propcache_module: APIProtocol) -> N
64
96
  a = A()
65
97
 
66
98
  with pytest.raises(AttributeError):
67
- a.prop = 123
99
+ a.prop = 123 # type: ignore[assignment]
68
100
 
69
101
 
70
102
  def test_under_cached_property_check_without_cache(
@@ -105,10 +137,16 @@ def test_under_cached_property_class_docstring(propcache_module: APIProtocol) ->
105
137
  """Init."""
106
138
 
107
139
  @propcache_module.under_cached_property
108
- def prop(self) -> Any:
140
+ def prop(self) -> None:
109
141
  """Docstring."""
110
142
 
111
- assert isinstance(A.prop, propcache_module.under_cached_property)
143
+ if TYPE_CHECKING:
144
+ # At type checking, the fixture doesn't represent the real module, so
145
+ # we use the global-level imported module to verify the isinstance() check here
146
+ # matches the behaviour users would see in real code.
147
+ assert isinstance(A.prop, under_cached_property)
148
+ else:
149
+ assert isinstance(A.prop, propcache_module.under_cached_property)
112
150
  assert "Docstring." == A.prop.__doc__
113
151
 
114
152
 
@@ -1 +0,0 @@
1
- cython==3.1.0a1
@@ -1 +0,0 @@
1
- cython==3.0.12
@@ -1 +0,0 @@
1
- pre-commit==4.1.0
@@ -1,2 +0,0 @@
1
- -r cython-freethreading.txt
2
- -r test-pure.txt
@@ -1,2 +0,0 @@
1
- -r cython.txt
2
- -r test-pure.txt
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes