unitx 0.6.3__tar.gz → 0.7.0__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 (50) hide show
  1. {unitx-0.6.3 → unitx-0.7.0}/CHANGELOG.md +64 -0
  2. {unitx-0.6.3 → unitx-0.7.0}/PKG-INFO +88 -12
  3. {unitx-0.6.3 → unitx-0.7.0}/README.md +87 -11
  4. {unitx-0.6.3 → unitx-0.7.0}/pyproject.toml +1 -1
  5. {unitx-0.6.3 → unitx-0.7.0}/pyrightconfig.json +3 -0
  6. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/_unicode.py +5 -4
  7. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/quantity.py +7 -2
  8. unitx-0.7.0/src/unitx/si/bases.py +91 -0
  9. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/si/constants.py +57 -71
  10. unitx-0.7.0/src/unitx/si/units.py +178 -0
  11. unitx-0.7.0/src/unitx/singleunit.py +160 -0
  12. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/unit.py +23 -14
  13. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/unitparser.py +47 -16
  14. unitx-0.7.0/src/unitx/unitregistry.py +305 -0
  15. unitx-0.6.3/tests/test_unit_data.py → unitx-0.7.0/tests/test_builtin_definitions.py +21 -21
  16. {unitx-0.6.3 → unitx-0.7.0}/tests/test_quantity.py +2 -1
  17. {unitx-0.6.3 → unitx-0.7.0}/tests/test_si.py +60 -1
  18. {unitx-0.6.3 → unitx-0.7.0}/tests/test_singleunit.py +35 -15
  19. {unitx-0.6.3 → unitx-0.7.0}/tests/test_unit_definition.py +51 -44
  20. {unitx-0.6.3 → unitx-0.7.0}/tests/test_unitparser.py +78 -0
  21. unitx-0.7.0/tests/test_unitregistry.py +204 -0
  22. {unitx-0.6.3 → unitx-0.7.0}/tests/typing/check_quantity.py +19 -0
  23. {unitx-0.6.3 → unitx-0.7.0}/tests/typing/check_si.py +4 -0
  24. unitx-0.6.3/src/unitx/_unit_data.py +0 -193
  25. unitx-0.6.3/src/unitx/si/units.py +0 -193
  26. unitx-0.6.3/src/unitx/singleunit.py +0 -208
  27. {unitx-0.6.3 → unitx-0.7.0}/.github/workflows/ci.yml +0 -0
  28. {unitx-0.6.3 → unitx-0.7.0}/.github/workflows/release.yml +0 -0
  29. {unitx-0.6.3 → unitx-0.7.0}/.gitignore +0 -0
  30. {unitx-0.6.3 → unitx-0.7.0}/LICENSE +0 -0
  31. {unitx-0.6.3 → unitx-0.7.0}/conftest.py +0 -0
  32. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/__init__.py +0 -0
  33. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/_constclass.py +0 -0
  34. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/_numerics.py +0 -0
  35. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/dimension.py +0 -0
  36. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/exceptions.py +0 -0
  37. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/factor.py +0 -0
  38. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/monomial.py +0 -0
  39. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/py.typed +0 -0
  40. {unitx-0.6.3 → unitx-0.7.0}/src/unitx/si/__init__.py +0 -0
  41. {unitx-0.6.3 → unitx-0.7.0}/tests/__init__.py +0 -0
  42. {unitx-0.6.3 → unitx-0.7.0}/tests/test_dimension.py +0 -0
  43. {unitx-0.6.3 → unitx-0.7.0}/tests/test_factor.py +0 -0
  44. {unitx-0.6.3 → unitx-0.7.0}/tests/test_import.py +0 -0
  45. {unitx-0.6.3 → unitx-0.7.0}/tests/test_monomial.py +0 -0
  46. {unitx-0.6.3 → unitx-0.7.0}/tests/test_numeric.py +0 -0
  47. {unitx-0.6.3 → unitx-0.7.0}/tests/test_typing.py +0 -0
  48. {unitx-0.6.3 → unitx-0.7.0}/tests/test_unicode.py +0 -0
  49. {unitx-0.6.3 → unitx-0.7.0}/tests/test_unit.py +0 -0
  50. {unitx-0.6.3 → unitx-0.7.0}/tests/test_warnings.py +0 -0
