unitx 0.6.3__tar.gz → 0.7.1__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 (52) hide show
  1. {unitx-0.6.3 → unitx-0.7.1}/CHANGELOG.md +88 -0
  2. {unitx-0.6.3 → unitx-0.7.1}/PKG-INFO +115 -18
  3. {unitx-0.6.3 → unitx-0.7.1}/README.md +114 -17
  4. {unitx-0.6.3 → unitx-0.7.1}/pyproject.toml +1 -1
  5. {unitx-0.6.3 → unitx-0.7.1}/pyrightconfig.json +3 -0
  6. unitx-0.7.1/src/unitx/_constnamespace.py +33 -0
  7. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/_unicode.py +5 -4
  8. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/quantity.py +7 -2
  9. unitx-0.7.1/src/unitx/si/bases.py +93 -0
  10. unitx-0.7.1/src/unitx/si/constants.py +178 -0
  11. unitx-0.7.1/src/unitx/si/units.py +233 -0
  12. unitx-0.7.1/src/unitx/singleunit.py +160 -0
  13. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/unit.py +24 -14
  14. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/unitparser.py +47 -16
  15. unitx-0.7.1/src/unitx/unitregistry.py +305 -0
  16. unitx-0.6.3/tests/test_unit_data.py → unitx-0.7.1/tests/test_builtin_definitions.py +21 -21
  17. {unitx-0.6.3 → unitx-0.7.1}/tests/test_quantity.py +2 -1
  18. {unitx-0.6.3 → unitx-0.7.1}/tests/test_si.py +121 -1
  19. {unitx-0.6.3 → unitx-0.7.1}/tests/test_singleunit.py +39 -15
  20. {unitx-0.6.3 → unitx-0.7.1}/tests/test_unit_definition.py +51 -44
  21. {unitx-0.6.3 → unitx-0.7.1}/tests/test_unitparser.py +78 -0
  22. unitx-0.7.1/tests/test_unitregistry.py +228 -0
  23. {unitx-0.6.3 → unitx-0.7.1}/tests/typing/check_quantity.py +25 -0
  24. {unitx-0.6.3 → unitx-0.7.1}/tests/typing/check_si.py +13 -1
  25. unitx-0.6.3/src/unitx/_constclass.py +0 -34
  26. unitx-0.6.3/src/unitx/_unit_data.py +0 -193
  27. unitx-0.6.3/src/unitx/si/constants.py +0 -200
  28. unitx-0.6.3/src/unitx/si/units.py +0 -193
  29. unitx-0.6.3/src/unitx/singleunit.py +0 -208
  30. {unitx-0.6.3 → unitx-0.7.1}/.github/workflows/ci.yml +0 -0
  31. {unitx-0.6.3 → unitx-0.7.1}/.github/workflows/release.yml +0 -0
  32. {unitx-0.6.3 → unitx-0.7.1}/.gitignore +0 -0
  33. {unitx-0.6.3 → unitx-0.7.1}/LICENSE +0 -0
  34. {unitx-0.6.3 → unitx-0.7.1}/conftest.py +0 -0
  35. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/__init__.py +0 -0
  36. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/_numerics.py +0 -0
  37. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/dimension.py +0 -0
  38. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/exceptions.py +0 -0
  39. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/factor.py +0 -0
  40. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/monomial.py +0 -0
  41. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/py.typed +0 -0
  42. {unitx-0.6.3 → unitx-0.7.1}/src/unitx/si/__init__.py +0 -0
  43. {unitx-0.6.3 → unitx-0.7.1}/tests/__init__.py +0 -0
  44. {unitx-0.6.3 → unitx-0.7.1}/tests/test_dimension.py +0 -0
  45. {unitx-0.6.3 → unitx-0.7.1}/tests/test_factor.py +0 -0
  46. {unitx-0.6.3 → unitx-0.7.1}/tests/test_import.py +0 -0
  47. {unitx-0.6.3 → unitx-0.7.1}/tests/test_monomial.py +0 -0
  48. {unitx-0.6.3 → unitx-0.7.1}/tests/test_numeric.py +0 -0
  49. {unitx-0.6.3 → unitx-0.7.1}/tests/test_typing.py +0 -0
  50. {unitx-0.6.3 → unitx-0.7.1}/tests/test_unicode.py +0 -0
  51. {unitx-0.6.3 → unitx-0.7.1}/tests/test_unit.py +0 -0
  52. {unitx-0.6.3 → unitx-0.7.1}/tests/test_warnings.py +0 -0
