denial 0.0.2__tar.gz → 0.0.3__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.
denial-0.0.3/PKG-INFO ADDED
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.1
2
+ Name: denial
3
+ Version: 0.0.3
4
+ Summary: Is one None not enough for you? There's more
5
+ Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
6
+ Project-URL: Source, https://github.com/pomponchik/denial
7
+ Project-URL: Tracker, https://github.com/pomponchik/denial/issues
8
+ Keywords: None,sentinel
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Operating System :: MacOS :: MacOS X
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: POSIX
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: Free Threading
23
+ Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
24
+ Classifier: License :: OSI Approved :: MIT License
25
+ Classifier: Intended Audience :: Developers
26
+ Classifier: Topic :: Software Development :: Libraries
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+
31
+ <details>
32
+ <summary>ⓘ</summary>
33
+
34
+ [![Downloads](https://static.pepy.tech/badge/denial/month)](https://pepy.tech/project/denial)
35
+ [![Downloads](https://static.pepy.tech/badge/denial)](https://pepy.tech/project/denial)
36
+ [![Coverage Status](https://coveralls.io/repos/github/pomponchik/denial/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/denial?branch=main)
37
+ [![Lines of code](https://sloc.xyz/github/pomponchik/denial/?category=code)](https://github.com/boyter/scc/)
38
+ [![Hits-of-Code](https://hitsofcode.com/github/pomponchik/denial?branch=main&label=Hits-of-Code&exclude=docs/)](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
39
+ [![Test-Package](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
40
+ [![Python versions](https://img.shields.io/pypi/pyversions/denial.svg)](https://pypi.python.org/pypi/denial)
41
+ [![PyPI version](https://badge.fury.io/py/denial.svg)](https://badge.fury.io/py/denial)
42
+ [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
43
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
44
+ [![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pomponchik/denial)
45
+
46
+ </details>
47
+
48
+
49
+ ![logo](https://raw.githubusercontent.com/pomponchik/denial/develop/docs/assets/logo_1.svg)
50
+
51
+
52
+ The [`None`](https://docs.python.org/3/library/constants.html#None) constant built into Python is convenient for client code, but it is often insufficient when creating libraries. The fact is that this makes it impossible to [distinguish situations](https://colinmcginn.net/truth-value-gaps-and-meaning/) where a value is *undefined* from situations where it is *defined as undefined*. Does that sound too abstract?
53
+
54
+ In fact, the problem of this distinction is found everywhere in library development. `Sentinel objects` are used to resolve it, and [many modules](https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/) from the standard library define their own. For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value.
55
+
56
+ However, we can't all use sentinel objects from some built-in module if we don't need the functionality of that module. Until the [PEP](https://peps.python.org/pep-0661/) has been adopted on this topic, it is better to use a special package containing only this primitive. Such a package is `denial`. It defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations. In addition, this library offers a special class that allows you to create your own sentinels.
57
+
58
+
59
+ ## Table of contents
60
+
61
+ - [**Installation**](#installation)
62
+ - [**The second None**](#the-second-none)
63
+ - [**Your own None objects**](#your-own-none-objects)
64
+ - [**Type hinting**](#type-hinting)
65
+
66
+
67
+ ## Installation
68
+
69
+ Install it:
70
+
71
+ ```bash
72
+ pip install denial
73
+ ```
74
+
75
+ You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld).
76
+
77
+
78
+ ## The second `None`
79
+
80
+ This library defines an object that is proposed to be used in almost the same way as a regular `None`. This is how it is imported:
81
+
82
+ ```python
83
+ from denial import InnerNone
84
+ ```
85
+
86
+ This object is equal only to itself:
87
+
88
+ ```python
89
+ print(InnerNone == InnerNone)
90
+ #> True
91
+ print(InnerNone == False)
92
+ #> False
93
+ ```
94
+
95
+ This object is also an instance of `InnerNoneType` class (an analog of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), however, is not inherited from this), which makes it possible to check through [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance):
96
+
97
+ ```python
98
+ from denial import InnerNoneType
99
+
100
+ print(isinstance(InnerNone, InnerNoneType))
101
+ #> True
102
+ ```
103
+
104
+ It is recommended to use the `InnerNone` object inside libraries where a value close to None is required, but meaning a situation where the value is not really set, rather than set as `None`. This object should be completely isolated from the user code space. None of the public methods of your library should return this object.
105
+
106
+
107
+ ## Your own `None` objects
108
+
109
+ If `None` and [`InnerNone`](#the-second-none) are not enough for you, you can create your own similar objects by instantiating `InnerNoneType`:
110
+
111
+ ```python
112
+ sentinel = InnerNoneType()
113
+ ```
114
+
115
+ This object will also be equal only to itself:
116
+
117
+ ```python
118
+ print(sentinel == sentinel)
119
+ #> True
120
+
121
+ print(sentinel == InnerNoneType()) # Comparison with another object of the same type
122
+ #> False
123
+ print(sentinel == InnerNone) # Also comparison with another object of the same type
124
+ #> False
125
+ print(sentinel == None) # Comparison with None
126
+ #> False
127
+ print(sentinel == 123) # Comparison with an arbitrary object
128
+ #> False
129
+ ```
130
+
131
+ You can also pass an integer or a string to the class constructor. An `InnerNoneType` object is equal to another such object with the same argument:
132
+
133
+ ```python
134
+ print(InnerNoneType(123) == InnerNoneType(123))
135
+ #> True
136
+ print(InnerNoneType('key') == InnerNoneType('key'))
137
+ #> True
138
+
139
+ print(InnerNoneType(123) == InnerNoneType(1234))
140
+ #> False
141
+ print(InnerNoneType('key') == InnerNoneType('another key'))
142
+ #> False
143
+ print(InnerNoneType(123) == InnerNoneType())
144
+ #> False
145
+ print(InnerNoneType(123) == 123)
146
+ #> False
147
+ ```
148
+
149
+ > 💡 Any `InnerNoneType` objects can be used as keys in dictionaries.
150
+
151
+ ## Type hinting
152
+
153
+ > When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
154
+
155
+ > *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
156
+
157
+ `None` is a special value for which Python type checkers make an exception, allowing it to be used as an annotation of its own type. Unfortunately, this behavior cannot be reproduced without changing the internal implementation of existing type checkers, which I would not expect until the [PEP](https://peps.python.org/pep-0661/) is adopted.
158
+
159
+ Therefore, it is suggested to use class `InnerNoneType` as a type annotation:
160
+
161
+ ```python
162
+ def function(default: int | InnerNoneType):
163
+ ...
164
+ ```
denial-0.0.3/README.md ADDED
@@ -0,0 +1,134 @@
1
+ <details>
2
+ <summary>ⓘ</summary>
3
+
4
+ [![Downloads](https://static.pepy.tech/badge/denial/month)](https://pepy.tech/project/denial)
5
+ [![Downloads](https://static.pepy.tech/badge/denial)](https://pepy.tech/project/denial)
6
+ [![Coverage Status](https://coveralls.io/repos/github/pomponchik/denial/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/denial?branch=main)
7
+ [![Lines of code](https://sloc.xyz/github/pomponchik/denial/?category=code)](https://github.com/boyter/scc/)
8
+ [![Hits-of-Code](https://hitsofcode.com/github/pomponchik/denial?branch=main&label=Hits-of-Code&exclude=docs/)](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
9
+ [![Test-Package](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
10
+ [![Python versions](https://img.shields.io/pypi/pyversions/denial.svg)](https://pypi.python.org/pypi/denial)
11
+ [![PyPI version](https://badge.fury.io/py/denial.svg)](https://badge.fury.io/py/denial)
12
+ [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
13
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
14
+ [![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pomponchik/denial)
15
+
16
+ </details>
17
+
18
+
19
+ ![logo](https://raw.githubusercontent.com/pomponchik/denial/develop/docs/assets/logo_1.svg)
20
+
21
+
22
+ The [`None`](https://docs.python.org/3/library/constants.html#None) constant built into Python is convenient for client code, but it is often insufficient when creating libraries. The fact is that this makes it impossible to [distinguish situations](https://colinmcginn.net/truth-value-gaps-and-meaning/) where a value is *undefined* from situations where it is *defined as undefined*. Does that sound too abstract?
23
+
24
+ In fact, the problem of this distinction is found everywhere in library development. `Sentinel objects` are used to resolve it, and [many modules](https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/) from the standard library define their own. For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value.
25
+
26
+ However, we can't all use sentinel objects from some built-in module if we don't need the functionality of that module. Until the [PEP](https://peps.python.org/pep-0661/) has been adopted on this topic, it is better to use a special package containing only this primitive. Such a package is `denial`. It defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations. In addition, this library offers a special class that allows you to create your own sentinels.
27
+
28
+
29
+ ## Table of contents
30
+
31
+ - [**Installation**](#installation)
32
+ - [**The second None**](#the-second-none)
33
+ - [**Your own None objects**](#your-own-none-objects)
34
+ - [**Type hinting**](#type-hinting)
35
+
36
+
37
+ ## Installation
38
+
39
+ Install it:
40
+
41
+ ```bash
42
+ pip install denial
43
+ ```
44
+
45
+ You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld).
46
+
47
+
48
+ ## The second `None`
49
+
50
+ This library defines an object that is proposed to be used in almost the same way as a regular `None`. This is how it is imported:
51
+
52
+ ```python
53
+ from denial import InnerNone
54
+ ```
55
+
56
+ This object is equal only to itself:
57
+
58
+ ```python
59
+ print(InnerNone == InnerNone)
60
+ #> True
61
+ print(InnerNone == False)
62
+ #> False
63
+ ```
64
+
65
+ This object is also an instance of `InnerNoneType` class (an analog of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), however, is not inherited from this), which makes it possible to check through [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance):
66
+
67
+ ```python
68
+ from denial import InnerNoneType
69
+
70
+ print(isinstance(InnerNone, InnerNoneType))
71
+ #> True
72
+ ```
73
+
74
+ It is recommended to use the `InnerNone` object inside libraries where a value close to None is required, but meaning a situation where the value is not really set, rather than set as `None`. This object should be completely isolated from the user code space. None of the public methods of your library should return this object.
75
+
76
+
77
+ ## Your own `None` objects
78
+
79
+ If `None` and [`InnerNone`](#the-second-none) are not enough for you, you can create your own similar objects by instantiating `InnerNoneType`:
80
+
81
+ ```python
82
+ sentinel = InnerNoneType()
83
+ ```
84
+
85
+ This object will also be equal only to itself:
86
+
87
+ ```python
88
+ print(sentinel == sentinel)
89
+ #> True
90
+
91
+ print(sentinel == InnerNoneType()) # Comparison with another object of the same type
92
+ #> False
93
+ print(sentinel == InnerNone) # Also comparison with another object of the same type
94
+ #> False
95
+ print(sentinel == None) # Comparison with None
96
+ #> False
97
+ print(sentinel == 123) # Comparison with an arbitrary object
98
+ #> False
99
+ ```
100
+
101
+ You can also pass an integer or a string to the class constructor. An `InnerNoneType` object is equal to another such object with the same argument:
102
+
103
+ ```python
104
+ print(InnerNoneType(123) == InnerNoneType(123))
105
+ #> True
106
+ print(InnerNoneType('key') == InnerNoneType('key'))
107
+ #> True
108
+
109
+ print(InnerNoneType(123) == InnerNoneType(1234))
110
+ #> False
111
+ print(InnerNoneType('key') == InnerNoneType('another key'))
112
+ #> False
113
+ print(InnerNoneType(123) == InnerNoneType())
114
+ #> False
115
+ print(InnerNoneType(123) == 123)
116
+ #> False
117
+ ```
118
+
119
+ > 💡 Any `InnerNoneType` objects can be used as keys in dictionaries.
120
+
121
+ ## Type hinting
122
+
123
+ > When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
124
+
125
+ > *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
126
+
127
+ `None` is a special value for which Python type checkers make an exception, allowing it to be used as an annotation of its own type. Unfortunately, this behavior cannot be reproduced without changing the internal implementation of existing type checkers, which I would not expect until the [PEP](https://peps.python.org/pep-0661/) is adopted.
128
+
129
+ Therefore, it is suggested to use class `InnerNoneType` as a type annotation:
130
+
131
+ ```python
132
+ def function(default: int | InnerNoneType):
133
+ ...
134
+ ```
@@ -0,0 +1,31 @@
1
+ from itertools import count
2
+ from typing import Any, Optional, Union
3
+
4
+ from printo import descript_data_object
5
+
6
+
7
+ class InnerNoneType:
8
+ id: Optional[Union[int, str]] # pragma: no cover
9
+ counter = count()
10
+
11
+ def __init__(self, id: Optional[Union[int, str]] = None) -> None: # noqa: A002
12
+ if id is None:
13
+ self.id = next(self.counter)
14
+ else:
15
+ self.id = id
16
+
17
+ def __eq__(self, other: Any) -> bool:
18
+ if not isinstance(other, type(self)):
19
+ return False
20
+ return self.id == other.id
21
+
22
+ def __hash__(self) -> int:
23
+ return hash(self.id)
24
+
25
+ def __repr__(self) -> str:
26
+ if not self.id:
27
+ return 'InnerNone'
28
+ return descript_data_object(type(self).__name__, (self.id,), {})
29
+
30
+
31
+ InnerNone = InnerNoneType()
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.1
2
+ Name: denial
3
+ Version: 0.0.3
4
+ Summary: Is one None not enough for you? There's more
5
+ Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
6
+ Project-URL: Source, https://github.com/pomponchik/denial
7
+ Project-URL: Tracker, https://github.com/pomponchik/denial/issues
8
+ Keywords: None,sentinel
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Operating System :: MacOS :: MacOS X
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: POSIX
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: Free Threading
23
+ Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
24
+ Classifier: License :: OSI Approved :: MIT License
25
+ Classifier: Intended Audience :: Developers
26
+ Classifier: Topic :: Software Development :: Libraries
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+
31
+ <details>
32
+ <summary>ⓘ</summary>
33
+
34
+ [![Downloads](https://static.pepy.tech/badge/denial/month)](https://pepy.tech/project/denial)
35
+ [![Downloads](https://static.pepy.tech/badge/denial)](https://pepy.tech/project/denial)
36
+ [![Coverage Status](https://coveralls.io/repos/github/pomponchik/denial/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/denial?branch=main)
37
+ [![Lines of code](https://sloc.xyz/github/pomponchik/denial/?category=code)](https://github.com/boyter/scc/)
38
+ [![Hits-of-Code](https://hitsofcode.com/github/pomponchik/denial?branch=main&label=Hits-of-Code&exclude=docs/)](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
39
+ [![Test-Package](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
40
+ [![Python versions](https://img.shields.io/pypi/pyversions/denial.svg)](https://pypi.python.org/pypi/denial)
41
+ [![PyPI version](https://badge.fury.io/py/denial.svg)](https://badge.fury.io/py/denial)
42
+ [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
43
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
44
+ [![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pomponchik/denial)
45
+
46
+ </details>
47
+
48
+
49
+ ![logo](https://raw.githubusercontent.com/pomponchik/denial/develop/docs/assets/logo_1.svg)
50
+
51
+
52
+ The [`None`](https://docs.python.org/3/library/constants.html#None) constant built into Python is convenient for client code, but it is often insufficient when creating libraries. The fact is that this makes it impossible to [distinguish situations](https://colinmcginn.net/truth-value-gaps-and-meaning/) where a value is *undefined* from situations where it is *defined as undefined*. Does that sound too abstract?
53
+
54
+ In fact, the problem of this distinction is found everywhere in library development. `Sentinel objects` are used to resolve it, and [many modules](https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/) from the standard library define their own. For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value.
55
+
56
+ However, we can't all use sentinel objects from some built-in module if we don't need the functionality of that module. Until the [PEP](https://peps.python.org/pep-0661/) has been adopted on this topic, it is better to use a special package containing only this primitive. Such a package is `denial`. It defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations. In addition, this library offers a special class that allows you to create your own sentinels.
57
+
58
+
59
+ ## Table of contents
60
+
61
+ - [**Installation**](#installation)
62
+ - [**The second None**](#the-second-none)
63
+ - [**Your own None objects**](#your-own-none-objects)
64
+ - [**Type hinting**](#type-hinting)
65
+
66
+
67
+ ## Installation
68
+
69
+ Install it:
70
+
71
+ ```bash
72
+ pip install denial
73
+ ```
74
+
75
+ You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld).
76
+
77
+
78
+ ## The second `None`
79
+
80
+ This library defines an object that is proposed to be used in almost the same way as a regular `None`. This is how it is imported:
81
+
82
+ ```python
83
+ from denial import InnerNone
84
+ ```
85
+
86
+ This object is equal only to itself:
87
+
88
+ ```python
89
+ print(InnerNone == InnerNone)
90
+ #> True
91
+ print(InnerNone == False)
92
+ #> False
93
+ ```
94
+
95
+ This object is also an instance of `InnerNoneType` class (an analog of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), however, is not inherited from this), which makes it possible to check through [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance):
96
+
97
+ ```python
98
+ from denial import InnerNoneType
99
+
100
+ print(isinstance(InnerNone, InnerNoneType))
101
+ #> True
102
+ ```
103
+
104
+ It is recommended to use the `InnerNone` object inside libraries where a value close to None is required, but meaning a situation where the value is not really set, rather than set as `None`. This object should be completely isolated from the user code space. None of the public methods of your library should return this object.
105
+
106
+
107
+ ## Your own `None` objects
108
+
109
+ If `None` and [`InnerNone`](#the-second-none) are not enough for you, you can create your own similar objects by instantiating `InnerNoneType`:
110
+
111
+ ```python
112
+ sentinel = InnerNoneType()
113
+ ```
114
+
115
+ This object will also be equal only to itself:
116
+
117
+ ```python
118
+ print(sentinel == sentinel)
119
+ #> True
120
+
121
+ print(sentinel == InnerNoneType()) # Comparison with another object of the same type
122
+ #> False
123
+ print(sentinel == InnerNone) # Also comparison with another object of the same type
124
+ #> False
125
+ print(sentinel == None) # Comparison with None
126
+ #> False
127
+ print(sentinel == 123) # Comparison with an arbitrary object
128
+ #> False
129
+ ```
130
+
131
+ You can also pass an integer or a string to the class constructor. An `InnerNoneType` object is equal to another such object with the same argument:
132
+
133
+ ```python
134
+ print(InnerNoneType(123) == InnerNoneType(123))
135
+ #> True
136
+ print(InnerNoneType('key') == InnerNoneType('key'))
137
+ #> True
138
+
139
+ print(InnerNoneType(123) == InnerNoneType(1234))
140
+ #> False
141
+ print(InnerNoneType('key') == InnerNoneType('another key'))
142
+ #> False
143
+ print(InnerNoneType(123) == InnerNoneType())
144
+ #> False
145
+ print(InnerNoneType(123) == 123)
146
+ #> False
147
+ ```
148
+
149
+ > 💡 Any `InnerNoneType` objects can be used as keys in dictionaries.
150
+
151
+ ## Type hinting
152
+
153
+ > When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
154
+
155
+ > *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
156
+
157
+ `None` is a special value for which Python type checkers make an exception, allowing it to be used as an annotation of its own type. Unfortunately, this behavior cannot be reproduced without changing the internal implementation of existing type checkers, which I would not expect until the [PEP](https://peps.python.org/pep-0661/) is adopted.
158
+
159
+ Therefore, it is suggested to use class `InnerNoneType` as a type annotation:
160
+
161
+ ```python
162
+ def function(default: int | InnerNoneType):
163
+ ...
164
+ ```
@@ -7,5 +7,6 @@ denial/py.typed
7
7
  denial.egg-info/PKG-INFO
8
8
  denial.egg-info/SOURCES.txt
9
9
  denial.egg-info/dependency_links.txt
10
+ denial.egg-info/requires.txt
10
11
  denial.egg-info/top_level.txt
11
12
  tests/test_inner.py
@@ -0,0 +1 @@
1
+ printo==0.0.8
@@ -4,11 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "denial"
7
- version = "0.0.2"
7
+ version = "0.0.3"
8
8
  authors = [{ name = "Evgeniy Blinov", email = "zheni-b@yandex.ru" }]
9
9
  description = "Is one None not enough for you? There's more"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.8"
12
+ dependencies = [
13
+ 'printo==0.0.8',
14
+ ]
15
+
12
16
  classifiers = [
13
17
  "Operating System :: OS Independent",
14
18
  'Operating System :: MacOS :: MacOS X',
@@ -0,0 +1,99 @@
1
+ from threading import Thread
2
+
3
+ from denial import InnerNone, InnerNoneType
4
+
5
+
6
+ def test_inner_none_is_inner_none():
7
+ assert InnerNone is InnerNone # noqa: PLR0124
8
+
9
+
10
+ def test_inner_none_is_instance_of_inner_none_type():
11
+ assert isinstance(InnerNone, InnerNoneType)
12
+
13
+
14
+ def test_str_inner_none():
15
+ assert str(InnerNone) == 'InnerNone'
16
+
17
+
18
+ def test_repr_inner_none():
19
+ assert repr(InnerNone) == 'InnerNone'
20
+
21
+
22
+ def test_new_instance_has_id_more_0():
23
+ instance_1 = InnerNoneType()
24
+ instance_2 = InnerNoneType()
25
+
26
+ assert isinstance(instance_1.id, int)
27
+ assert isinstance(instance_2.id, int)
28
+
29
+ assert InnerNone.id == 0
30
+ assert instance_1.id > 0
31
+ assert instance_2.id > 0
32
+ assert instance_2.id == instance_1.id + 1
33
+
34
+
35
+ def test_new_instance_repr():
36
+ new_instance = InnerNoneType()
37
+ assert repr(new_instance) == f'InnerNoneType({new_instance.id})'
38
+ assert repr(InnerNoneType('kek')) == "InnerNoneType('kek')"
39
+ assert repr(InnerNoneType(123)) == "InnerNoneType(123)"
40
+
41
+
42
+ def test_eq():
43
+ new_instance = InnerNoneType()
44
+
45
+ assert InnerNone == InnerNone # noqa: PLR0124
46
+ assert InnerNone != new_instance
47
+ assert InnerNone != InnerNoneType('kek')
48
+ assert InnerNone != InnerNoneType(123)
49
+
50
+ assert new_instance == new_instance # noqa: PLR0124
51
+ assert new_instance != InnerNoneType()
52
+ assert InnerNoneType() != InnerNoneType()
53
+
54
+ assert InnerNoneType(123) == InnerNoneType(123)
55
+ assert InnerNoneType('kek') == InnerNoneType('kek')
56
+
57
+ assert InnerNoneType(123) != InnerNoneType(124)
58
+ assert InnerNoneType('kek') != InnerNoneType(123)
59
+ assert InnerNoneType('kek') != InnerNoneType('lol')
60
+
61
+ assert InnerNone != None # noqa: E711
62
+ assert InnerNoneType() != None # noqa: E711
63
+ assert InnerNoneType(123) != None # noqa: E711
64
+
65
+ assert InnerNoneType(123) != 123
66
+ assert InnerNoneType('kek') != 'kek'
67
+
68
+
69
+ def test_hashing_and_use_as_key_in_dict():
70
+ assert hash(InnerNone) == hash(InnerNone.id)
71
+ assert hash(InnerNoneType(123)) == hash(123)
72
+ assert hash(InnerNoneType('123')) == hash('123')
73
+
74
+ new_instance = InnerNoneType()
75
+ assert hash(new_instance) == hash(new_instance.id)
76
+
77
+ dict_with_it = {new_instance: 'kek'}
78
+ assert dict_with_it[new_instance] == 'kek'
79
+
80
+
81
+ def test_thread_safety():
82
+ number_of_iterations = 10_000
83
+ number_of_threads = 10
84
+
85
+ nones = []
86
+
87
+ def go_increment():
88
+ for _ in range(number_of_iterations):
89
+ nones.append(InnerNoneType())
90
+
91
+ threads = [Thread(target=go_increment()) for _ in range(number_of_threads)]
92
+
93
+ for thread in threads:
94
+ thread.start()
95
+
96
+ for thread in threads:
97
+ thread.join()
98
+
99
+ assert len(set(x.id for x in nones)) == number_of_iterations * number_of_threads
denial-0.0.2/PKG-INFO DELETED
@@ -1,76 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: denial
3
- Version: 0.0.2
4
- Summary: Is one None not enough for you? There's more
5
- Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
6
- Project-URL: Source, https://github.com/pomponchik/denial
7
- Project-URL: Tracker, https://github.com/pomponchik/denial/issues
8
- Keywords: None,sentinel
9
- Classifier: Operating System :: OS Independent
10
- Classifier: Operating System :: MacOS :: MacOS X
11
- Classifier: Operating System :: Microsoft :: Windows
12
- Classifier: Operating System :: POSIX
13
- Classifier: Operating System :: POSIX :: Linux
14
- Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.8
16
- Classifier: Programming Language :: Python :: 3.9
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
- Classifier: Programming Language :: Python :: 3.12
20
- Classifier: Programming Language :: Python :: 3.13
21
- Classifier: Programming Language :: Python :: 3.14
22
- Classifier: Programming Language :: Python :: Free Threading
23
- Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
24
- Classifier: License :: OSI Approved :: MIT License
25
- Classifier: Intended Audience :: Developers
26
- Classifier: Topic :: Software Development :: Libraries
27
- Requires-Python: >=3.8
28
- Description-Content-Type: text/markdown
29
- License-File: LICENSE
30
-
31
- # denial
32
-
33
- [![Downloads](https://static.pepy.tech/badge/denial/month)](https://pepy.tech/project/denial)
34
- [![Downloads](https://static.pepy.tech/badge/denial)](https://pepy.tech/project/denial)
35
- [![Coverage Status](https://coveralls.io/repos/github/pomponchik/denial/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/denial?branch=main)
36
- [![Lines of code](https://sloc.xyz/github/pomponchik/denial/?category=code)](https://github.com/boyter/scc/)
37
- [![Hits-of-Code](https://hitsofcode.com/github/pomponchik/denial?branch=main&label=Hits-of-Code&exclude=docs/)](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
38
- [![Test-Package](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
39
- [![Python versions](https://img.shields.io/pypi/pyversions/denial.svg)](https://pypi.python.org/pypi/denial)
40
- [![PyPI version](https://badge.fury.io/py/denial.svg)](https://badge.fury.io/py/denial)
41
- [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
42
- [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
43
- [![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pomponchik/denial)
44
-
45
-
46
- There is a small but annoying misunderstanding in the design of Python as a language. The language defines the constant `None`, which designates a special object that is used as a "stub" when it is not possible to use the "real" value. But sometimes, when implementing libraries, it is not possible to distinguish `None`, passed by the user as the default value, from `None`, which means that the value is *really undefined*. In some rare cases, this distinction is important.
47
-
48
- For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value. However, the use of `MISSING` is tied to the use of this library, and sometimes this constant may be needed for completely different purposes.
49
-
50
- This library defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations.
51
-
52
- Well, how to use it?
53
-
54
- Let's start with the installation:
55
-
56
- ```bash
57
- pip install denial
58
- ```
59
-
60
- This is how this additional version of `None` and its class are imported (can be used for type hints or checks via isinstance):
61
-
62
- ```python
63
- from denial import InnerNone, InnerNoneType
64
- ```
65
-
66
- `InnerNone` is used the same way as `None`, with a couple of additional caveats:
67
-
68
- 1. `InnerNone` is not an instance of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), it has its own parent class.
69
-
70
- 2. `InnerNone` cannot be used as your own type hint. What am I talking about? Let's look at the documentation:
71
-
72
- > When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
73
-
74
- > *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
75
-
76
- In most type checkers, this is implemented using a special "crutch", an exception in the code that cannot be repeated for any other value. Therefore, use `InnerNoneType` as a type hint.
denial-0.0.2/README.md DELETED
@@ -1,46 +0,0 @@
1
- # denial
2
-
3
- [![Downloads](https://static.pepy.tech/badge/denial/month)](https://pepy.tech/project/denial)
4
- [![Downloads](https://static.pepy.tech/badge/denial)](https://pepy.tech/project/denial)
5
- [![Coverage Status](https://coveralls.io/repos/github/pomponchik/denial/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/denial?branch=main)
6
- [![Lines of code](https://sloc.xyz/github/pomponchik/denial/?category=code)](https://github.com/boyter/scc/)
7
- [![Hits-of-Code](https://hitsofcode.com/github/pomponchik/denial?branch=main&label=Hits-of-Code&exclude=docs/)](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
8
- [![Test-Package](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
9
- [![Python versions](https://img.shields.io/pypi/pyversions/denial.svg)](https://pypi.python.org/pypi/denial)
10
- [![PyPI version](https://badge.fury.io/py/denial.svg)](https://badge.fury.io/py/denial)
11
- [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
12
- [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
13
- [![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pomponchik/denial)
14
-
15
-
16
- There is a small but annoying misunderstanding in the design of Python as a language. The language defines the constant `None`, which designates a special object that is used as a "stub" when it is not possible to use the "real" value. But sometimes, when implementing libraries, it is not possible to distinguish `None`, passed by the user as the default value, from `None`, which means that the value is *really undefined*. In some rare cases, this distinction is important.
17
-
18
- For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value. However, the use of `MISSING` is tied to the use of this library, and sometimes this constant may be needed for completely different purposes.
19
-
20
- This library defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations.
21
-
22
- Well, how to use it?
23
-
24
- Let's start with the installation:
25
-
26
- ```bash
27
- pip install denial
28
- ```
29
-
30
- This is how this additional version of `None` and its class are imported (can be used for type hints or checks via isinstance):
31
-
32
- ```python
33
- from denial import InnerNone, InnerNoneType
34
- ```
35
-
36
- `InnerNone` is used the same way as `None`, with a couple of additional caveats:
37
-
38
- 1. `InnerNone` is not an instance of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), it has its own parent class.
39
-
40
- 2. `InnerNone` cannot be used as your own type hint. What am I talking about? Let's look at the documentation:
41
-
42
- > When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
43
-
44
- > *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
45
-
46
- In most type checkers, this is implemented using a special "crutch", an exception in the code that cannot be repeated for any other value. Therefore, use `InnerNoneType` as a type hint.
@@ -1,5 +0,0 @@
1
- class InnerNoneType:
2
- def __repr__(self) -> str:
3
- return 'InnerNone'
4
-
5
- InnerNone = InnerNoneType()
@@ -1,76 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: denial
3
- Version: 0.0.2
4
- Summary: Is one None not enough for you? There's more
5
- Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
6
- Project-URL: Source, https://github.com/pomponchik/denial
7
- Project-URL: Tracker, https://github.com/pomponchik/denial/issues
8
- Keywords: None,sentinel
9
- Classifier: Operating System :: OS Independent
10
- Classifier: Operating System :: MacOS :: MacOS X
11
- Classifier: Operating System :: Microsoft :: Windows
12
- Classifier: Operating System :: POSIX
13
- Classifier: Operating System :: POSIX :: Linux
14
- Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.8
16
- Classifier: Programming Language :: Python :: 3.9
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
- Classifier: Programming Language :: Python :: 3.12
20
- Classifier: Programming Language :: Python :: 3.13
21
- Classifier: Programming Language :: Python :: 3.14
22
- Classifier: Programming Language :: Python :: Free Threading
23
- Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
24
- Classifier: License :: OSI Approved :: MIT License
25
- Classifier: Intended Audience :: Developers
26
- Classifier: Topic :: Software Development :: Libraries
27
- Requires-Python: >=3.8
28
- Description-Content-Type: text/markdown
29
- License-File: LICENSE
30
-
31
- # denial
32
-
33
- [![Downloads](https://static.pepy.tech/badge/denial/month)](https://pepy.tech/project/denial)
34
- [![Downloads](https://static.pepy.tech/badge/denial)](https://pepy.tech/project/denial)
35
- [![Coverage Status](https://coveralls.io/repos/github/pomponchik/denial/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/denial?branch=main)
36
- [![Lines of code](https://sloc.xyz/github/pomponchik/denial/?category=code)](https://github.com/boyter/scc/)
37
- [![Hits-of-Code](https://hitsofcode.com/github/pomponchik/denial?branch=main&label=Hits-of-Code&exclude=docs/)](https://hitsofcode.com/github/pomponchik/denial/view?branch=main)
38
- [![Test-Package](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/denial/actions/workflows/tests_and_coverage.yml)
39
- [![Python versions](https://img.shields.io/pypi/pyversions/denial.svg)](https://pypi.python.org/pypi/denial)
40
- [![PyPI version](https://badge.fury.io/py/denial.svg)](https://badge.fury.io/py/denial)
41
- [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
42
- [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
43
- [![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pomponchik/denial)
44
-
45
-
46
- There is a small but annoying misunderstanding in the design of Python as a language. The language defines the constant `None`, which designates a special object that is used as a "stub" when it is not possible to use the "real" value. But sometimes, when implementing libraries, it is not possible to distinguish `None`, passed by the user as the default value, from `None`, which means that the value is *really undefined*. In some rare cases, this distinction is important.
47
-
48
- For example, the [dataclasses](https://docs.python.org/3/library/dataclasses.html) library defines a special [MISSING](https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING) constant for such cases. This is used to separate the cases when the user has not set a default value from the case when he has set `None` as the default value. However, the use of `MISSING` is tied to the use of this library, and sometimes this constant may be needed for completely different purposes.
49
-
50
- This library defines just such an object: `None` for situations where you need to distinguish `None` as a value from the user, and None as a designation that something is really undefined. This value should not fall "outside", into the user's space, it should remain only inside the libraries implementations.
51
-
52
- Well, how to use it?
53
-
54
- Let's start with the installation:
55
-
56
- ```bash
57
- pip install denial
58
- ```
59
-
60
- This is how this additional version of `None` and its class are imported (can be used for type hints or checks via isinstance):
61
-
62
- ```python
63
- from denial import InnerNone, InnerNoneType
64
- ```
65
-
66
- `InnerNone` is used the same way as `None`, with a couple of additional caveats:
67
-
68
- 1. `InnerNone` is not an instance of [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType), it has its own parent class.
69
-
70
- 2. `InnerNone` cannot be used as your own type hint. What am I talking about? Let's look at the documentation:
71
-
72
- > When used in a type hint, the expression `None` is considered equivalent to `type(None)`.
73
-
74
- > *[Official typing documentation](https://typing.python.org/en/latest/spec/special-types.html#none)*
75
-
76
- In most type checkers, this is implemented using a special "crutch", an exception in the code that cannot be repeated for any other value. Therefore, use `InnerNoneType` as a type hint.
@@ -1,17 +0,0 @@
1
- from denial import InnerNone, InnerNoneType
2
-
3
-
4
- def test_inner_none_is_inner_none():
5
- assert InnerNone is InnerNone # noqa: PLR0124
6
-
7
-
8
- def test_inner_none_is_instance_of_inner_none_type():
9
- assert isinstance(InnerNone, InnerNoneType)
10
-
11
-
12
- def test_str():
13
- assert str(InnerNone) == 'InnerNone'
14
-
15
-
16
- def test_repr():
17
- assert repr(InnerNone) == 'InnerNone'
File without changes
File without changes
File without changes
File without changes