@@ -1,5 +1,69 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.0] - 2026-10-19
4
+
5
+ ### Added
6
+
7
+ - Add `[registered name]` references as single factors in unit expressions,
8
+ including products, reciprocals, parentheses, and rational powers. References
9
+ resolve names and name aliases only, with whitespace normalization and no
10
+ expression evaluation or nested quoting.
11
+ - Support standalone hyphenated names and adjacent Unicode integer superscripts
12
+ such as `m²`, `s⁻¹²`, and `[metre]²`.
13
+ - Extend `prefixable: PrefixPolicy = True` on all `.define()` entry points to
14
+ accept a tuple of exact registered symbols, names, or aliases. Only those
15
+ spellings accept prefixes; True enables all spellings and False or () disables
16
+ all spellings. `PrefixPolicy` is `bool | tuple[str, ...]`.
17
+ - Enable prefixes on `arcsec` and `arcsecond` while keeping angle punctuation
18
+ unprefixable. Register unprefixable are (`a`) and hectare (`ha`), and reactive
19
+ power (`var`, name `volt-ampere reactive`). Keep `as` as attosecond and `yr`
20
+ as a 365-day year.
21
+ - Add the Julian year `a_j` (exactly 31,557,600 seconds) and its twelfth,
22
+ `mo`, as a fixed month interval distinct from variable calendar months.
23
+ - Add light year (`ly`), unified atomic mass unit (`u`), and common prefixed
24
+ namespace attributes, including attosecond (`as_`) and petaelectronvolt (`PeV`).
25
+
26
+ ### Changed
27
+
28
+ - Move definitions, spelling resolution, and registration validation into
29
+ `unitx.unitregistry`, shared by SingleUnit and UnitParser. Preserve formal identities,
30
+ exact SI factors, existing `.define()` defaults, and validation before mutation.
31
+ - Remove the superseded private `_unit_data` module. Name definition records
32
+ `PrefixDefinition` and `UnitDefinition`, with explicit definition indexes and
33
+ alias-to-symbol mappings.
34
+ - Select permitted canonical output spellings after applying binding policies.
35
+ When only a name permits a prefix, expressions use `[name]` so repr and pickle
36
+ preserve the prefixed identity. Expose `PrefixPolicy` for type annotations.
37
+ - Use the finite spelling exceptions `megohm` and `kilohm` in preferred names,
38
+ retaining `megaohm` and `kiloohm` as accepted inputs.
39
+ - Reuse Unicode superscript tables, handle malformed name brackets through
40
+ the common lexical error path, and simplify definition initialization,
41
+ preferred spelling selection, and generated prefix-name validation.
42
+ - Share coherent SI units and the seven defining constants through private
43
+ bases. Derive `ly` from `c` and the Julian year, and `eV` from `e` and `V`,
44
+ preserving the public `si` and `SI` namespaces and constant magnitude types.
45
+
46
+ ### Fixed
47
+
48
+ - Register metre of mercury (`mHg`) as the prefixable unit and construct `mmHg`
49
+ from its milli prefix. Preserve `760 mmHg = 1 atm` and the existing Torr scale,
50
+ keep the definition exact, and reject stacked prefixes such as `kmmHg`.
51
+
52
+ ### Migration
53
+
54
+ - Square brackets and `⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻` are now reserved grammar characters and can
55
+ no longer occur in registered symbols or names. Rename custom spellings that
56
+ previously used them before rebuilding or unpickling those definitions.
57
+ - Long names remain whole-input lookups unless explicitly referenced in brackets.
58
+ `str()` remains readable display and is not a general serialization format;
59
+ use `expression()` for formal unit products and repr for ScaledUnit state.
60
+ - `mmHg` now has the formal identity of milli-prefixed `mHg`; rebuild stored
61
+ units when moving between versions. Its pressure conversion scale is unchanged.
62
+ `kmmHg` and names with repeated prefixes are no longer accepted.
63
+ - Registering `u` makes ASCII micro spellings such as `um` issue the existing
64
+ ambiguity warning. Their meaning is unchanged; use `µm`, `μm`, or `micrometre`
65
+ to avoid the warning. `kt` remains a knot alias and tonne stays unprefixable.
66
+
3
67
  ## [0.6.3] - 2026-10-18
4
68
 
5
69
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: unitx
3
- Version: 0.6.3
3
+ Version: 0.7.0
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
@@ -246,14 +246,25 @@ False
246
246
  token: `ms` is a millisecond, while `m s` is metre times second.
247
247
  - Powers can use adjacent numbers, `^`, or `**`. Exponents written as ratios
248
248
  require parentheses, as in `m^(1/2)`; decimal exponents such as `m^0.5` are exact.
249
+ Adjacent integer superscripts are also accepted: `m²`, `s⁻¹²`, and `(m/s)²`.
250
+ Superscript fractions and mixed exponent notations are not supported.
249
251
  - Multiplication and division associate left to right. Use `m/(s A)` to put
250
252
  both `s` and `A` in the denominator; `m/s A` means `(m/s)*A`.
251
253
  - Unit symbols and names are case-sensitive. `u`, `µ`, and `μ` are accepted
252
254
  micro prefixes. Use lowercase `k` for kilo; `K` denotes kelvin.
253
- - A registered multiword name is recognized before implicit multiplication
255
+ - A registered multiword or hyphenated name is recognized before implicit multiplication
254
256
  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.
257
+ as a single space. Inside expressions, use `[name]` to make a name one factor,
258
+ for example `[astronomical unit] / s` or `[volt-ampere reactive]**2`.
259
+ Symbols and `Unit` object composition remain available.
260
+
261
+ Square brackets reference registered names and name aliases only: `[metre]`
262
+ works, while `[m]` and `[m/s]` do not. The contents are never evaluated as an
263
+ expression. Empty, nested, and unmatched brackets are errors. Put permitted
264
+ name prefixes inside the brackets, as in `[kiloastronomical unit]`; `k[name]`
265
+ does not apply a prefix. Single and double quotes retain their angle-unit
266
+ meaning rather than acting as string delimiters. Bare compound names are
267
+ not guessed from adjacent words, and ordinary parentheses still group products.
257
268
 
258
269
  The unit table contains `s`, `m`, `g`, `A`, `K`, `mol`, and `cd`, plus the
259
270
  dimensionless identity. Importing unitx also initializes the common units in
@@ -303,9 +314,10 @@ aliases gives an empty tuple. `symbol_aliases` accepts a string or tuple of
303
314
  strings; it and `prefixable` must be passed by keyword.
304
315
  Symbols and symbol aliases must each be a single printable unit token.
305
316
  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.
317
+ ASCII spaces or hyphens, without leading or trailing separators. Decimal digits
318
+ and parser operators are not allowed in any token. The primary symbol and `name` are
319
+ required and must be nonempty; empty aliases are ignored. Square brackets and
320
+ the superscript characters `⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻` are reserved for parser syntax.
309
321
 
310
322
  ```pycon
311
323
  >>> span = (2 * si.m).define('span', 'survey span', 'survey distance')
@@ -315,22 +327,86 @@ True
315
327
  True
316
328
  >>> Unit('survey span') / si.s == Unit('span / s')
317
329
  True
330
+ >>> Unit('[survey span] / [second]') == Unit('span / s')
331
+ True
318
332
 
319
333
  ```
320
334
 
321
- Multiword names are recognized only as complete inputs. For example,
335
+ Bare multiword names are recognized only as complete inputs. For example,
322
336
  `Unit('astronomical unit / arcsec')` does not recognize `astronomical unit`