@@ -1,5 +1,93 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1] - 2026-10-19
4
+
5
+ ### Added
6
+
7
+ - Accept a single exact spelling string for `prefixable`, equivalent to a
8
+ one-element tuple, with the same spelling validation before registration.
9
+ - Expand the `si` namespace's prefixed shortcuts and common units, including
10
+ percent, permille, permyriad, rotational frequency (`rpm`), speed of light
11
+ (`c`), gram-force, volt-ampere, ampere-hour, mho, and nit. Enable Torr prefixes.
12
+ - Add `SI.kappa` and the wavelength and frequency Wien displacement constants,
13
+ `SI.b` and `SI.b_`.
14
+
15
+ ### Changed
16
+
17
+ - Use fixed Decimal approximations with about 50 significant digits for pi and
18
+ the positive roots in the Wien displacement laws. Derived constants still use
19
+ the Decimal context active at import.
20
+ - Treat `u` and `Da` as spellings of one formal unit, with prefixes enabled only
21
+ on `Da`. Rebuild stored units when migrating from the separate `u` identity;
22
+ the mass conversion scale is unchanged. Use `kDa` rather than `kilodalton`.
23
+ - Keep ASCII micro spellings such as `um` and `us` convenient to type without
24
+ warnings. The explicit product `u m` retains its separate mass-times-length
25
+ meaning.
26
+
27
+ ## [0.7.0] - 2026-10-19
28
+
29
+ ### Added
30
+
31
+ - Add `[registered name]` references as single factors in unit expressions,
32
+ including products, reciprocals, parentheses, and rational powers. References
33
+ resolve names and name aliases only, with whitespace normalization and no
34
+ expression evaluation or nested quoting.
35
+ - Support standalone hyphenated names and adjacent Unicode integer superscripts
36
+ such as `m²`, `s⁻¹²`, and `[metre]²`.
37
+ - Extend `prefixable: PrefixPolicy = True` on all `.define()` entry points to
38
+ accept a tuple of exact registered symbols, names, or aliases. Only those
39
+ spellings accept prefixes; True enables all spellings and False or () disables
40
+ all spellings. `PrefixPolicy` is `bool | tuple[str, ...]`.
41
+ - Enable prefixes on `arcsec` and `arcsecond` while keeping angle punctuation
42
+ unprefixable. Register unprefixable are (`a`) and hectare (`ha`), and reactive
43
+ power (`var`, name `volt-ampere reactive`). Keep `as` as attosecond and `yr`
44
+ as a 365-day year.
45
+ - Add the Julian year `a_j` (exactly 31,557,600 seconds) and its twelfth,
46
+ `mo`, as a fixed month interval distinct from variable calendar months.
47
+ - Add light year (`ly`), unified atomic mass unit (`u`), and common prefixed
48
+ namespace attributes, including attosecond (`as_`) and petaelectronvolt (`PeV`).
49
+
50
+ ### Changed
51
+
52
+ - Move definitions, spelling resolution, and registration validation into
53
+ `unitx.unitregistry`, shared by SingleUnit and UnitParser. Preserve formal identities,
54
+ exact SI factors, existing `.define()` defaults, and validation before mutation.
55
+ - Remove the superseded private `_unit_data` module. Name definition records
56
+ `PrefixDefinition` and `UnitDefinition`, with explicit definition indexes and
57
+ alias-to-symbol mappings.
58
+ - Select permitted canonical output spellings after applying binding policies.
59
+ When only a name permits a prefix, expressions use `[name]` so repr and pickle
60
+ preserve the prefixed identity. Expose `PrefixPolicy` for type annotations.
61
+ - Use the finite spelling exceptions `megohm` and `kilohm` in preferred names,
62
+ retaining `megaohm` and `kiloohm` as accepted inputs.
63
+ - Reuse Unicode superscript tables, handle malformed name brackets through
64
+ the common lexical error path, and simplify definition initialization,
65
+ preferred spelling selection, and generated prefix-name validation.
66
+ - Share coherent SI units and the seven defining constants through private
67
+ bases. Derive `ly` from `c` and the Julian year, and `eV` from `e` and `V`,
68
+ preserving the public `si` and `SI` namespaces and constant magnitude types.
69
+
70
+ ### Fixed
71
+
72
+ - Register metre of mercury (`mHg`) as the prefixable unit and construct `mmHg`
73
+ from its milli prefix. Preserve `760 mmHg = 1 atm` and the existing Torr scale,
74
+ keep the definition exact, and reject stacked prefixes such as `kmmHg`.
75
+
76
+ ### Migration
77
+
78
+ - Square brackets and `⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻` are now reserved grammar characters and can
79
+ no longer occur in registered symbols or names. Rename custom spellings that
80
+ previously used them before rebuilding or unpickling those definitions.
81
+ - Long names remain whole-input lookups unless explicitly referenced in brackets.
82
+ `str()` remains readable display and is not a general serialization format;
83
+ use `expression()` for formal unit products and repr for ScaledUnit state.
84
+ - `mmHg` now has the formal identity of milli-prefixed `mHg`; rebuild stored
85
+ units when moving between versions. Its pressure conversion scale is unchanged.
86
+ `kmmHg` and names with repeated prefixes are no longer accepted.
87
+ - Registering `u` makes ASCII micro spellings such as `um` issue the existing
88
+ ambiguity warning. Their meaning is unchanged; use `µm`, `μm`, or `micrometre`
89
+ to avoid the warning. `kt` remains a knot alias and tonne stays unprefixable.
90
+
3
91
  ## [0.6.3] - 2026-10-18
