unit-syntax 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unit-syntax
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Physical unit literals for Jupyter and IPython
5
5
  Home-page: https://github.com/ahupp/unit-syntax
6
6
  Author: Adam Hupp
@@ -25,11 +25,11 @@ Description-Content-Type: text/markdown
25
25
  10 meter
26
26
  ```
27
27
 
28
- Why? I often use Python as an interactive calculator for physical problems, and wished it had the type safety of explicit units along with the readability of normal notation.
28
+ Why? I often use Python as an interactive calculator for physical problems and wished it had the type safety of explicit units along with the readability of normal notation.
29
29
 
30
- Where? `unit-syntax` currently supports Jupyter notebooks and the IPython interpreter; support for standalone scripts is planned.
30
+ `unit-syntax` currently supports Jupyter notebooks and the IPython interpreter; maybe someday support for standalone scripts.
31
31
 
32
- How? A syntax transformer based on the official Python grammar turns these expression into calls to the excellent [Pint](https://pint.readthedocs.io/) units library.
32
+ [How does it work?](#how-does-it-work)
33
33
 
34
34
  ## Getting Started
35
35
 
@@ -42,8 +42,7 @@ $ pip install unit-syntax
42
42
  To enable unit-syntax in a Jupyter/IPython session run:
43
43
 
44
44
  ```python
45
- import unit_syntax
46
- unit_syntax.enable_ipython()
45
+ %load_ext unit_syntax
47
46
  ```
48
47
 
49
48
  Tip: In Jupyter this must be run in its own cell before any units expressions are evaluated.
@@ -60,13 +59,13 @@ Units can be applied to any "simple" expression:
60
59
  - unary operators: `-x dBm`
61
60
  - power: `x**2 meters`
62
61
 
63
- To apply units within a more complex expression, use parentheses:
62
+ In expressions mixing units and binary operators, parenthesize:
64
63
 
65
64
  ```python
66
65
  one_lux = (1 lumen)/(1 meter**2)
67
66
  ```
68
67
 
69
- Units can be used in other places where Python allows expressions like:
68
+ Units can be used in any place where Python allows expressions, e.g:
70
69
 
71
70
  - function arguments: `area_of_circle(radius=1 meter)`
72
71
  - list comprehensions: `[x meters for x in range(10)]`
@@ -92,26 +91,32 @@ x (newton meters)/(second*kg)
92
91
  x newton meters/(second*kg)
93
92
  ```
94
93
 
95
- ## Why only allow units on simple expressions?
96
-
97
- The rule for applying units only to "simple" expressions rather than treating it as a typical operator is to avoid unintentional error. Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
94
+ Using invalid units produces a syntax error at import time:
98
95
 
99
96
  ```python
100
- ppi = 300 pixels/inch
101
- y = x inches * ppi
97
+ >>> 1 smoot
98
+ ...
99
+ SyntaxError: 'smoot' is not defined in the unit registry
102
100
  ```
103
101
 