323
- as a factor; use one of the two compound forms above instead.
337
+ as a factor; use `[astronomical unit] / arcsec`, symbols, or Unit objects instead.
338
+
339
+ SI prefixes are enabled by default. `prefixable: PrefixPolicy = True` accepts
340
+ `True` for all spellings, `False` for none, or a tuple of exact symbols, names,
341
+ and aliases supplied in that definition. With a tuple, only the listed
342
+ spellings accept prefixes; an empty tuple disables prefixes. Unknown spellings
343
+ and non-string entries are errors. For example,
344
+ `prefixable=('arcsec', 'arcsecond')` enables all SI prefixes on those two
345
+ spellings, while other spellings of that unit remain unprefixable.
346
+
347
+ The tuple selects **unit spellings**, not prefix symbols. Prefix names attach
348
+ directly to the first word of a multiword name, as in `kiloastronomical unit`;
349
+ a separate `kilo astronomical unit` is not accepted. `PrefixPolicy` is available
350
+ from `unitx.unitregistry` for annotations and means `bool | tuple[str, ...]`.
351
+
352
+ ```pycon
353
+ >>> tick = si.s.define(
354
+ ... 'demoTick',
355
+ ... 'survey interval',
356
+ ... symbol_aliases='demoT',
357
+ ... prefixable=('demoT', 'survey interval'),
358
+ ... )
359
+ >>> Unit('[millisurvey interval]') == Unit('mdemoT')
360
+ True
361
+ >>> Quantity(1000, 'mdemoT').to(tick).value
362
+ Fraction(1, 1)
363
+ >>> Unit('mdemoT').expression()
364
+ 'mdemoT'
365
+
366
+ ```
367
+
368
+ Prefix permissions are checked before alias normalization. Canonical identity
369
+ still uses the original registered unit and canonical prefix, independent of
370
+ the input alias. Preferred spellings use the first permitted symbol or alias;
371
+ if only a name supports a prefix, `SingleUnit.symbol` falls back to that name
372
+ and `Unit.expression()` emits a `[name]` reference. Conversely, `SingleUnit.name`
373
+ falls back to a symbol if no name allows that prefix. Repr and pickle preserve
374
+ the same formal unit, rather than replacing the prefix with an extra scale.
375
+
376
+ `arcsec` and `arcsecond` accept prefixes, for example `marcsec` and
377
+ `milliarcsecond`; `″` and `"` do not. `as` continues to mean attosecond,
378
+ and `mas` is not an arcsecond alias. `a` (are) and `ha` (hectare) are separate
379
+ unprefixable units; `Pa` remains pascal. `yr` remains exactly 365 days.
380
+ `a_j` is the Julian year of exactly 365.25 days (31,557,600 seconds), while
381
+ `mo` is one twelfth of that interval, rather than a variable calendar month.
382
+ The name exceptions `megohm` and `kilohm` are supported alongside the older
383
+ `megaohm` and `kiloohm` spellings; `gigaohm` retains both vowels. No general
384
+ vowel-elision rule is applied.
385
+
386
+ `ly` is defined using the speed of light and the Julian year; `eV` uses the
387
+ elementary charge and volt. Both reuse the defining constants exposed by `SI`.
388
+ `mHg` is the registered metre-of-mercury unit and `mmHg` is its milli-prefixed
389
+ form; `760 * si.mmHg` converts to `si.atm` with value 1 under the library's
390
+ existing pressure convention. Prefixes do not stack: `mmmHg` and `kmmHg` are
391
+ invalid. `kt` retains its meaning as a knot alias; tonne remains unprefixable.
392
+
393
+ The newly registered `u` denotes the unified atomic mass unit. ASCII micro
394
+ spellings such as `um` still mean micrometres, but now issue `UnitSymbolWarning`
395
+ because `u m` has a separate meaning. Use `µm`, `μm`, or `micrometre` to avoid
396
+ this warning.
324
397
 
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
398
  Registration raises `UnitRegistryError` if a new spelling, including a generated
329
399
  prefixed form, already resolves to a unit or conflicts with another spelling
330
400
  in the same definition. A multiword spelling must not replace an existing
331
401
  implicit product such as `metre second`. Validation finishes before the
332
402
  registry changes.
333
403
 
404
+ The registry and spelling rules live in `unitx.unitregistry`; parsing expressions
405
+ does not change unit definitions. A parser instance caches only a successful
406
+ result; failed parses may be retried after a definition is added. Registration
407
+ preserves existing valid spellings and validates before updating its indexes.
408
+ Concurrent registry writes and isolated registry contexts are not supported.
409
+
334
410
  Quantity definitions require positive finite `int`, `Fraction`, `Decimal`, or
335
411
  `float` magnitudes. Prefer `Fraction` or `Decimal` for exact scales. Floats use
336
412
  Factor's denominator limit and can warn or round to zero and fail, for example
@@ -224,14 +224,25 @@ False
224
224
  token: `ms` is a millisecond, while `m s` is metre times second.
225
225
  - Powers can use adjacent numbers, `^`, or `**`. Exponents written as ratios
226
226
  require parentheses, as in `m^(1/2)`; decimal exponents such as `m^0.5` are exact.
227
+ Adjacent integer superscripts are also accepted: `m²`, `s⁻¹²`, and `(m/s)²`.
228
+ Superscript fractions and mixed exponent notations are not supported.
227
229
  - Multiplication and division associate left to right. Use `m/(s A)` to put
228
230
  both `s` and `A` in the denominator; `m/s A` means `(m/s)*A`.
229
231
  - Unit symbols and names are case-sensitive. `u`, `µ`, and `μ` are accepted
230
232
  micro prefixes. Use lowercase `k` for kilo; `K` denotes kelvin.
231
- - A registered multiword name is recognized before implicit multiplication
233
+ - A registered multiword or hyphenated name is recognized before implicit multiplication
232
234
  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.
235
+ as a single space. Inside expressions, use `[name]` to make a name one factor,
236
+ for example `[astronomical unit] / s` or `[volt-ampere reactive]**2`.
237
+ Symbols and `Unit` object composition remain available.
238
+
239
+ Square brackets reference registered names and name aliases only: `[metre]`
240
+ works, while `[m]` and `[m/s]` do not. The contents are never evaluated as an
241
+ expression. Empty, nested, and unmatched brackets are errors. Put permitted
242
+ name prefixes inside the brackets, as in `[kiloastronomical unit]`; `k[name]`
243
+ does not apply a prefix. Single and double quotes retain their angle-unit
244
+ meaning rather than acting as string delimiters. Bare compound names are
245
+ not guessed from adjacent words, and ordinary parentheses still group products.
235
246
 
236
247
  The unit table contains `s`, `m`, `g`, `A`, `K`, `mol`, and `cd`, plus the
237
248
  dimensionless identity. Importing unitx also initializes the common units in
@@ -281,9 +292,10 @@ aliases gives an empty tuple. `symbol_aliases` accepts a string or tuple of
281
292
  strings; it and `prefixable` must be passed by keyword.
282
293
  Symbols and symbol aliases must each be a single printable unit token.
283
294
  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.
295
+ ASCII spaces or hyphens, without leading or trailing separators. Decimal digits
296
+ and parser operators are not allowed in any token. The primary symbol and `name` are
297
+ required and must be nonempty; empty aliases are ignored. Square brackets and
298
+ the superscript characters `⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻` are reserved for parser syntax.
287
299
 
288
300
  ```pycon
289
301
  >>> span = (2 * si.m).define('span', 'survey span', 'survey distance')
@@ -293,22 +305,86 @@ True
293
305
  True
294
306
  >>> Unit('survey span') / si.s == Unit('span / s')
295
307
  True
308
+ >>> Unit('[survey span] / [second]') == Unit('span / s')
309
+ True
296
310
 
297
311
  ```
298
312
 
299
- Multiword names are recognized only as complete inputs. For example,
313
+ Bare multiword names are recognized only as complete inputs. For example,
300
314
  `Unit('astronomical unit / arcsec')` does not recognize `astronomical unit`
301
- as a factor; use one of the two compound forms above instead.
315
+ as a factor; use `[astronomical unit] / arcsec`, symbols, or Unit objects instead.
316
+
317
+ SI prefixes are enabled by default. `prefixable: PrefixPolicy = True` accepts
318
+ `True` for all spellings, `False` for none, or a tuple of exact symbols, names,
319
+ and aliases supplied in that definition. With a tuple, only the listed
320
+ spellings accept prefixes; an empty tuple disables prefixes. Unknown spellings
321
+ and non-string entries are errors. For example,
322
+ `prefixable=('arcsec', 'arcsecond')` enables all SI prefixes on those two
323
+ spellings, while other spellings of that unit remain unprefixable.
324
+
325
+ The tuple selects **unit spellings**, not prefix symbols. Prefix names attach
326
+ directly to the first word of a multiword name, as in `kiloastronomical unit`;
327
+ a separate `kilo astronomical unit` is not accepted. `PrefixPolicy` is available
328
+ from `unitx.unitregistry` for annotations and means `bool | tuple[str, ...]`.
329
+
330
+ ```pycon
331
+ >>> tick = si.s.define(
332
+ ... 'demoTick',
333
+ ... 'survey interval',
334
+ ... symbol_aliases='demoT',
335
+ ... prefixable=('demoT', 'survey interval'),
336
+ ... )
337
+ >>> Unit('[millisurvey interval]') == Unit('mdemoT')
338
+ True
339
+ >>> Quantity(1000, 'mdemoT').to(tick).value
340
+ Fraction(1, 1)
341
+ >>> Unit('mdemoT').expression()
342
+ 'mdemoT'
343
+
344
+ ```
345
+
346
+ Prefix permissions are checked before alias normalization. Canonical identity
347
+ still uses the original registered unit and canonical prefix, independent of
348
+ the input alias. Preferred spellings use the first permitted symbol or alias;
349
+ if only a name supports a prefix, `SingleUnit.symbol` falls back to that name
350
+ and `Unit.expression()` emits a `[name]` reference. Conversely, `SingleUnit.name`
351
+ falls back to a symbol if no name allows that prefix. Repr and pickle preserve
352
+ the same formal unit, rather than replacing the prefix with an extra scale.
353
+
354
+ `arcsec` and `arcsecond` accept prefixes, for example `marcsec` and
355
+ `milliarcsecond`; `″` and `"` do not. `as` continues to mean attosecond,
356
+ and `mas` is not an arcsecond alias. `a` (are) and `ha` (hectare) are separate
357
+ unprefixable units; `Pa` remains pascal. `yr` remains exactly 365 days.
358
+ `a_j` is the Julian year of exactly 365.25 days (31,557,600 seconds), while
359
+ `mo` is one twelfth of that interval, rather than a variable calendar month.
360
+ The name exceptions `megohm` and `kilohm` are supported alongside the older
361
+ `megaohm` and `kiloohm` spellings; `gigaohm` retains both vowels. No general
362
+ vowel-elision rule is applied.
363
+
364
+ `ly` is defined using the speed of light and the Julian year; `eV` uses the
365
+ elementary charge and volt. Both reuse the defining constants exposed by `SI`.
366
+ `mHg` is the registered metre-of-mercury unit and `mmHg` is its milli-prefixed
367
+ form; `760 * si.mmHg` converts to `si.atm` with value 1 under the library's
368
+ existing pressure convention. Prefixes do not stack: `mmmHg` and `kmmHg` are
369
+ invalid. `kt` retains its meaning as a knot alias; tonne remains unprefixable.
370
+
371
+ The newly registered `u` denotes the unified atomic mass unit. ASCII micro
372
+ spellings such as `um` still mean micrometres, but now issue `UnitSymbolWarning`
373
+ because `u m` has a separate meaning. Use `µm`, `μm`, or `micrometre` to avoid
374
+ this warning.
302
375
 
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
376
  Registration raises `UnitRegistryError` if a new spelling, including a generated
307
377
  prefixed form, already resolves to a unit or conflicts with another spelling
308
378
  in the same definition. A multiword spelling must not replace an existing
309
379
  implicit product such as `metre second`. Validation finishes before the
310
380
  registry changes.
311
381
 
382
+ The registry and spelling rules live in `unitx.unitregistry`; parsing expressions
383
+ does not change unit definitions. A parser instance caches only a successful
384
+ result; failed parses may be retried after a definition is added. Registration
385
+ preserves existing valid spellings and validates before updating its indexes.
386
+ Concurrent registry writes and isolated registry contexts are not supported.
387
+
312
388
  Quantity definitions require positive finite `int`, `Fraction`, `Decimal`, or
313
389
  `float` magnitudes. Prefer `Fraction` or `Decimal` for exact scales. Floats use