4
92
 
5
93
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: unitx
3
- Version: 0.6.3
3
+ Version: 0.7.1
4
4
  Summary: A modern, intuitive Python library for units, quantities, and dimensional analysis.
5
5
  Project-URL: Homepage, https://github.com/DaitTHU/unitx
6
6
  Project-URL: Issues, https://github.com/DaitTHU/unitx/issues
@@ -22,11 +22,15 @@ Description-Content-Type: text/markdown
22
22
 
23
23
  # unitx
24
24
 
25
- Write Python formulas with ready-to-use units and physical constants. unitx
25
+ [![PyPI version](https://img.shields.io/pypi/v/unitx)](https://pypi.org/project/unitx/)
26
+ [![Python versions](https://img.shields.io/pypi/pyversions/unitx)](https://pypi.org/project/unitx/)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
28
+
29
+ Write Python formulas with ready-to-use units and physical constants. `unitx`
26
30
  handles scalar quantities, unit conversion, and dimensional analysis, with
27
31
  exact rational unit scales and exponents.
28
32
 
29
- unitx is in early development. Public APIs may change during the 0.x series.
33
+ `unitx` is in early development. Public APIs may change during the 0.x series.
30
34
 
31
35
  ## Installation
32
36
 
@@ -101,15 +105,17 @@ astronomical units are included. Units and constants can be combined directly:
101
105
 
102
106
  Both namespaces are read-only and do not need to be instantiated. `si` exposes
103
107
  a selection of convenient attributes; other registered prefix combinations
104
- remain available through `Unit`, for example `Unit('Tm')`. Importing unitx
108
+ remain available through `Unit`, for example `Unit('Tm')`. Importing `unitx`
105
109
  initializes the predefined unit catalogue. Defining a custom unit registers its
106
110
  text spellings without adding an attribute to `si`.
107
111
 
108
112
  SI defining constants retain their exact decimal values. Measured constants
109
113
  are stored nominal values; measurement uncertainties are not propagated.
110
114
  Derived Decimal constants are evaluated in the Decimal context active at
111
- import. Formulas involving pi use `Decimal(str(math.pi))` as an approximation;
112
- additional Decimal precision does not add precision to pi.
115
+ import. Pi and the positive roots used by the Wien displacement constants
116
+ (`SI.b` for wavelength and `SI.b_` for frequency) use fixed Decimal
117
+ approximations with about 50 significant digits. Raising the context precision
118
+ does not add accuracy beyond those stored approximations.
113
119
 
114
120
  ## Constructing units and quantities
115
121
 
@@ -246,17 +252,28 @@ False
246
252
  token: `ms` is a millisecond, while `m s` is metre times second.
247
253
  - Powers can use adjacent numbers, `^`, or `**`. Exponents written as ratios
248
254
  require parentheses, as in `m^(1/2)`; decimal exponents such as `m^0.5` are exact.
255
+ Adjacent integer superscripts are also accepted: `m²`, `s⁻¹²`, and `(m/s)²`.
256
+ Superscript fractions and mixed exponent notations are not supported.
249
257
  - Multiplication and division associate left to right. Use `m/(s A)` to put
250
258
  both `s` and `A` in the denominator; `m/s A` means `(m/s)*A`.
251
259
  - Unit symbols and names are case-sensitive. `u`, `µ`, and `μ` are accepted
252
260
  micro prefixes. Use lowercase `k` for kilo; `K` denotes kelvin.
253
- - A registered multiword name is recognized before implicit multiplication
261
+ - A registered multiword or hyphenated name is recognized before implicit multiplication
254
262
  when it occupies the entire input. Whitespace between its words is treated
255
- as a single space. Inside products, parentheses, reciprocals, and powers,
256
- use its symbol or combine `Unit` objects instead.
263
+ as a single space. Inside expressions, use `[name]` to make a name one factor,
264
+ for example `[astronomical unit] / s` or `[volt-ampere reactive]**2`.
265
+ Symbols and `Unit` object composition remain available.
266
+
267
+ Square brackets reference registered names and name aliases only: `[metre]`
268
+ works, while `[m]` and `[m/s]` do not. The contents are never evaluated as an
269
+ expression. Empty, nested, and unmatched brackets are errors. Put permitted
270
+ name prefixes inside the brackets, as in `[kiloastronomical unit]`; `k[name]`
271
+ does not apply a prefix. Single and double quotes retain their angle-unit
272
+ meaning rather than acting as string delimiters. Bare compound names are
273
+ not guessed from adjacent words, and ordinary parentheses still group products.
257
274
 
258
275
  The unit table contains `s`, `m`, `g`, `A`, `K`, `mol`, and `cd`, plus the
259
- dimensionless identity. Importing unitx also initializes the common units in
276
+ dimensionless identity. Importing `unitx` also initializes the common units in
260
277
  `unitx.si`, including named derived units such as `N`. Mass scales are referenced
261
278
  to kilograms: `g` has factor `1/1000`, and `kg` has factor `1`.
262
279
 
@@ -303,9 +320,10 @@ aliases gives an empty tuple. `symbol_aliases` accepts a string or tuple of
303
320
  strings; it and `prefixable` must be passed by keyword.
304
321
  Symbols and symbol aliases must each be a single printable unit token.
305
322
  Names and name aliases may contain multiple such tokens separated by single
306
- ASCII spaces, without leading or trailing spaces. Decimal digits and parser
307
- operators are not allowed in any token. The primary symbol and `name` are
308
- required and must be nonempty; empty aliases are ignored.
323
+ ASCII spaces or hyphens, without leading or trailing separators. Decimal digits
324
+ and parser operators are not allowed in any token. The primary symbol and `name` are
325
+ required and must be nonempty; empty aliases are ignored. Square brackets and
326
+ the superscript characters `⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻` are reserved for parser syntax.
309
327
 
310
328
  ```pycon
311
329
  >>> span = (2 * si.m).define('span', 'survey span', 'survey distance')
@@ -315,22 +333,101 @@ True
315
333
  True
316
334
  >>> Unit('survey span') / si.s == Unit('span / s')
317
335
  True
336
+ >>> Unit('[survey span] / [second]') == Unit('span / s')
337
+ True
318
338
 
319
339
  ```
320
340
 
321
- Multiword names are recognized only as complete inputs. For example,
341
+ Bare multiword names are recognized only as complete inputs. For example,
322
342
  `Unit('astronomical unit / arcsec')` does not recognize `astronomical unit`
323
- as a factor; use one of the two compound forms above instead.
343
+ as a factor; use `[astronomical unit] / arcsec`, symbols, or Unit objects instead.
344
+
345
+ SI prefixes are enabled by default. `prefixable: PrefixPolicy = True` accepts
346
+ `True` for all spellings, `False` for none, a single spelling string, or a tuple
347
+ of exact symbols, names, and aliases supplied in that definition. A string such
348
+ as `prefixable='Da'` is equivalent to `prefixable=('Da',)`. Only the selected
349
+ spellings accept prefixes; an empty tuple disables prefixes. Unknown spellings
350
+ and non-string entries are errors. For example,
351
+ `prefixable=('arcsec', 'arcsecond')` enables all SI prefixes on those two
352
+ spellings, while other spellings of that unit remain unprefixable.
353
+
354
+ The tuple selects **unit spellings**, not prefix symbols. Prefix names attach
355
+ directly to the first word of a multiword name, as in `kiloastronomical unit`;
356
+ a separate `kilo astronomical unit` is not accepted. `PrefixPolicy` is available
357
+ from `unitx.unitregistry` for annotations and means `bool | str | tuple[str, ...]`.
358
+
359
+ ```pycon
360
+ >>> tick = si.s.define(
361
+ ... 'demoTick',
362
+ ... 'survey interval',
363
+ ... symbol_aliases='demoT',
364
+ ... prefixable=('demoT', 'survey interval'),
365
+ ... )
366
+ >>> Unit('[millisurvey interval]') == Unit('mdemoT')
367
+ True
368
+ >>> Quantity(1000, 'mdemoT').to(tick).value
369
+ Fraction(1, 1)
370
+ >>> Unit('mdemoT').expression()
371
+ 'mdemoT'
372
+
373
+ ```
374
+
375
+ Prefix permissions are checked before alias normalization. Canonical identity
376
+ still uses the original registered unit and canonical prefix, independent of
377
+ the input alias. Preferred spellings use the first permitted symbol or alias;
378
+ if only a name supports a prefix, `SingleUnit.symbol` falls back to that name
379
+ and `Unit.expression()` emits a `[name]` reference. Conversely, `SingleUnit.name`
380
+ falls back to a symbol if no name allows that prefix. Repr and pickle preserve
381
+ the same formal unit, rather than replacing the prefix with an extra scale.
382
+
383
+ `arcsec` and `arcsecond` accept prefixes, for example `marcsec` and
384
+ `milliarcsecond`; `″` and `"` do not. `as` continues to mean attosecond,
385
+ and `mas` is not an arcsecond alias. `a` (are) and `ha` (hectare) are separate
386
+ unprefixable units; `Pa` remains pascal. `yr` remains exactly 365 days.
387
+ `a_j` is the Julian year of exactly 365.25 days (31,557,600 seconds), while
388
+ `mo` is one twelfth of that interval, rather than a variable calendar month.
389
+ The name exceptions `megohm` and `kilohm` are supported alongside the older
390
+ `megaohm` and `kiloohm` spellings; `gigaohm` retains both vowels. No general
391
+ vowel-elision rule is applied.
392
+
393
+ `ly` is defined using the speed of light and the Julian year; `eV` uses the
394
+ elementary charge and volt. Both reuse the defining constants exposed by `SI`.
395
+ `mHg` is the registered metre-of-mercury unit and `mmHg` is its milli-prefixed
396
+ form; `760 * si.mmHg` converts to `si.atm` with value 1 under the library's
397
+ existing pressure convention. Prefixes do not stack: `mmmHg` and `kmmHg` are
398
+ invalid. `kt` retains its meaning as a knot alias; tonne remains unprefixable.
399
+
400
+ `si.mas` and `si.uas` provide milli- and microarcseconds as namespace shortcuts;
401
+ their registered expressions are `marcsec` and `µarcsec`. The strings `mas`
402
+ and `uas` are not registered aliases. `si.Da` and `si.u` now share one formal
403
+ unit identity; only the spelling `Da` accepts prefixes, such as `kDa`.
404
+ `si.mTorr` is available, and Torr accepts SI prefixes. Tonne uses `t` or
405
+ `tonne`; the ambiguous spelling `ton` is not registered.
406
+
407
+ `si.rpm` represents rotational frequency: `60 * si.rpm` converts to `si.Hz`
408
+ with value 1. For angular velocity, multiply that frequency by `2 * PI` and
409
+ convert to `si.rad / si.s`; directly converting frequency to `rad/s` does
410
+ not insert the physical factor of 2π. `si.c` is a speed unit equal to the
411
+ speed of light, while `SI.c` is the corresponding physical quantity.
412
+
413
+ The spelling `u` denotes the unified atomic mass unit when used on its own.
414
+ ASCII micro spellings such as `um` and `us` remain supported without warnings,
415
+ so entering non-ASCII characters is optional. `um`, `µm`, `μm`, and
416
+ `micrometre` all mean micrometres; the explicit product `u m` instead means
417
+ unified atomic mass unit times metre.
324
418
 
325
- SI prefixes are enabled by default; set `prefixable=False` to disable them.
326
- Prefix names attach directly to the first word of a multiword name, as in
327
- `kiloastronomical unit`; a separate `kilo astronomical unit` is not accepted.
328
419
  Registration raises `UnitRegistryError` if a new spelling, including a generated
329
420
  prefixed form, already resolves to a unit or conflicts with another spelling
330
421
  in the same definition. A multiword spelling must not replace an existing
331
422
  implicit product such as `metre second`. Validation finishes before the
332
423
  registry changes.
333
424
 
425
+ The registry and spelling rules live in `unitx.unitregistry`; parsing expressions
426
+ does not change unit definitions. A parser instance caches only a successful
427
+ result; failed parses may be retried after a definition is added. Registration
428
+ preserves existing valid spellings and validates before updating its indexes.
429
+ Concurrent registry writes and isolated registry contexts are not supported.
430
+
334
431
  Quantity definitions require positive finite `int`, `Fraction`, `Decimal`, or
335
432
  `float` magnitudes. Prefer `Fraction` or `Decimal` for exact scales. Floats use
336
433
  Factor's denominator limit and can warn or round to zero and fail, for example
@@ -1,10 +1,14 @@
1
1
  # unitx
2
2
 
3
- Write Python formulas with ready-to-use units and physical constants. unitx
3
+ [![PyPI version](https://img.shields.io/pypi/v/unitx)](https://pypi.org/project/unitx/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/unitx)](https://pypi.org/project/unitx/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ Write Python formulas with ready-to-use units and physical constants. `unitx`
4
8
  handles scalar quantities, unit conversion, and dimensional analysis, with
5
9
  exact rational unit scales and exponents.
6
10
 
7
- unitx is in early development. Public APIs may change during the 0.x series.
11
+ `unitx` is in early development. Public APIs may change during the 0.x series.
8
12
 
9
13
  ## Installation
10
14
 
@@ -79,15 +83,17 @@ astronomical units are included. Units and constants can be combined directly:
79
83
 
80
84
  Both namespaces are read-only and do not need to be instantiated. `si` exposes
81
85
  a selection of convenient attributes; other registered prefix combinations
82
- remain available through `Unit`, for example `Unit('Tm')`. Importing unitx
86
+ remain available through `Unit`, for example `Unit('Tm')`. Importing `unitx`
83
87
  initializes the predefined unit catalogue. Defining a custom unit registers its
84
88
  text spellings without adding an attribute to `si`.
85
89
 
86
90
  SI defining constants retain their exact decimal values. Measured constants
87
91
  are stored nominal values; measurement uncertainties are not propagated.
88
92
  Derived Decimal constants are evaluated in the Decimal context active at
89
- import. Formulas involving pi use `Decimal(str(math.pi))` as an approximation;
90
- additional Decimal precision does not add precision to pi.
93
+ import. Pi and the positive roots used by the Wien displacement constants
94
+ (`SI.b` for wavelength and `SI.b_` for frequency) use fixed Decimal
95
+ approximations with about 50 significant digits. Raising the context precision
96
+ does not add accuracy beyond those stored approximations.
91
97
 
92
98
  ## Constructing units and quantities
93
99
 
@@ -224,17 +230,28 @@ False
224
230
  token: `ms` is a millisecond, while `m s` is metre times second.
225
231
  - Powers can use adjacent numbers, `^`, or `**`. Exponents written as ratios
226
232
  require parentheses, as in `m^(1/2)`; decimal exponents such as `m^0.5` are exact.
233
+ Adjacent integer superscripts are also accepted: `m²`, `s⁻¹²`, and `(m/s)²`.
234
+ Superscript fractions and mixed exponent notations are not supported.
227
235
  - Multiplication and division associate left to right. Use `m/(s A)` to put
228
236
  both `s` and `A` in the denominator; `m/s A` means `(m/s)*A`.
229
237
  - Unit symbols and names are case-sensitive. `u`, `µ`, and `μ` are accepted
230
238
  micro prefixes. Use lowercase `k` for kilo; `K` denotes kelvin.
231
- - A registered multiword name is recognized before implicit multiplication
239
+ - A registered multiword or hyphenated name is recognized before implicit multiplication
232
240
  when it occupies the entire input. Whitespace between its words is treated
233
- as a single space. Inside products, parentheses, reciprocals, and powers,
234
- use its symbol or combine `Unit` objects instead.
241
+ as a single space. Inside expressions, use `[name]` to make a name one factor,
242
+ for example `[astronomical unit] / s` or `[volt-ampere reactive]**2`.
243
+ Symbols and `Unit` object composition remain available.
244
+
245
+ Square brackets reference registered names and name aliases only: `[metre]`
246
+ works, while `[m]` and `[m/s]` do not. The contents are never evaluated as an
247
+ expression. Empty, nested, and unmatched brackets are errors. Put permitted
248
+ name prefixes inside the brackets, as in `[kiloastronomical unit]`; `k[name]`
249
+ does not apply a prefix. Single and double quotes retain their angle-unit
250
+ meaning rather than acting as string delimiters. Bare compound names are
251
+ not guessed from adjacent words, and ordinary parentheses still group products.
235
252
 
236
253
  The unit table contains `s`, `m`, `g`, `A`, `K`, `mol`, and `cd`, plus the
237
- dimensionless identity. Importing unitx also initializes the common units in
254
+ dimensionless identity. Importing `unitx` also initializes the common units in
238
255
  `unitx.si`, including named derived units such as `N`. Mass scales are referenced
239
256
  to kilograms: `g` has factor `1/1000`, and `kg` has factor `1`.
240
257
 
@@ -281,9 +298,10 @@ aliases gives an empty tuple. `symbol_aliases` accepts a string or tuple of
281
298
  strings; it and `prefixable` must be passed by keyword.
282
299
  Symbols and symbol aliases must each be a single printable unit token.
283
300
  Names and name aliases may contain multiple such tokens separated by single
284
- ASCII spaces, without leading or trailing spaces. Decimal digits and parser
285
- operators are not allowed in any token. The primary symbol and `name` are
286
- required and must be nonempty; empty aliases are ignored.
301
+ ASCII spaces or hyphens, without leading or trailing separators. Decimal digits
302
+ and parser operators are not allowed in any token. The primary symbol and `name` are
303
+ required and must be nonempty; empty aliases are ignored. Square brackets and
304
+ the superscript characters `⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻` are reserved for parser syntax.
287
305
 
288
306
  ```pycon
289
307
  >>> span = (2 * si.m).define('span', 'survey span', 'survey distance')
@@ -293,22 +311,101 @@ True
293
311
  True
294
312
  >>> Unit('survey span') / si.s == Unit('span / s')
295
313
  True
314
+ >>> Unit('[survey span] / [second]') == Unit('span / s')
315
+ True
296
316
 
297
317
  ```
298
318
 
299
- Multiword names are recognized only as complete inputs. For example,
319
+ Bare multiword names are recognized only as complete inputs. For example,
300
320
  `Unit('astronomical unit / arcsec')` does not recognize `astronomical unit`
301
- as a factor; use one of the two compound forms above instead.
321
+ as a factor; use `[astronomical unit] / arcsec`, symbols, or Unit objects instead.
322
+
323
+ SI prefixes are enabled by default. `prefixable: PrefixPolicy = True` accepts
324
+ `True` for all spellings, `False` for none, a single spelling string, or a tuple
325
+ of exact symbols, names, and aliases supplied in that definition. A string such
326
+ as `prefixable='Da'` is equivalent to `prefixable=('Da',)`. Only the selected
327
+ spellings accept prefixes; an empty tuple disables prefixes. Unknown spellings
328
+ and non-string entries are errors. For example,
329
+ `prefixable=('arcsec', 'arcsecond')` enables all SI prefixes on those two
330
+ spellings, while other spellings of that unit remain unprefixable.
331
+
332
+ The tuple selects **unit spellings**, not prefix symbols. Prefix names attach
333
+ directly to the first word of a multiword name, as in `kiloastronomical unit`;
334
+ a separate `kilo astronomical unit` is not accepted. `PrefixPolicy` is available
335
+ from `unitx.unitregistry` for annotations and means `bool | str | tuple[str, ...]`.
336
+
337
+ ```pycon
338
+ >>> tick = si.s.define(
339
+ ... 'demoTick',
340
+ ... 'survey interval',
341
+ ... symbol_aliases='demoT',
342
+ ... prefixable=('demoT', 'survey interval'),
343
+ ... )
344
+ >>> Unit('[millisurvey interval]') == Unit('mdemoT')
345
+ True
346
+ >>> Quantity(1000, 'mdemoT').to(tick).value
347
+ Fraction(1, 1)
348
+ >>> Unit('mdemoT').expression()
349
+ 'mdemoT'
350
+
351
+ ```
352
+
353
+ Prefix permissions are checked before alias normalization. Canonical identity
354
+ still uses the original registered unit and canonical prefix, independent of
355
+ the input alias. Preferred spellings use the first permitted symbol or alias;
356
+ if only a name supports a prefix, `SingleUnit.symbol` falls back to that name
357
+ and `Unit.expression()` emits a `[name]` reference. Conversely, `SingleUnit.name`
358
+ falls back to a symbol if no name allows that prefix. Repr and pickle preserve
359
+ the same formal unit, rather than replacing the prefix with an extra scale.
360
+
361
+ `arcsec` and `arcsecond` accept prefixes, for example `marcsec` and
362
+ `milliarcsecond`; `″` and `"` do not. `as` continues to mean attosecond,
363
+ and `mas` is not an arcsecond alias. `a` (are) and `ha` (hectare) are separate
364
+ unprefixable units; `Pa` remains pascal. `yr` remains exactly 365 days.
365
+ `a_j` is the Julian year of exactly 365.25 days (31,557,600 seconds), while
366
+ `mo` is one twelfth of that interval, rather than a variable calendar month.
367
+ The name exceptions `megohm` and `kilohm` are supported alongside the older
368
+ `megaohm` and `kiloohm` spellings; `gigaohm` retains both vowels. No general
369
+ vowel-elision rule is applied.
370
+
371
+ `ly` is defined using the speed of light and the Julian year; `eV` uses the
372
+ elementary charge and volt. Both reuse the defining constants exposed by `SI`.
373
+ `mHg` is the registered metre-of-mercury unit and `mmHg` is its milli-prefixed
374
+ form; `760 * si.mmHg` converts to `si.atm` with value 1 under the library's
375
+ existing pressure convention. Prefixes do not stack: `mmmHg` and `kmmHg` are
376
+ invalid. `kt` retains its meaning as a knot alias; tonne remains unprefixable.
377
+
378
+ `si.mas` and `si.uas` provide milli- and microarcseconds as namespace shortcuts;
379
+ their registered expressions are `marcsec` and `µarcsec`. The strings `mas`
380
+ and `uas` are not registered aliases. `si.Da` and `si.u` now share one formal
381
+ unit identity; only the spelling `Da` accepts prefixes, such as `kDa`.
382
+ `si.mTorr` is available, and Torr accepts SI prefixes. Tonne uses `t` or
383
+ `tonne`; the ambiguous spelling `ton` is not registered.
384
+
385
+ `si.rpm` represents rotational frequency: `60 * si.rpm` converts to `si.Hz`
386
+ with value 1. For angular velocity, multiply that frequency by `2 * PI` and
387
+ convert to `si.rad / si.s`; directly converting frequency to `rad/s` does
388
+ not insert the physical factor of 2π. `si.c` is a speed unit equal to the
389
+ speed of light, while `SI.c` is the corresponding physical quantity.
390
+
391
+ The spelling `u` denotes the unified atomic mass unit when used on its own.
392
+ ASCII micro spellings such as `um` and `us` remain supported without warnings,
393
+ so entering non-ASCII characters is optional. `um`, `µm`, `μm`, and
394
+ `micrometre` all mean micrometres; the explicit product `u m` instead means
395
+ unified atomic mass unit times metre.
302
396
 
303
- SI prefixes are enabled by default; set `prefixable=False` to disable them.
304
- Prefix names attach directly to the first word of a multiword name, as in
305
- `kiloastronomical unit`; a separate `kilo astronomical unit` is not accepted.
306
397
  Registration raises `UnitRegistryError` if a new spelling, including a generated
307
398
  prefixed form, already resolves to a unit or conflicts with another spelling
308
399
  in the same definition. A multiword spelling must not replace an existing
309
400
  implicit product such as `metre second`. Validation finishes before the
310
401
  registry changes.
311
402
 
403
+ The registry and spelling rules live in `unitx.unitregistry`; parsing expressions
404
+ does not change unit definitions. A parser instance caches only a successful
405
+ result; failed parses may be retried after a definition is added. Registration
406
+ preserves existing valid spellings and validates before updating its indexes.
407
+ Concurrent registry writes and isolated registry contexts are not supported.
408
+
312
409
  Quantity definitions require positive finite `int`, `Fraction`, `Decimal`, or
313
410
  `float` magnitudes. Prefer `Fraction` or `Decimal` for exact scales. Floats use
314
411
  Factor's denominator limit and can warn or round to zero and fail, for example
@@ -4,7 +4,7 @@ build-backend = 'hatchling.build'
4
4
 
5
5
  [project]
6
6
  name = 'unitx'
7
- version = '0.6.3'
7
+ version = '0.7.1'
8
8
  description = 'A modern, intuitive Python library for units, quantities, and dimensional analysis.'
9
9
  readme = 'README.md'
10
10
  requires-python = '>=3.12'
@@ -4,6 +4,9 @@
4
4
  "src/unitx/_numerics.py",
5
5
  "src/unitx/factor.py",
6
6
  "src/unitx/unit.py",
7
+ "src/unitx/unitregistry.py",
8
+ "src/unitx/singleunit.py",
9
+ "src/unitx/unitparser.py",
7
10
  "tests/typing"
8
11
  ],
9
12
  "strict": [
@@ -0,0 +1,33 @@
1
+ """Class-based namespaces for constants accessed directly as class attributes.
2
+
3
+ Namespaces cannot be instantiated, and their attributes cannot be rebound or
4
+ deleted. Objects referenced by those attributes are not recursively frozen.
5
+ """
6
+
7
+ from typing import Any, NoReturn
8
+
9
+ __all__ = ['ConstNamespace']
10
+
11
+
12
+ class _ConstNamespaceMeta(type):
13
+ def __call__(metacls, *args: Any, **kwds: Any) -> NoReturn:
14
+ """
15
+ This namespace cannot be instantiated; access constants via class attributes.
16
+ """
17
+ raise TypeError(metacls.__call__.__doc__)
18
+
19
+ def __setattr__(cls, name, value) -> NoReturn:
20
+ """
21
+ Constant namespace attributes cannot be assigned.
22
+ """
23
+ raise AttributeError(cls.__class__.__setattr__.__doc__)
24
+
25
+ def __delattr__(cls, name) -> NoReturn:
26
+ """
27
+ Constant namespace attributes cannot be deleted.
28
+ """
29
+ raise AttributeError(cls.__class__.__delattr__.__doc__)
30
+
31
+
32
+ class ConstNamespace(metaclass=_ConstNamespaceMeta):
33
+ """Base for constant namespaces with protected class attribute bindings."""
@@ -1,16 +1,17 @@
1
1
  """Unicode utilities for unitx."""
2
2
 
3
3
  from fractions import Fraction
4
- from typing import Literal, TypeAlias
4
+ from typing import Literal
5
5
 
6
6
  __all__ = ['DOT', 'DotType', 'superscript']
7
7
 
8
8
  DOT = '⋅' # chr(0x22C5)
9
- DotType: TypeAlias = Literal['⋅']
9
+ type DotType = Literal['⋅']
10
10
 
11
- DIGITS = '0123456789+-=()'
12
- SUPERSCRIPTS = '⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾'
11
+ DIGITS = '0123456789+-'
12
+ SUPERSCRIPTS = '⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻'
13
13
  DIGIT_TO_SUP = str.maketrans(DIGITS, SUPERSCRIPTS)
14
+ SUP_TO_DIGIT = str.maketrans(SUPERSCRIPTS, DIGITS)
14
15
  SUPERSCRIPT_SLASH = 'ᐟ' # Canadian Syllabics Final Acute, chr(0x141F)
15
16
 
16
17
 
@@ -14,6 +14,7 @@ from .dimension import DIMENSIONLESS, Dimension
14
14
  from .exceptions import DimensionError
15
15
  from .factor import ONE, Factor, SymbolicConstant
16
16
  from .unit import Unit
17
+ from .unitregistry import PrefixPolicy
17
18
 
18
19
  # Magnitudes are exposed read-only; operations construct new quantities.
19
20
  T = TypeVar('T', bound=Magnitude, covariant=True)
@@ -208,7 +209,7 @@ class Quantity(Generic[T]):
208
209
  name: str,
209
210
  *name_aliases: str,
210
211
  symbol_aliases: str | tuple[str, ...] = (),
211
- prefixable: bool = True,
212
+ prefixable: PrefixPolicy = True,
212
213
  ) -> Unit:
213
214
  """Register a unit equal to this quantity, using Unit.define's name rules.
214
215
 
@@ -222,7 +223,11 @@ class Quantity(Generic[T]):
222
223
  """
223
224
  scale = Factor(self.value)
224
225
  return (scale * self.unit).define(
225
- symbol, name, *name_aliases, symbol_aliases=symbol_aliases, prefixable=prefixable
226
+ symbol,
227
+ name,
228
+ *name_aliases,
229
+ symbol_aliases=symbol_aliases,
230
+ prefixable=prefixable,
226
231
  )
227
232
 
228
233
  def _comparison_pair(self) -> tuple[Magnitude, Factor]: