unit-syntax 0.2.1__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.1
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,7 +25,7 @@ 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
30
  `unit-syntax` currently supports Jupyter notebooks and the IPython interpreter; maybe someday support for standalone scripts.
31
31
 
@@ -42,7 +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.ipython
45
+ %load_ext unit_syntax
46
46
  ```
47
47
 
48
48
  Tip: In Jupyter this must be run in its own cell before any units expressions are evaluated.
@@ -105,30 +105,18 @@ The parser is [pegen](https://we-like-parsers.github.io/pegen/), which is the sa
105
105
 
106
106
  Syntax transformation in IPython/Jupyter uses [IPython custom input transformers](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html).
107
107
 
108
- ````python
109
-
110
- An IPython [custom input transformer](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html) parses the new syntax using a modified version of the official Python grammar, and translates it into calls to the excellent [Pint](https://pint.readthedocs.io/) units library.
111
-
112
108
  ## Why only allow units on simple expressions?
113
109
 
114
- 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:
110
+ Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
115
111
 
116
112
  ```python
117
113
  ppi = 300 pixels/inch
118
114
  y = x inches * ppi
119
- ````
120
-
121
- `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)
122
-
123
- ## Help!
124
-
125
- 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.
126
-
127
- If you're stuck, feel free to open an issue.
115
+ ```
128
116
 
129
- ## How does it work?
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)
130
118
 
131
- 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.
132
120
 
133
121
  ## Prior Art
134
122
 
@@ -162,4 +150,5 @@ $ poetry run pytest
162
150
  - Typography of output
163
151
  - make it work with numba
164
152
  - understand how numpy interop works
153
+ - pre-parse units
165
154
 
@@ -6,7 +6,7 @@
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
11
  `unit-syntax` currently supports Jupyter notebooks and the IPython interpreter; maybe someday support for standalone scripts.
12
12
 
@@ -23,7 +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.ipython
26
+ %load_ext unit_syntax
27
27
  ```
28
28
 
29
29
  Tip: In Jupyter this must be run in its own cell before any units expressions are evaluated.
@@ -86,30 +86,18 @@ The parser is [pegen](https://we-like-parsers.github.io/pegen/), which is the sa
86
86
 
87
87
  Syntax transformation in IPython/Jupyter uses [IPython custom input transformers](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html).
88
88
 
89
- ````python
90
-
91
- An IPython [custom input transformer](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html) parses the new syntax using a modified version of the official Python grammar, and translates it into calls to the excellent [Pint](https://pint.readthedocs.io/) units library.
92
-
93
89
  ## Why only allow units on simple expressions?
94
90
 
95
- 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:
91
+ Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
96
92
 
97
93
  ```python
98
94
  ppi = 300 pixels/inch
99
95
  y = x inches * ppi
100
- ````
101
-
102
- `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)
103
-
104
- ## Help!
105
-
106
- 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.
107
-
108
- If you're stuck, feel free to open an issue.
96
+ ```
109
97
 
110
- ## How does it work?
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)
111
99
 
112
- 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.
113
101
 
114
102
  ## Prior Art
115
103
 
@@ -143,3 +131,4 @@ $ poetry run pytest
143
131
  - Typography of output
144
132
  - make it work with numba
145
133
  - understand how numpy interop works
134
+ - pre-parse units
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "unit-syntax"
3
- version = "0.2.1"
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)
@@ -1,12 +1,13 @@
1
1
  import ast
2
- from io import StringIO
3
- from typing import Iterator, Optional, Generator
4
2
  from .parser import parse_string, UnitsExpr
5
3
  import pint
6
- from pprint import pprint
7
4
 
8
5
 
9
6
  class UnitExprTransformer(ast.NodeTransformer):
7
+ """
8
+ AST transformer to turn python-with-units into standard python
9
+ """
10
+
10
11
  ureg: pint.UnitRegistry
11
12
 
12
13
  def __init__(self, ureg: pint.UnitRegistry):
@@ -22,11 +23,7 @@ class UnitExprTransformer(ast.NodeTransformer):
22
23
 
23
24
  value = self.generic_visit(node.value)
24
25
  return ast.Call(
25
- func=ast.Attribute(
26
- ast.Name(id="unit_syntax", ctx=ast.Load()),
27
- "Quantity",
28
- ctx=ast.Load(),
29
- ),
26
+ ast.Name(id="_unit_syntax_q", ctx=ast.Load()),
30
27
  args=[value, node.units],
31
28
  keywords=[],
32
29
  )
@@ -38,13 +35,7 @@ def transform(code: str) -> str:
38
35
  """Transform a string of python-with-units into a standard python string"""
39
36
 
40
37
  tree = parse_string(code, mode="file")
41
- ureg = pint.UnitRegistry()
38
+ ureg = pint._DEFAULT_REGISTRY
42
39
  tree_std = UnitExprTransformer(ureg).visit(tree)
43
40
  tree_std = ast.fix_missing_locations(tree_std)
44
41
  return ast.unparse(tree_std)
45
-
46
-
47
- def transform_lines(lines: list[str]) -> list[str]:
48
- """IPython transforms provide a list of strings in the current cell, but to parse correctly we
49
- need to parse them as a single string"""
50
- return transform("".join(lines)).splitlines(keepends=True)
@@ -1,68 +0,0 @@
1
- import pint
2
-
3
- # Use the _DEFAULT_REGISTRY so we can interoperate with other pint-using libraries
4
- ureg = pint._DEFAULT_REGISTRY
5
-
6
-
7
- def Quantity(value, units):
8
- if isinstance(value, ureg.Quantity):
9
- return value.to(units)
10
- else:
11
- return ureg.Quantity(value, units)
12
-
13
-
14
- def enable_ipython(debug_transform=False):
15
- """
16
- debug_transform -- if True, log the input and output of each transform
17
- """
18
- import IPython
19
- from .transform import transform_lines
20
-
21
- ip = IPython.get_ipython()
22
- if not hasattr(ip, "input_transformers_post"):
23
- raise ImportError("Unsupported IPython version, version >=7 is required")
24
-
25
- if debug_transform:
26
- import logging
27
-
28
- logging.basicConfig(level=logging.DEBUG, force=True)
29
-
30
- def transform_lines_dbg(lines):
31
- ret = transform_lines(lines)
32
- logging.debug("unit_syntax: %s -> %s", "\n".join(lines), "\n".join(ret))
33
- return ret
34
-
35
- do_transform = transform_lines_dbg
36
- else:
37
- do_transform = transform_lines
38
- ip.input_transformers_post.append(do_transform)
39
-
40
- # Overide default formatters to use reduced units. This lets us keep using the
41
- # default registry for interop with other pint-using libraries, but gives more
42
- # sensible output in the notebook.
43
- def add_formatter(mime, display_fn_name):
44
- fn = getattr(pint.Quantity, display_fn_name, None)
45
- if fn is None:
46
- return
47
-
48
- def fmt_reduced(q, *args):
49
- return fn(q.to_reduced_units(), *args)
50
-
51
- formatter = ip.display_formatter.formatters[mime]
52
- formatter.for_type(pint.Quantity, fmt_reduced)
53
-
54
- add_formatter("text/markdown", "_repr_markdown_")
55
- add_formatter("text/html", "_repr_html_")
56
- add_formatter("text/plain", "_repr_pretty_")
57
- add_formatter("text/latex", "_repr_latex_")
58
-
59
- # ensure the module is still visible if imported via
60
- # `from unit_syntax import ipython` for some reason
61
- ip.run_cell("import unit_syntax")
62
-
63
- try:
64
- import matplotlib
65
-
66
- pint.setup_matplotlib(True)
67
- except ImportError:
68
- pass
@@ -1,3 +0,0 @@
1
- from .. import enable_ipython
2
-
3
- enable_ipython()