314
390
  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.0'
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": [
@@ -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]:
@@ -0,0 +1,91 @@
1
+ """Shared coherent SI units and the seven defining constants."""
2
+
3
+ from typing import Final
4
+
5
+ from .._constclass import ConstClass
6
+ from ..factor import ONE
7
+ from ..quantity import Quantity, quantity
8
+ from ..unit import Unit
9
+
10
+
11
+ class _si(ConstClass):
12
+ # SI base unit
13
+
14
+ one: Final = Unit()
15
+ """unitless"""
16
+ s: Final = Unit('s')
17
+ """second, time"""
18
+ m: Final = Unit('m')
19
+ """metre/meter, length"""
20
+ kg: Final = Unit('kg')
21
+ """kilogram, mass"""
22
+ A: Final = Unit('A')
23
+ """ampere, electric current"""
24
+ K: Final = Unit('K')
25
+ """kelvin, thermodynamic temperature"""
26
+ mol: Final = Unit('mol')
27
+ """mole, amount of substance"""
28
+ cd: Final = Unit('cd')
29
+ """candela, luminous intensity"""
30
+
31
+ # SI derived unit
32
+
33
+ rad: Final = one.define('rad', name='radian')
34
+ """radian, plane angle"""
35
+ sr: Final = one.define('sr', name='steradian')
36
+ """steradian, solid angle"""
37
+ Hz: Final = (ONE / s).define('Hz', name='hertz')
38
+ """hertz, frequency"""
39
+ N: Final = (kg * m / s**2).define('N', name='newton')
40
+ """newton, force/weight"""
41
+ Pa: Final = (N / m**2).define('Pa', name='pascal')
42
+ """pascal, pressure/stress"""
43
+ J: Final = (N * m).define('J', name='joule')
44
+ """joule, energy/work/heat"""
45
+ W: Final = (J / s).define('W', name='watt')
46
+ """watt, power/radiant flux"""
47
+ C: Final = (A * s).define('C', name='coulomb')
48
+ """coulomb, electric charge"""
49
+ V: Final = (W / A).define('V', name='volt')
50
+ """volt, electric potential/voltage/emf"""
51
+ F: Final = (C / V).define('F', name='farad')
52
+ """farad, capacitance"""
53
+ ohm: Final = (V / A).define('Ω', name='ohm')
54
+ """ohm, resistance/impedance/reactance"""
55
+ S: Final = (A / V).define('S', name='siemens')
56
+ """siemens, electrical conductance"""
57
+ Wb: Final = (V * s).define('Wb', name='weber')
58
+ """weber, magnetic flux"""
59
+ T: Final = (Wb / m**2).define('T', name='tesla')
60
+ """tesla, magnetic flux density"""
61
+ H: Final = (Wb / A).define('H', name='henry')
62
+ """henry, inductance"""
63
+ lm: Final = (cd * sr).define('lm', name='lumen')
64
+ """lumen, luminous flux"""
65
+ lx: Final = (lm / m**2).define('lx', name='lux')
66
+ """lux, illuminance"""
67
+ Bq: Final = (ONE / s).define('Bq', name='becquerel')
68
+ """becquerel, activity referred to a radionuclide"""
69
+ Gy: Final = (J / kg).define('Gy', name='gray')
70
+ """gray, absorbed dose"""
71
+ Sv: Final = (J / kg).define('Sv', name='sievert')
72
+ """sievert, equivalent dose"""
73
+ kat: Final = (mol / s).define('kat', name='katal')
74
+ """katal, catalytic activity"""
75
+
76
+
77
+ class _SI(ConstClass):
78
+ DeltanuCs: Final = Quantity(9_192_631_770, _si.Hz)
79
+ """hyperfine transition frequency of Cs-133, definition of second."""
80
+ c: Final = Quantity(299_792_458, _si.m / _si.s)
81
+ """speed of light in vacuum"""
82
+ h: Final = quantity(6.626_070_15e-34, _si.J * _si.s)
83
+ """Planck constant"""
84
+ e: Final = quantity(1.602_176_634e-19, _si.C)
85
+ """elementary charge"""
86
+ kB: Final = quantity(1.380_649e-23, _si.J / _si.K)
87
+ """Boltzmann constant"""
88
+ NA: Final = quantity(6.022_140_76e23, _si.mol**-1)
89
+ """Avogadro constant"""
90
+ Kcd: Final = Quantity(683, _si.lm / _si.W)
91
+ """luminous efficacy of 540 THz radiation, definition of candela"""