104
- `inches * ppi` would be parsed as the unit, leading to (at best) a runtime error sometime later and at worst an incorrect calculation. This could be avoided by parenthesizing the expression (e.g. `(x inches) * ppi`, but in general it's too error prone to allow free intermixing of operators and units. (Note: This is not a hypoethical concern, I hit this within 10 minutes of first trying out the idea)
102
+ ## How does it work?
105
103
 
106
- ## Help!
104
+ The parser is [pegen](https://we-like-parsers.github.io/pegen/), which is the same parser generator used by Python itself. The grammar is a lightly modified version the official Python grammar.
107
105
 
108
- If you're getting an unexpected result, try using `unit_syntax.enable_ipython(debug_transform=True)`. This will log the transformed Python code to the console.
106
+ Syntax transformation in IPython/Jupyter uses [IPython custom input transformers](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html).
109
107
 
110
- If you're stuck, feel free to open an issue.
108
+ ## Why only allow units on simple expressions?
111
109
 
112
- ## How does it work?
110
+ Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
111
+
112
+ ```python
113
+ ppi = 300 pixels/inch
114
+ y = x inches * ppi
115
+ ```
116
+
117
+ `inches * ppi` would be parsed as the unit, leading to (at best) a runtime error sometime later and at worst an incorrect calculation. This could be avoided by parenthesizing the expression (e.g. `(x inches) * ppi`, but in general it's too error prone to allow free intermixing of operators and units. (Note: This is not a hypothical concern, I hit this within 10 minutes of first trying out the idea)
113
118
 
114
- The parser is derived from the official Python grammar using the same parser generator ([pegen](https://github.com/we-like-parsers/pegen)) as Python itself. The transformer hooks into IPython/Jupyter using [custom input transformers](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html).
119
+ It's easy to detect units used when they aren't allowed (that syntax isn't valid anywhere else), but not generally possible to determine if you forgot parens or meant to write a unit.
115
120
 
116
121
  ## Prior Art
117
122
 
@@ -127,7 +132,7 @@ A [long discussion on the python-ideas mailing list](https://lwn.net/Articles/90
127
132
 
128
133
  To regenerate the parser:
129
134
 
130
- `python -m pegen grammar.txt -o unit_syntax/parser.py`
135
+ `python -m pegen python_units.gram -o unit_syntax/parser.py`
131
136
 
132
137
  Running tests:
133
138
 
@@ -140,11 +145,10 @@ $ poetry run pytest
140
145
 
141
146
  - Test against various ipython and python versions
142
147
  - Support standalone scripts through sys.meta_path
143
- - Check units at parse time
144
148
  - Unit type hints, maybe checked with [@runtime_checkable](https://docs.python.org/3/library/typing.html#typing.runtime_checkable). More Pint typechecking [discussion](https://github.com/hgrecco/pint/issues/1166)
145
149
  - Expand the demo Colab notebook
146
150
  - Typography of output
147
151
  - make it work with numba
148
152
  - understand how numpy interop works
149
- - fail more clearly when units are used in an invalid location
153
+ - pre-parse units
150
154
 
@@ -6,11 +6,11 @@
6
6
  10 meter
7
7
  ```
8
8
 
9
- Why? I often use Python as an interactive calculator for physical problems, and wished it had the type safety of explicit units along with the readability of normal notation.
9
+ Why? I often use Python as an interactive calculator for physical problems and wished it had the type safety of explicit units along with the readability of normal notation.
10
10
 
11
- Where? `unit-syntax` currently supports Jupyter notebooks and the IPython interpreter; support for standalone scripts is planned.
11
+ `unit-syntax` currently supports Jupyter notebooks and the IPython interpreter; maybe someday support for standalone scripts.
12
12
 
13
- How? A syntax transformer based on the official Python grammar turns these expression into calls to the excellent [Pint](https://pint.readthedocs.io/) units library.
13
+ [How does it work?](#how-does-it-work)
14
14
 
15
15
  ## Getting Started
16
16
 
@@ -23,8 +23,7 @@ $ pip install unit-syntax
23
23
  To enable unit-syntax in a Jupyter/IPython session run:
24
24
 
25
25
  ```python
26
- import unit_syntax
27
- unit_syntax.enable_ipython()
26
+ %load_ext unit_syntax
28
27
  ```
29
28
 
30
29
  Tip: In Jupyter this must be run in its own cell before any units expressions are evaluated.
@@ -41,13 +40,13 @@ Units can be applied to any "simple" expression:
41
40
  - unary operators: `-x dBm`
42
41
  - power: `x**2 meters`
43
42
 
44
- To apply units within a more complex expression, use parentheses:
43
+ In expressions mixing units and binary operators, parenthesize:
45
44
 
46
45
  ```python
47
46
  one_lux = (1 lumen)/(1 meter**2)
48
47
  ```
49
48
 
50
- Units can be used in other places where Python allows expressions like:
49
+ Units can be used in any place where Python allows expressions, e.g:
51
50
 
52
51
  - function arguments: `area_of_circle(radius=1 meter)`
53
52
  - list comprehensions: `[x meters for x in range(10)]`
@@ -73,26 +72,32 @@ x (newton meters)/(second*kg)
73
72
  x newton meters/(second*kg)
74
73
  ```
75
74
 
76
- ## Why only allow units on simple expressions?
77
-
78
- The rule for applying units only to "simple" expressions rather than treating it as a typical operator is to avoid unintentional error. Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
75
+ Using invalid units produces a syntax error at import time:
79
76
 
80
77
  ```python
81
- ppi = 300 pixels/inch
82
- y = x inches * ppi
78
+ >>> 1 smoot
79
+ ...
80
+ SyntaxError: 'smoot' is not defined in the unit registry
83
81
  ```
84
82
 
85
- `inches * ppi` would be parsed as the unit, leading to (at best) a runtime error sometime later and at worst an incorrect calculation. This could be avoided by parenthesizing the expression (e.g. `(x inches) * ppi`, but in general it's too error prone to allow free intermixing of operators and units. (Note: This is not a hypoethical concern, I hit this within 10 minutes of first trying out the idea)
83
+ ## How does it work?
86
84
 
87
- ## Help!
85
+ The parser is [pegen](https://we-like-parsers.github.io/pegen/), which is the same parser generator used by Python itself. The grammar is a lightly modified version the official Python grammar.
88
86
 
89
- If you're getting an unexpected result, try using `unit_syntax.enable_ipython(debug_transform=True)`. This will log the transformed Python code to the console.
87
+ Syntax transformation in IPython/Jupyter uses [IPython custom input transformers](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html).
90
88
 
91
- If you're stuck, feel free to open an issue.
89
+ ## Why only allow units on simple expressions?
92
90
 
93
- ## How does it work?
91
+ Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
92
+
93
+ ```python
94
+ ppi = 300 pixels/inch
95
+ y = x inches * ppi
96
+ ```
97
+
98
+ `inches * ppi` would be parsed as the unit, leading to (at best) a runtime error sometime later and at worst an incorrect calculation. This could be avoided by parenthesizing the expression (e.g. `(x inches) * ppi`, but in general it's too error prone to allow free intermixing of operators and units. (Note: This is not a hypothical concern, I hit this within 10 minutes of first trying out the idea)
94
99
 
95
- The parser is derived from the official Python grammar using the same parser generator ([pegen](https://github.com/we-like-parsers/pegen)) as Python itself. The transformer hooks into IPython/Jupyter using [custom input transformers](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html).
100
+ It's easy to detect units used when they aren't allowed (that syntax isn't valid anywhere else), but not generally possible to determine if you forgot parens or meant to write a unit.
96
101
 
97
102
  ## Prior Art
98
103
 
@@ -108,7 +113,7 @@ A [long discussion on the python-ideas mailing list](https://lwn.net/Articles/90
108
113
 
109
114
  To regenerate the parser:
110
115
 
111
- `python -m pegen grammar.txt -o unit_syntax/parser.py`
116
+ `python -m pegen python_units.gram -o unit_syntax/parser.py`
112
117
 
113
118
  Running tests:
114
119
 
@@ -121,10 +126,9 @@ $ poetry run pytest
121
126
 
122
127
  - Test against various ipython and python versions
123
128
  - Support standalone scripts through sys.meta_path
124
- - Check units at parse time
125
129
  - Unit type hints, maybe checked with [@runtime_checkable](https://docs.python.org/3/library/typing.html#typing.runtime_checkable). More Pint typechecking [discussion](https://github.com/hgrecco/pint/issues/1166)
126
130
  - Expand the demo Colab notebook
127
131
  - Typography of output
128
132
  - make it work with numba
129
133
  - understand how numpy interop works
130
- - fail more clearly when units are used in an invalid location
134
+ - pre-parse units
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "unit-syntax"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  authors = ["Adam Hupp <adam@hupp.org>"]
5
5
  packages = [{ include = "unit_syntax" }]
6
6
  readme = "README.md"
@@ -0,0 +1,14 @@
1
+ import pint
2
+
3
+
4
+ # Use the _DEFAULT_REGISTRY so we can interoperate with other pint-using libraries
5
+ ureg = pint._DEFAULT_REGISTRY
6
+
7
+
8
+ BOOTSTRAP = """
9
+ import unit_syntax
10
+ _unit_syntax_q = unit_syntax.ureg.Quantity
11
+ """
12
+
13
+
14
+ from .ipython import load_ipython_extension, unload_ipython_extension
@@ -0,0 +1,71 @@
1
+ from .transform import transform
2
+ import pint
3
+
4
+
5
+ def _add_formatters(ipython):
6
+ # Overide default formatters to use reduced units. This lets us keep using the
7
+ # default registry for interop with other pint-using libraries, but gives more
8
+ # sensible output in the notebook.
9
+ def add_formatter(mime, display_fn_name):
10
+ fn = getattr(pint.Quantity, display_fn_name, None)
11
+ if fn is None:
12
+ return
13
+
14
+ def fmt_reduced(q, *args):
15
+ return fn(q.to_reduced_units(), *args)
16
+
17
+ formatter = ipython.display_formatter.formatters[mime]
18
+ formatter.for_type(pint.Quantity, fmt_reduced)
19
+
20
+ add_formatter("text/markdown", "_repr_markdown_")
21
+ add_formatter("text/html", "_repr_html_")
22
+ add_formatter("text/plain", "_repr_pretty_")
23
+ add_formatter("text/latex", "_repr_latex_")
24
+
25
+
26
+ _has_init_log = False
27
+ _debug = False
28
+
29
+
30
+ def transform_lines(lines: list[str]) -> list[str]:
31
+ """IPython transforms provide a list of strings in the current cell, but to parse correctly we
32
+ need to parse them as a single string"""
33
+ return transform("".join(lines)).splitlines(keepends=True)
34
+
35
+
36
+ def ipython_transform(lines):
37
+ ret = transform_lines(lines)
38
+
39
+ if _debug:
40
+ import logging
41
+
42
+ global _has_init_log
43
+ if not _has_init_log:
44
+ logging.basicConfig(level=logging.DEBUG, force=True)
45
+ _has_init_log = True
46
+ logging.debug("unit_syntax: %s -> %s", "\n".join(lines), "\n".join(ret))
47
+ return ret
48
+
49
+
50
+ def load_ipython_extension(ipython):
51
+ if not hasattr(ipython, "input_transformers_post"):
52
+ raise ImportError("Unsupported IPython version, version >=7 is required")
53
+
54
+ ipython.input_transformers_post.append(ipython_transform)
55
+
56
+ _add_formatters(ipython)
57
+
58
+ from . import BOOTSTRAP
59
+
60
+ ipython.run_cell(BOOTSTRAP)
61
+
62
+ try:
63
+ import matplotlib
64
+
65
+ pint.setup_matplotlib(True)
66
+ except ImportError:
67
+ pass
68
+
69
+
70
+ def unload_ipython_extension(ipython):
71
+ ipython.input_transformers_post.remove(ipython_transform)