From d116e7f0b965d3287f2c06fbc87477f0536377b9 Mon Sep 17 00:00:00 2001 From: Ryan Senkbeil Date: Mon, 7 Nov 2011 11:38:11 -0600 Subject: [PATCH 001/212] Added new unit definitions for hPa and mb. --- quantities/units/pressure.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/quantities/units/pressure.py b/quantities/units/pressure.py index bc343da..81561c3 100644 --- a/quantities/units/pressure.py +++ b/quantities/units/pressure.py @@ -24,6 +24,11 @@ symbol='Pa', aliases=['pascals'] ) +hPa = hectopascal = UnitQuantity( + 'hectopascal', + 100*Pa, + symbol='hPa', +) kPa = kilopascal = UnitQuantity( 'kilopascal', 1000*Pa, @@ -47,6 +52,11 @@ 100000*pascal, aliases=['bars'] ) +mb = millibar = UnitQuantity( + 'millibar', + 0.001*bar, + symbol='mb', +) kbar = kilobar = UnitQuantity( 'kilobar', 1000*bar, From 724f2bcb00e93b42c0a51a08d8c3e5b4a3a09608 Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Thu, 17 Jan 2013 09:27:05 -0500 Subject: [PATCH 002/212] supports numpy.fabs, fixes #51 --- quantities/dimensionality.py | 1 + quantities/quantity.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index abec291..73735fc 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -309,6 +309,7 @@ def _d_reciprocal(q1, out=None): def _d_copy(q1, out=None): return q1.dimensionality +p_dict[np.fabs] = _d_copy p_dict[np.absolute] = _d_copy p_dict[np.conjugate] = _d_copy p_dict[np.negative] = _d_copy diff --git a/quantities/quantity.py b/quantities/quantity.py index d0b3cfc..a5b4639 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -233,7 +233,7 @@ def __array_prepare__(self, obj, context=None): raise ValueError( """ufunc %r not supported by quantities please file a bug report at https://github.com/python-quantities - """ + """ % uf ) return res From c521f325193bdfb0d389d38dd5866931e4297b56 Mon Sep 17 00:00:00 2001 From: lumbric Date: Tue, 26 Mar 2013 11:51:38 +0100 Subject: [PATCH 003/212] enable subclassing of Quantity see issue #56 for detailed discussion --- quantities/quantity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index a5b4639..47d299d 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -116,12 +116,12 @@ class Quantity(np.ndarray): __array_priority__ = 21 def __new__(cls, data, units='', dtype=None, copy=True): - if isinstance(data, cls): + if isinstance(data, Quantity): if units: data = data.rescale(units) if isinstance(data, unit_registry['UnitQuantity']): return 1*data - return np.array(data, dtype=dtype, copy=copy, subok=True) + return np.array(data, dtype=dtype, copy=copy, subok=True).view(cls) ret = np.array(data, dtype=dtype, copy=copy).view(cls) ret._dimensionality.update(validate_dimensionality(units)) From 56ef09a017a0d5ce66622a9ebab3a7f4a0dc3e2c Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Tue, 16 Apr 2013 09:39:23 -0400 Subject: [PATCH 004/212] fixes squeeze bug introduced in recent numpy release --- quantities/quantity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/quantities/quantity.py b/quantities/quantity.py index a5b4639..bb278dd 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -534,6 +534,14 @@ def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None): copy=False ) + @with_doc(np.ndarray.squeeze) + def squeeze(self, axis=None): + return Quantity( + self.magnitude.squeeze(axis), + self.dimensionality, + copy=False + ) + @with_doc(np.ndarray.mean) def mean(self, axis=None, dtype=None, out=None): return Quantity( From 1a333c96d3e52ee82cd1cec2fc99864da193b45f Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Sun, 2 Jun 2013 10:59:46 +0200 Subject: [PATCH 005/212] initial travis CI support --- .travis.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8a62681 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: python +python: + - 2.6 + - 2.7 + - 3.3 +before_install: + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then + pip install unittest2; + fi + - pip install "numpy==1.7.0" +install: + - python setup.py install +script: + - python setup.py test +notifications: + email: false From c768a4a81ea36db3fa3289ebbcd36ad8d160d5ed Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 15 Aug 2013 14:52:20 +0900 Subject: [PATCH 006/212] Added milli->pico versions of Farads and Siemens and kilo->Mega of ohms. Also added 'Ohm' alias for 'ohm'. --- quantities/units/electromagnetism.py | 56 +++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/quantities/units/electromagnetism.py b/quantities/units/electromagnetism.py index b2f6da5..d632b1c 100644 --- a/quantities/units/electromagnetism.py +++ b/quantities/units/electromagnetism.py @@ -107,17 +107,69 @@ symbol='F', aliases=['farads'] ) -ohm = UnitQuantity( +mF = UnitQuantity( + 'millifarad', + F/1000, + symbol='mF' +) +uF = UnitQuantity( + 'microfarad', + mF/1000, + symbol='uF', + u_symbol='μF' +) +nF = UnitQuantity( + 'nanofarad', + uF/1000, + symbol='nF' +) +pF = UnitQuantity( + 'picofarad', + nF/1000, + symbol='pF' +) +ohm = Ohm = UnitQuantity( 'ohm', V/A, u_symbol='Ω', - aliases=['ohms'] + aliases=['ohms', 'Ohm'] +) +kOhm = UnitQuantity( + 'kiloohm', + ohm*1000, + u_symbol='kΩ' +) +MOhm = UnitQuantity( + 'megaohm', + kOhm*1000, + u_symbol='MΩ' ) S = siemens = UnitQuantity( 'siemens', A/V, symbol='S' ) +mS = siemens = UnitQuantity( + 'millisiemens', + S/1000, + symbol='mS' +) +uS = siemens = UnitQuantity( + 'microsiemens', + mS/1000, + symbol='uS', + u_symbol='μS' +) +nS = siemens = UnitQuantity( + 'nanosiemens', + uS/1000, + symbol='nS' +) +pS = siemens = UnitQuantity( + 'picosiemens', + nS/1000, + symbol='pS' +) Wb = weber = UnitQuantity( 'weber', V*s, From e267cd2d9281a6a010b956e2905c2fa4fa386cc8 Mon Sep 17 00:00:00 2001 From: Tom Close Date: Fri, 16 Aug 2013 19:54:47 +0900 Subject: [PATCH 007/212] added Eclipse project settings to git ignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f05b080..0e10680 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ quantities.egg-info build dist +.project +.pydevproject +.settings From 6151b815bc97612881c88f563678e4bb9fa96037 Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 22 Aug 2013 08:42:26 +0900 Subject: [PATCH 008/212] added aliases to kOhm and MOhm --- quantities/units/electromagnetism.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quantities/units/electromagnetism.py b/quantities/units/electromagnetism.py index d632b1c..38bf5e8 100644 --- a/quantities/units/electromagnetism.py +++ b/quantities/units/electromagnetism.py @@ -137,12 +137,14 @@ kOhm = UnitQuantity( 'kiloohm', ohm*1000, - u_symbol='kΩ' + u_symbol='kΩ', + aliases=['kOhm', 'kohm', 'kiloohms'] ) MOhm = UnitQuantity( 'megaohm', kOhm*1000, - u_symbol='MΩ' + u_symbol='MΩ', + aliases=['MOhm', 'Mohm', 'megaohms'] ) S = siemens = UnitQuantity( 'siemens', From 836772c1fe82eb7dc98b0d127aeecbc6d9ae13de Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 22 Aug 2013 08:49:33 +0900 Subject: [PATCH 009/212] Added k-G liter units, plus British-English spelling aliases for all "litre" units --- quantities/units/volume.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/quantities/units/volume.py b/quantities/units/volume.py index 7737bb6..22ca700 100644 --- a/quantities/units/volume.py +++ b/quantities/units/volume.py @@ -12,11 +12,29 @@ symbol='L', aliases=['l', 'liters', 'litre', 'litres'] ) -mL = milliliter = UnitQuantity( +mL = milliliter = millilitre = UnitQuantity( 'milliliter', liter/1000, symbol='mL', - aliases=['milliliters'] + aliases=['milliliters', 'millilitre', 'millilitres'] +) +kL = kiloliter = kilolitre = UnitQuantity( + 'kiloliter', + liter*1000, + symbol='kL', + aliases=['kiloliters', 'kilolitre', 'kilolitres'] +) +ML = megaliter = megalitre = UnitQuantity( + 'megaliter', + kiloliter*1000, + symbol='ML', + aliases=['megaliters', 'megalitre', 'megalitres'] +) +GL = gigaliter = gigalitre = UnitQuantity( + 'gigaliter', + megaliter*1000, + symbol='GL', + aliases=['gigaliters', 'gigalitre', 'gigalitres'] ) cc = cubic_centimeter = milliliter = UnitQuantity( 'cubic_centimeter', From fd8cfdfabbb0c7291a8dc9e18862383774b53601 Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 22 Aug 2013 11:47:13 +0900 Subject: [PATCH 010/212] added ml, kl, Ml and Gl aliases to litre units --- quantities/units/volume.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quantities/units/volume.py b/quantities/units/volume.py index 22ca700..34adf1a 100644 --- a/quantities/units/volume.py +++ b/quantities/units/volume.py @@ -16,25 +16,25 @@ 'milliliter', liter/1000, symbol='mL', - aliases=['milliliters', 'millilitre', 'millilitres'] + aliases=['ml', 'milliliters', 'millilitre', 'millilitres'] ) kL = kiloliter = kilolitre = UnitQuantity( 'kiloliter', liter*1000, symbol='kL', - aliases=['kiloliters', 'kilolitre', 'kilolitres'] + aliases=['kl', 'kiloliters', 'kilolitre', 'kilolitres'] ) ML = megaliter = megalitre = UnitQuantity( 'megaliter', kiloliter*1000, symbol='ML', - aliases=['megaliters', 'megalitre', 'megalitres'] + aliases=['ml', 'megaliters', 'megalitre', 'megalitres'] ) GL = gigaliter = gigalitre = UnitQuantity( 'gigaliter', megaliter*1000, symbol='GL', - aliases=['gigaliters', 'gigalitre', 'gigalitres'] + aliases=['Gl', 'gigaliters', 'gigalitre', 'gigalitres'] ) cc = cubic_centimeter = milliliter = UnitQuantity( 'cubic_centimeter', From 911119b2e2758a019ffe8899292f9ad3981aea1d Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 22 Aug 2013 11:48:59 +0900 Subject: [PATCH 011/212] Added molar concentrations --- quantities/units/concentration.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 quantities/units/concentration.py diff --git a/quantities/units/concentration.py b/quantities/units/concentration.py new file mode 100644 index 0000000..1a1fd42 --- /dev/null +++ b/quantities/units/concentration.py @@ -0,0 +1,28 @@ +""" +""" +from __future__ import absolute_import + +from ..unitquantity import UnitQuantity +from .substance import mol +from .volume import L + +M = molar = UnitQuantity( + 'molar', + mol / L, + symbol='M', + aliases=['molar', 'Molar'] +) + +mM = millimolar = UnitQuantity( + 'millimolar', + molar / 1000, + symbol='mM', + aliases=['millimolar'] +) + +uM = micromolar = UnitQuantity( + 'micromolar', + mM / 1000, + symbol='M', + aliases=['mircomolar'] +) \ No newline at end of file From 82c8fc8b74fa8692c898cb659964f432958fde1c Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 22 Aug 2013 14:37:08 +0900 Subject: [PATCH 012/212] corrected volume and concentration aliases --- quantities/units/concentration.py | 2 +- quantities/units/volume.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quantities/units/concentration.py b/quantities/units/concentration.py index 1a1fd42..a088f25 100644 --- a/quantities/units/concentration.py +++ b/quantities/units/concentration.py @@ -23,6 +23,6 @@ uM = micromolar = UnitQuantity( 'micromolar', mM / 1000, - symbol='M', + symbol='uM', aliases=['mircomolar'] ) \ No newline at end of file diff --git a/quantities/units/volume.py b/quantities/units/volume.py index 34adf1a..ea6eeb4 100644 --- a/quantities/units/volume.py +++ b/quantities/units/volume.py @@ -28,7 +28,7 @@ 'megaliter', kiloliter*1000, symbol='ML', - aliases=['ml', 'megaliters', 'megalitre', 'megalitres'] + aliases=['Ml', 'megaliters', 'megalitre', 'megalitres'] ) GL = gigaliter = gigalitre = UnitQuantity( 'gigaliter', From b8948514a5f05c01c022a84e0bd5cdfc3fa45a9b Mon Sep 17 00:00:00 2001 From: Tom Close Date: Thu, 22 Aug 2013 14:37:29 +0900 Subject: [PATCH 013/212] Added concentration module to units --- quantities/units/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quantities/units/__init__.py b/quantities/units/__init__.py index 48a34fe..39fa644 100644 --- a/quantities/units/__init__.py +++ b/quantities/units/__init__.py @@ -18,6 +18,9 @@ from . import compound from .compound import * +from . import concentration +from .concentration import * + from . import dimensionless from .dimensionless import * From 1e4f4826fe13130ca8f0d78d343de715c86baf52 Mon Sep 17 00:00:00 2001 From: Tom Close Date: Tue, 27 Aug 2013 09:13:13 +0900 Subject: [PATCH 014/212] cleaned up units/concentration.py by adding trailing newline and unicode symbol to micromolar, and removing redundant aliases --- quantities/units/concentration.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/quantities/units/concentration.py b/quantities/units/concentration.py index a088f25..26b220f 100644 --- a/quantities/units/concentration.py +++ b/quantities/units/concentration.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ """ from __future__ import absolute_import @@ -10,19 +11,18 @@ 'molar', mol / L, symbol='M', - aliases=['molar', 'Molar'] + aliases=['Molar'] ) mM = millimolar = UnitQuantity( 'millimolar', molar / 1000, - symbol='mM', - aliases=['millimolar'] + symbol='mM' ) uM = micromolar = UnitQuantity( 'micromolar', mM / 1000, symbol='uM', - aliases=['mircomolar'] -) \ No newline at end of file + u_symbol='µM' +) From 814146bf5bcb5905eb145abd48419c51dcbc7800 Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Sun, 23 Mar 2014 17:32:54 -0400 Subject: [PATCH 015/212] Calculate mean w/ uncertainties --- quantities/uncertainquantity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index 9294677..c6d1384 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -207,6 +207,14 @@ def sum(self, axis=None, dtype=None, out=None): copy=False ) + @with_doc(np.ndarray.mean) + def mean(self, axis=None, dtype=None, out=None): + return UncertainQuantity( + self.magnitude.mean(axis, dtype, out), + self.dimensionality, + ((1.0/np.size(self,axis))**2 * np.sum(self.uncertainty.magnitude**2, axis))**0.5, + copy=False) + def __getstate__(self): """ Return the internal state of the quantity, for pickling From d7b8642d322360ed2a98d2c261ce3f09304822f3 Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Sun, 23 Mar 2014 20:41:48 -0400 Subject: [PATCH 016/212] nanmean for uncertain quantities --- quantities/uncertainquantity.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index c6d1384..f36eff5 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -215,6 +215,15 @@ def mean(self, axis=None, dtype=None, out=None): ((1.0/np.size(self,axis))**2 * np.sum(self.uncertainty.magnitude**2, axis))**0.5, copy=False) + @with_doc(np.ndarray.mean) + def nanmean(self, axis=None, dtype=None, out=None): + size = np.sum(~np.isnan(self),axis) + return UncertainQuantity( + np.nanmean(self.magnitude, axis, dtype, out), + self.dimensionality, + ((1.0/size)**2 * np.nansum(np.nan_to_num(self.uncertainty.magnitude)**2, axis))**0.5, + copy=False) + def __getstate__(self): """ Return the internal state of the quantity, for pickling From e5a7565b443e72cf191c6b93d23db7f74ef1bcfe Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Sun, 23 Mar 2014 20:42:14 -0400 Subject: [PATCH 017/212] nanstd for quantities --- quantities/quantity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/quantities/quantity.py b/quantities/quantity.py index c2b7875..1d5c063 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -565,6 +565,14 @@ def std(self, axis=None, dtype=None, out=None, ddof=0): copy=False ) + @with_doc(np.nanstd) + def nanstd(self, axis=None, dtype=None, out=None, ddof=0): + return Quantity( + np.nanstd(self.magnitude, axis, dtype, out, ddof), + self._dimensionality, + copy=False + ) + @with_doc(np.ndarray.prod) def prod(self, axis=None, dtype=None, out=None): if axis == None: From 9d07f3dc5a5e996631b8edd51ba8c8623c25ffd2 Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Sun, 23 Mar 2014 21:12:12 -0400 Subject: [PATCH 018/212] unit tests for mean, nanmean, and nanstd --- quantities/tests/test_methods.py | 17 +++++++++++++++++ quantities/tests/test_uncertainty.py | 14 ++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index abbf487..d45e24e 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -146,12 +146,29 @@ def test_cumsum(self): def test_mean(self): self.assertQuantityEqual(self.q.mean(), 2.5 * pq.m) + #self.q = [[1, 2], [3, 4]] * pq.m + def test_nanmean(self): + import numpy as np + q = [[1,2], [3,4], [np.nan,np.nan]] * pq.m + self.assertQuantityEqual(q.nanmean(), self.q.mean()) + def test_var(self): self.assertQuantityEqual(self.q.var(), 1.25*pq.m**2) def test_std(self): self.assertQuantityEqual(self.q.std(), 1.11803*pq.m, delta=1e-5) + def test_nanstd(self): + import numpy as np + q0 = [[1,2], [3,4]] * pq.m + q1 = [[1,2], [3,4], [np.nan,np.nan]] * pq.m + self.assertQuantityEqual(q0.std(), q1.nanstd()) + + def test_nanmean(self): + import numpy as np + q = [[1,2], [3,4], [np.nan,np.nan]] * pq.m + self.assertQuantityEqual(q.nanstd(), self.q.std()) + def test_prod(self): self.assertQuantityEqual(self.q.prod(), 24 * pq.m**4) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index 94cec2a..9b0dce7 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -60,3 +60,17 @@ def test_uncertainquantity_divide(self): self.assertQuantityEqual((a/2).uncertainty, [0.05, 0.1 ]*pq.m) self.assertQuantityEqual(1/a, [1., 0.5]/pq.m) self.assertQuantityEqual((1/a).uncertainty, [0.1, 0.05]/pq.m) + + def test_uncertaintity_mean(self): + import numpy as np + a = UncertainQuantity([1,2], 'm', [.1,.2]) + mean0 = np.sum(a)/np.size(a) # calculated traditionally + mean1 = a.mean() # calculated using this code + self.assertQuantityEqual(mean0, mean1) + + def test_uncertaintity_nanmean(self): + import numpy as np + a = UncertainQuantity([1,2], 'm', [.1,.2]) + b = UncertainQuantity([1,2,np.nan], 'm', [.1,.2,np.nan]) + self.assertQuantityEqual(a.mean(),b.nanmean()) + From d3fb8da1dc89e6114edf8ccb40f94a8091e95c05 Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Mon, 24 Mar 2014 00:04:11 -0400 Subject: [PATCH 019/212] x.sqrt() now works (see #74) --- quantities/tests/test_uncertainty.py | 4 ++++ quantities/uncertainquantity.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index 9b0dce7..8f85f91 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -74,3 +74,7 @@ def test_uncertaintity_nanmean(self): b = UncertainQuantity([1,2,np.nan], 'm', [.1,.2,np.nan]) self.assertQuantityEqual(a.mean(),b.nanmean()) + + def test_uncertainty_sqrt(self): + a = UncertainQuantity([1,2], 'm', [.1,.2]) + self.assertQuantityEqual(a**0.5, a.sqrt()) diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index f36eff5..e589425 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -224,6 +224,10 @@ def nanmean(self, axis=None, dtype=None, out=None): ((1.0/size)**2 * np.nansum(np.nan_to_num(self.uncertainty.magnitude)**2, axis))**0.5, copy=False) + @with_doc(np.sqrt) + def sqrt(self, out=None): + return self**0.5 + def __getstate__(self): """ Return the internal state of the quantity, for pickling From 46c2efd97634a2e89a7c4a8e2580db58ce6e46da Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Mon, 24 Mar 2014 00:25:22 -0400 Subject: [PATCH 020/212] subtraction of same element handled correctly --- quantities/tests/test_uncertainty.py | 7 +++++++ quantities/uncertainquantity.py | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index 94cec2a..c4980bb 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -60,3 +60,10 @@ def test_uncertainquantity_divide(self): self.assertQuantityEqual((a/2).uncertainty, [0.05, 0.1 ]*pq.m) self.assertQuantityEqual(1/a, [1., 0.5]/pq.m) self.assertQuantityEqual((1/a).uncertainty, [0.1, 0.05]/pq.m) + + def test_uncertainquantity_subtract(self): + import numpy as np + a = UncertainQuantity(1, 'm', 1) + b = a.copy() # different object + self.assertQuantityEqual(a-a, UncertainQuantity(0, 'm', 0)) + self.assertQuantityEqual(a-b, UncertainQuantity(0, 'm', np.sqrt(2))) diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index 9294677..db84e9a 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -102,7 +102,10 @@ def __radd__(self, other): @scale_other_units def __sub__(self, other): res = super(UncertainQuantity, self).__sub__(other) - u = (self.uncertainty**2+other.uncertainty**2)**0.5 + if self is not other: + u = (self.uncertainty**2+other.uncertainty**2)**0.5 + else: + u = self.uncertainty*0 return UncertainQuantity(res, uncertainty=u, copy=False) @with_doc(Quantity.__rsub__, use_header=False) From 7207b5db8b58f8b6b9e2dfbf23f808ebb1acd076 Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Mon, 24 Mar 2014 08:45:02 -0400 Subject: [PATCH 021/212] fixed nanmean test (2nd test was typod) --- quantities/quantity.py | 1 + quantities/tests/test_methods.py | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 1d5c063..915cc61 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -345,6 +345,7 @@ def __str__(self): @with_doc(np.ndarray.__getitem__) def __getitem__(self, key): + print "here" ret = super(Quantity, self).__getitem__(key) if isinstance(ret, Quantity): return ret diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index d45e24e..2bf1a54 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -146,7 +146,6 @@ def test_cumsum(self): def test_mean(self): self.assertQuantityEqual(self.q.mean(), 2.5 * pq.m) - #self.q = [[1, 2], [3, 4]] * pq.m def test_nanmean(self): import numpy as np q = [[1,2], [3,4], [np.nan,np.nan]] * pq.m @@ -164,11 +163,6 @@ def test_nanstd(self): q1 = [[1,2], [3,4], [np.nan,np.nan]] * pq.m self.assertQuantityEqual(q0.std(), q1.nanstd()) - def test_nanmean(self): - import numpy as np - q = [[1,2], [3,4], [np.nan,np.nan]] * pq.m - self.assertQuantityEqual(q.nanstd(), self.q.std()) - def test_prod(self): self.assertQuantityEqual(self.q.prod(), 24 * pq.m**4) From b39b02528ee7613f309c516556bb97990c779b1a Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Mon, 24 Mar 2014 08:45:44 -0400 Subject: [PATCH 022/212] removed print statement --- quantities/quantity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 915cc61..1d5c063 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -345,7 +345,6 @@ def __str__(self): @with_doc(np.ndarray.__getitem__) def __getitem__(self, key): - print "here" ret = super(Quantity, self).__getitem__(key) if isinstance(ret, Quantity): return ret From de880b7946d2872141614e86c9111785aefa1951 Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Mon, 24 Mar 2014 11:18:32 -0400 Subject: [PATCH 023/212] nanmean for simple quantities, not just uncertain quantities --- quantities/quantity.py | 8 ++++++++ quantities/uncertainquantity.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 1d5c063..a5e1eaf 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -549,6 +549,14 @@ def mean(self, axis=None, dtype=None, out=None): self.dimensionality, copy=False) + @with_doc(np.nanmean) + def nanmean(self, axis=None, dtype=None, out=None): + import numpy as np + return Quantity( + np.nanmean(self.magnitude, axis, dtype, out), + self.dimensionality, + copy=False) + @with_doc(np.ndarray.var) def var(self, axis=None, dtype=None, out=None, ddof=0): return Quantity( diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index aef5493..141108f 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -218,7 +218,7 @@ def mean(self, axis=None, dtype=None, out=None): ((1.0/np.size(self,axis))**2 * np.sum(self.uncertainty.magnitude**2, axis))**0.5, copy=False) - @with_doc(np.ndarray.mean) + @with_doc(np.nanmean) def nanmean(self, axis=None, dtype=None, out=None): size = np.sum(~np.isnan(self),axis) return UncertainQuantity( From 7bd93c790addf39a8d4b37dabfa4b209b96c861f Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Mon, 24 Mar 2014 13:24:24 -0400 Subject: [PATCH 024/212] nansum for quantities and uncertain quantities --- quantities/quantity.py | 9 +++++++++ quantities/tests/test_methods.py | 6 ++++++ quantities/tests/test_uncertainty.py | 10 +++++++--- quantities/uncertainquantity.py | 10 ++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index a5e1eaf..9dbd034 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -428,6 +428,15 @@ def sum(self, axis=None, dtype=None, out=None): copy=False ) + @with_doc(np.nansum) + def nansum(self, axis=None, dtype=None, out=None): + import numpy as np + return Quantity( + np.nansum(self.magnitude, axis, dtype, out), + self.dimensionality, + copy=False + ) + @with_doc(np.ndarray.fill) def fill(self, value): self.magnitude.fill(value) diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index 2bf1a54..424d84f 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -17,6 +17,12 @@ def test_sum(self): self.assertQuantityEqual(self.q.sum(0), [4, 6]*pq.m) self.assertQuantityEqual(self.q.sum(1), [3, 7]*pq.m) + def test_nansum(self): + import numpy as np + qnan = [[1,2], [3,4], [np.nan,np.nan]] * pq.m + self.assertQuantityEqual(qnan.nansum(), 10*pq.m ) + self.assertQuantityEqual(qnan.nansum(0), [4,6]*pq.m ) + def test_fill(self): self.q.fill(6 * pq.ft) self.assertQuantityEqual(self.q, [[6, 6], [6, 6]] * pq.ft) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index 75c40ff..e345eb1 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -3,6 +3,7 @@ from .. import units as pq from ..uncertainquantity import UncertainQuantity from .common import TestCase +import numpy as np class TestUncertainty(TestCase): @@ -62,20 +63,17 @@ def test_uncertainquantity_divide(self): self.assertQuantityEqual((1/a).uncertainty, [0.1, 0.05]/pq.m) def test_uncertaintity_mean(self): - import numpy as np a = UncertainQuantity([1,2], 'm', [.1,.2]) mean0 = np.sum(a)/np.size(a) # calculated traditionally mean1 = a.mean() # calculated using this code self.assertQuantityEqual(mean0, mean1) def test_uncertaintity_nanmean(self): - import numpy as np a = UncertainQuantity([1,2], 'm', [.1,.2]) b = UncertainQuantity([1,2,np.nan], 'm', [.1,.2,np.nan]) self.assertQuantityEqual(a.mean(),b.nanmean()) def test_uncertainquantity_subtract(self): - import numpy as np a = UncertainQuantity(1, 'm', 1) b = a.copy() # different object self.assertQuantityEqual(a-a, UncertainQuantity(0, 'm', 0)) @@ -84,3 +82,9 @@ def test_uncertainquantity_subtract(self): def test_uncertainty_sqrt(self): a = UncertainQuantity([1,2], 'm', [.1,.2]) self.assertQuantityEqual(a**0.5, a.sqrt()) + + def test_uncertainty_nansum(self): + uq = UncertainQuantity([1,2], 'm', [1,1]) + uq_nan = UncertainQuantity([1,2,np.nan], 'm', [1,1,np.nan]) + self.assertQuantityEqual(np.sum(uq), np.nansum(uq)) + self.assertQuantityEqual(np.sum(uq), np.nansum(uq_nan)) diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index 141108f..9d3bcc1 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -210,6 +210,16 @@ def sum(self, axis=None, dtype=None, out=None): copy=False ) + @with_doc(np.nansum) + def nansum(self, axis=None, dtype=None, out=None): + import numpy as np + return UncertainQuantity( + np.nansum(self.magnitude, axis, dtype, out), + self.dimensionality, + (np.nansum(self.uncertainty.magnitude**2, axis))**0.5, + copy=False + ) + @with_doc(np.ndarray.mean) def mean(self, axis=None, dtype=None, out=None): return UncertainQuantity( From 73c5325c446163be973082bc15271d60898a78fa Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Mon, 24 Mar 2014 14:19:03 -0400 Subject: [PATCH 025/212] min,max,argmin,argmax,nanmin,nanmax,nanargmin,nanargmax --- quantities/quantity.py | 28 ++++++++++++++++++++++ quantities/tests/test_methods.py | 18 +++++++++++++- quantities/tests/test_uncertainty.py | 28 ++++++++++++++++++++++ quantities/uncertainquantity.py | 36 ++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 9dbd034..57a4e63 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -486,6 +486,14 @@ def max(self, axis=None, out=None): copy=False ) + @with_doc(np.nanmax) + def nanmax(self, axis=None, out=None): + return Quantity( + np.nanmax(self.magnitude), + self.dimensionality, + copy=False + ) + @with_doc(np.ndarray.min) def min(self, axis=None, out=None): return Quantity( @@ -494,10 +502,30 @@ def min(self, axis=None, out=None): copy=False ) + @with_doc(np.nanmin) + def nanmin(self, axis=None, out=None): + return Quantity( + np.nanmin(self.magnitude), + self.dimensionality, + copy=False + ) + @with_doc(np.ndarray.argmin) def argmin(self,axis=None, out=None): return self.magnitude.argmin() + @with_doc(np.ndarray.argmax) + def argmax(self,axis=None, out=None): + return self.magnitude.argmax() + + @with_doc(np.nanargmin) + def nanargmin(self,axis=None, out=None): + return np.nanargmin(self.magnitude) + + @with_doc(np.nanargmax) + def nanargmax(self,axis=None, out=None): + return np.nanargmax(self.magnitude) + @with_doc(np.ndarray.ptp) def ptp(self, axis=None, out=None): return Quantity( diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index 424d84f..8abc075 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -2,7 +2,7 @@ from .. import units as pq from .common import TestCase - +import numpy as np class TestQuantityMethods(TestCase): @@ -109,15 +109,31 @@ def test_nonzero(self): def test_max(self): self.assertQuantityEqual(self.q.max(), 4*pq.m) + def test_nanmax(self): + q = np.append(self.q, np.nan) * self.q.units + self.assertQuantityEqual(q.nanmax(), 4*pq.m) + def test_argmax(self): self.assertEqual(self.q.argmax(), 3) + def test_nanargmax(self): + q = np.append(self.q, np.nan) * self.q.units + self.assertEqual(self.q.nanargmax(), 3) + def test_min(self): self.assertEqual(self.q.min(), 1 * pq.m) + def test_nanmin(self): + q = np.append(self.q, np.nan) * self.q.units + self.assertQuantityEqual(q.nanmin(), 1*pq.m) + def test_argmin(self): self.assertEqual(self.q.argmin(), 0) + def test_nanargmax(self): + q = np.append(self.q, np.nan) * self.q.units + self.assertEqual(self.q.nanargmin(), 0) + def test_ptp(self): self.assertQuantityEqual(self.q.ptp(), 3 * pq.m) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index e345eb1..ca80657 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -88,3 +88,31 @@ def test_uncertainty_nansum(self): uq_nan = UncertainQuantity([1,2,np.nan], 'm', [1,1,np.nan]) self.assertQuantityEqual(np.sum(uq), np.nansum(uq)) self.assertQuantityEqual(np.sum(uq), np.nansum(uq_nan)) + + def test_uncertainty_minmax_nan_arg(self): + q = [1,2] * pq.m + # quantity + self.assertQuantityEqual(q.min(), 1*pq.m) # min + self.assertQuantityEqual(q.max(), 2*pq.m) # max + self.assertQuantityEqual(q.argmin(), 0) # argmin + self.assertQuantityEqual(q.argmax(), 1) # argmax + # uncertain quantity + uq = UncertainQuantity([1,2], pq.m, [1,1]) + self.assertQuantityEqual(uq.min(), 1*pq.m) # min + self.assertQuantityEqual(uq.max(), 2*pq.m) # max + self.assertQuantityEqual(uq.argmin(), 0) # argmin + self.assertQuantityEqual(uq.argmax(), 1) # argmax + # now repeat the above with NaNs + nanq = [1,2, np.nan] * pq.m + nanuq = UncertainQuantity([1,2, np.nan], pq.m, [1,1,np.nan]) + self.assertQuantityEqual(nanq.nanmin(), 1*pq.m) # min + self.assertQuantityEqual(nanq.nanmax(), 2*pq.m) # max + self.assertQuantityEqual(nanq.nanargmin(), 0) # argmin + self.assertQuantityEqual(nanq.nanargmax(), 1) # argmax + self.assertQuantityEqual(nanuq.nanmin(), 1*pq.m) # min + self.assertQuantityEqual(nanuq.nanmax(), 2*pq.m) # max + self.assertQuantityEqual(nanuq.nanargmin(), 0) # argmin + self.assertQuantityEqual(nanuq.nanargmax(), 1) # argmax + + + diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index 9d3bcc1..b5a3197 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -241,6 +241,42 @@ def nanmean(self, axis=None, dtype=None, out=None): def sqrt(self, out=None): return self**0.5 + @with_doc(np.ndarray.max) + def max(self, axis=None, out=None): + idx = np.argmax(self.magnitude) + return self[idx] + + @with_doc(np.nanmax) + def nanmax(self, axis=None, out=None): + idx = np.nanargmax(self.magnitude) + return self[idx] + + @with_doc(np.ndarray.min) + def min(self, axis=None, out=None): + idx = np.argmin(self.magnitude) + return self[idx] + + @with_doc(np.nanmin) + def nanmin(self, axis=None, out=None): + idx = np.nanargmin(self.magnitude) + return self[idx] + + @with_doc(np.ndarray.argmin) + def argmin(self,axis=None, out=None): + return self.magnitude.argmin() + + @with_doc(np.ndarray.argmax) + def argmax(self,axis=None, out=None): + return self.magnitude.argmax() + + @with_doc(np.nanargmin) + def nanargmin(self,axis=None, out=None): + return np.nanargmin(self.magnitude) + + @with_doc(np.nanargmax) + def nanargmax(self,axis=None, out=None): + return np.nanargmax(self.magnitude) + def __getstate__(self): """ Return the internal state of the quantity, for pickling From 6b4d1692530a7e04b79f1a20f5d78b093221012e Mon Sep 17 00:00:00 2001 From: "Kenneth D. Mankoff" Date: Mon, 24 Mar 2014 14:34:33 -0400 Subject: [PATCH 026/212] previous commit (min,max,nan..) works w/ 2D arrays --- quantities/tests/test_uncertainty.py | 27 ++++++++++++++------------- quantities/uncertainquantity.py | 8 ++++---- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index ca80657..6ffa3c4 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -90,29 +90,30 @@ def test_uncertainty_nansum(self): self.assertQuantityEqual(np.sum(uq), np.nansum(uq_nan)) def test_uncertainty_minmax_nan_arg(self): - q = [1,2] * pq.m - # quantity + q = [[1, 2], [3, 4]] * pq.m # quantity self.assertQuantityEqual(q.min(), 1*pq.m) # min - self.assertQuantityEqual(q.max(), 2*pq.m) # max + self.assertQuantityEqual(q.max(), 4*pq.m) # max self.assertQuantityEqual(q.argmin(), 0) # argmin - self.assertQuantityEqual(q.argmax(), 1) # argmax + self.assertQuantityEqual(q.argmax(), 3) # argmax # uncertain quantity - uq = UncertainQuantity([1,2], pq.m, [1,1]) + uq = UncertainQuantity([[1,2],[3,4]], pq.m, [[1,1],[1,1]]) self.assertQuantityEqual(uq.min(), 1*pq.m) # min - self.assertQuantityEqual(uq.max(), 2*pq.m) # max + self.assertQuantityEqual(uq.max(), 4*pq.m) # max self.assertQuantityEqual(uq.argmin(), 0) # argmin - self.assertQuantityEqual(uq.argmax(), 1) # argmax + self.assertQuantityEqual(uq.argmax(), 3) # argmax # now repeat the above with NaNs - nanq = [1,2, np.nan] * pq.m - nanuq = UncertainQuantity([1,2, np.nan], pq.m, [1,1,np.nan]) + nanq = [[1, 2], [3, 4], [np.nan,np.nan]] * pq.m # quantity + nanuq = UncertainQuantity([[1,2],[3,4],[np.nan,np.nan]], + pq.m, + [[1,1],[1,1],[np.nan,np.nan]]) self.assertQuantityEqual(nanq.nanmin(), 1*pq.m) # min - self.assertQuantityEqual(nanq.nanmax(), 2*pq.m) # max + self.assertQuantityEqual(nanq.nanmax(), 4*pq.m) # max self.assertQuantityEqual(nanq.nanargmin(), 0) # argmin - self.assertQuantityEqual(nanq.nanargmax(), 1) # argmax + self.assertQuantityEqual(nanq.nanargmax(), 3) # argmax self.assertQuantityEqual(nanuq.nanmin(), 1*pq.m) # min - self.assertQuantityEqual(nanuq.nanmax(), 2*pq.m) # max + self.assertQuantityEqual(nanuq.nanmax(), 4*pq.m) # max self.assertQuantityEqual(nanuq.nanargmin(), 0) # argmin - self.assertQuantityEqual(nanuq.nanargmax(), 1) # argmax + self.assertQuantityEqual(nanuq.nanargmax(), 3) # argmax diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index b5a3197..2c08232 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -243,22 +243,22 @@ def sqrt(self, out=None): @with_doc(np.ndarray.max) def max(self, axis=None, out=None): - idx = np.argmax(self.magnitude) + idx = np.unravel_index(np.argmax(self.magnitude), self.shape) return self[idx] @with_doc(np.nanmax) def nanmax(self, axis=None, out=None): - idx = np.nanargmax(self.magnitude) + idx = np.unravel_index(np.nanargmax(self.magnitude), self.shape) return self[idx] @with_doc(np.ndarray.min) def min(self, axis=None, out=None): - idx = np.argmin(self.magnitude) + idx = np.unravel_index(np.argmin(self.magnitude), self.shape) return self[idx] @with_doc(np.nanmin) def nanmin(self, axis=None, out=None): - idx = np.nanargmin(self.magnitude) + idx = np.unravel_index(np.nanargmin(self.magnitude), self.shape) return self[idx] @with_doc(np.ndarray.argmin) From 6a54c13d87977c9c900a6411e5526424ccd7e524 Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Thu, 17 Jul 2014 16:35:07 -0400 Subject: [PATCH 027/212] starting a conda recipe --- conda.recipe/build.sh | 1 + conda.recipe/meta.yaml | 27 ++++++++ conda.recipe/run_test.py | 11 +++ conda.recipe/setup.py | 140 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100755 conda.recipe/build.sh create mode 100644 conda.recipe/meta.yaml create mode 100644 conda.recipe/run_test.py create mode 100755 conda.recipe/setup.py diff --git a/conda.recipe/build.sh b/conda.recipe/build.sh new file mode 100755 index 0000000..ee23756 --- /dev/null +++ b/conda.recipe/build.sh @@ -0,0 +1 @@ +$PYTHON setup.py install \ No newline at end of file diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml new file mode 100644 index 0000000..242fb4c --- /dev/null +++ b/conda.recipe/meta.yaml @@ -0,0 +1,27 @@ +package: + name: quantities + version: master + +source: + git_url: https://github.com/python-quantities/python-quantities.git + git_tag: master + +build: + number: 0 + +requirements: + build: + - python + run: + - python + - numpy + +test: + imports: + - quantities + +about: + license: BSD + home: http://pythonhosted.org//quantities/ + summary: Physical quantities with units, based upon Numpy + diff --git a/conda.recipe/run_test.py b/conda.recipe/run_test.py new file mode 100644 index 0000000..e6536d8 --- /dev/null +++ b/conda.recipe/run_test.py @@ -0,0 +1,11 @@ +import os +import sys + + +if sys.version.startswith('2.6') or sys.version.startswith('3.1'): + import unittest2 as unittest +else: + import unittest + +suite = unittest.TestLoader().discover('..') +unittest.TextTestRunner(verbosity=1).run(suite) diff --git a/conda.recipe/setup.py b/conda.recipe/setup.py new file mode 100755 index 0000000..97165f5 --- /dev/null +++ b/conda.recipe/setup.py @@ -0,0 +1,140 @@ +from distutils.cmd import Command +from distutils.core import setup +from distutils.command.sdist import sdist as _sdist +from distutils.command.build import build as _build +import os + + +class data(Command): + + description = "Convert the NIST databas of constants" + + user_options = [] + + boolean_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + with open('quantities/constants/NIST_codata.txt') as f: + data = f.read() + data = data.split('\n')[10:-1] + + with open('quantities/constants/_codata.py', 'w') as f: + f.write('# THIS FILE IS AUTOMATICALLY GENERATED\n') + f.write('# ANY CHANGES MADE HERE WILL BE LOST\n\n') + f.write('physical_constants = {}\n\n') + for line in data: + name = line[:55].rstrip().replace('mag.','magnetic') + name = name.replace('mom.', 'moment') + val = line[55:77].replace(' ','').replace('...','') + prec = line[77:99].replace(' ','').replace('(exact)', '0') + unit = line[99:].rstrip().replace(' ', '*').replace('^', '**') + d = "{'value': %s, 'precision': %s, 'units': '%s'}" \ + %(val, prec, unit) + f.write("physical_constants['%s'] = %s\n"%(name, d)) + + +class sdist(_sdist): + + def run(self): + self.run_command('data') + _sdist.run(self) + + +class build(_build): + + def run(self): + self.run_command('data') + _build.run(self) + + +class test(Command): + + """Run the test suite.""" + + description = "Run the test suite" + + user_options = [('verbosity=', 'V', 'set test report verbosity')] + + def initialize_options(self): + self.verbosity = 0 + + def finalize_options(self): + try: + self.verbosity = int(self.verbosity) + except ValueError: + raise ValueError('verbosity must be an integer.') + + def run(self): + import sys + if sys.version.startswith('2.6') or sys.version.startswith('3.1'): + import unittest2 as unittest + else: + import unittest + suite = unittest.TestLoader().discover('..') + unittest.TextTestRunner(verbosity=self.verbosity+1).run(suite) + + +packages = [] +for dirpath, dirnames, filenames in os.walk('quantities'): + if '__init__.py' in filenames: + packages.append('.'.join(dirpath.split(os.sep))) + else: + del(dirnames[:]) + +with open('../quantities/version.py') as f: + for line in f: + if line.startswith('__version__'): + exec(line) + +setup( + author = 'Darren Dale', + author_email = 'dsdale24@gmail.com', +# classifiers = """Development Status :: 4 - Beta +# Environment :: Console +# Intended Audience :: Developers +# Intended Audience :: Education +# Intended Audience :: End Users/Desktop +# Intended Audience :: Science/Research +# License :: OSI Approved :: BSD License +# Operating System :: OS Independent +# Programming Language :: Python +# Topic :: Education +# Topic :: Scientific/Engineering +# """, + cmdclass = { + 'build': build, + 'data': data, + 'sdist': sdist, + 'test': test, + }, + description = "Support for physical quantities with units, based on numpy", + download_url = "http://pypi.python.org/pypi/quantities", + keywords = ['quantities', 'units', 'physical', 'constants'], + license = 'BSD', + long_description = """Quantities is designed to handle arithmetic and + conversions of physical quantities, which have a magnitude, dimensionality + specified by various units, and possibly an uncertainty. See the tutorial_ + for examples. Quantities builds on the popular numpy library and is + designed to work with numpy ufuncs, many of which are already + supported. Quantities is actively developed, and while the current features + and API are stable, test coverage is incomplete so the package is not + suggested for mission-critical applications. + + .. _tutorial: http://packages.python.org/quantities/user/tutorial.html + """, + name = 'quantities', + packages = packages, + platforms = 'Any', + requires = [ + 'python (>=2.6.0)', + 'numpy (>=1.4.0)', + ], + url = 'http://packages.python.org/quantities', + version = __version__, +) From ad24aa3e14dd9d2f8c8cc51a26b97daf68705de6 Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Thu, 17 Jul 2014 16:59:22 -0400 Subject: [PATCH 028/212] fix unit test support for conda --- conda.recipe/run_test.py | 2 +- quantities/__init__.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/conda.recipe/run_test.py b/conda.recipe/run_test.py index e6536d8..60805e8 100644 --- a/conda.recipe/run_test.py +++ b/conda.recipe/run_test.py @@ -7,5 +7,5 @@ else: import unittest -suite = unittest.TestLoader().discover('..') +suite = unittest.TestLoader().discover('quantities') unittest.TextTestRunner(verbosity=1).run(suite) diff --git a/quantities/__init__.py b/quantities/__init__.py index a8ea5e3..f807766 100644 --- a/quantities/__init__.py +++ b/quantities/__init__.py @@ -285,3 +285,9 @@ from . import constants from .umath import * + +def test(verbosity=1): + import unittest + suite = unittest.TestLoader().discover('quantities') + unittest.TextTestRunner(verbosity=verbosity).run(suite) + From 60313808eea45b638a7b7ce0abbdba0da73ff8e4 Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 23 Jul 2014 14:03:07 -0400 Subject: [PATCH 029/212] adds conda recipe for windows --- conda.recipe/bld.bat | 2 + conda.recipe/setup.py | 140 ------------------------------------------ 2 files changed, 2 insertions(+), 140 deletions(-) create mode 100644 conda.recipe/bld.bat delete mode 100755 conda.recipe/setup.py diff --git a/conda.recipe/bld.bat b/conda.recipe/bld.bat new file mode 100644 index 0000000..85a7bc3 --- /dev/null +++ b/conda.recipe/bld.bat @@ -0,0 +1,2 @@ +%PYTHON% setup.py install +if errorlevel 1 exit 1 diff --git a/conda.recipe/setup.py b/conda.recipe/setup.py deleted file mode 100755 index 97165f5..0000000 --- a/conda.recipe/setup.py +++ /dev/null @@ -1,140 +0,0 @@ -from distutils.cmd import Command -from distutils.core import setup -from distutils.command.sdist import sdist as _sdist -from distutils.command.build import build as _build -import os - - -class data(Command): - - description = "Convert the NIST databas of constants" - - user_options = [] - - boolean_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - with open('quantities/constants/NIST_codata.txt') as f: - data = f.read() - data = data.split('\n')[10:-1] - - with open('quantities/constants/_codata.py', 'w') as f: - f.write('# THIS FILE IS AUTOMATICALLY GENERATED\n') - f.write('# ANY CHANGES MADE HERE WILL BE LOST\n\n') - f.write('physical_constants = {}\n\n') - for line in data: - name = line[:55].rstrip().replace('mag.','magnetic') - name = name.replace('mom.', 'moment') - val = line[55:77].replace(' ','').replace('...','') - prec = line[77:99].replace(' ','').replace('(exact)', '0') - unit = line[99:].rstrip().replace(' ', '*').replace('^', '**') - d = "{'value': %s, 'precision': %s, 'units': '%s'}" \ - %(val, prec, unit) - f.write("physical_constants['%s'] = %s\n"%(name, d)) - - -class sdist(_sdist): - - def run(self): - self.run_command('data') - _sdist.run(self) - - -class build(_build): - - def run(self): - self.run_command('data') - _build.run(self) - - -class test(Command): - - """Run the test suite.""" - - description = "Run the test suite" - - user_options = [('verbosity=', 'V', 'set test report verbosity')] - - def initialize_options(self): - self.verbosity = 0 - - def finalize_options(self): - try: - self.verbosity = int(self.verbosity) - except ValueError: - raise ValueError('verbosity must be an integer.') - - def run(self): - import sys - if sys.version.startswith('2.6') or sys.version.startswith('3.1'): - import unittest2 as unittest - else: - import unittest - suite = unittest.TestLoader().discover('..') - unittest.TextTestRunner(verbosity=self.verbosity+1).run(suite) - - -packages = [] -for dirpath, dirnames, filenames in os.walk('quantities'): - if '__init__.py' in filenames: - packages.append('.'.join(dirpath.split(os.sep))) - else: - del(dirnames[:]) - -with open('../quantities/version.py') as f: - for line in f: - if line.startswith('__version__'): - exec(line) - -setup( - author = 'Darren Dale', - author_email = 'dsdale24@gmail.com', -# classifiers = """Development Status :: 4 - Beta -# Environment :: Console -# Intended Audience :: Developers -# Intended Audience :: Education -# Intended Audience :: End Users/Desktop -# Intended Audience :: Science/Research -# License :: OSI Approved :: BSD License -# Operating System :: OS Independent -# Programming Language :: Python -# Topic :: Education -# Topic :: Scientific/Engineering -# """, - cmdclass = { - 'build': build, - 'data': data, - 'sdist': sdist, - 'test': test, - }, - description = "Support for physical quantities with units, based on numpy", - download_url = "http://pypi.python.org/pypi/quantities", - keywords = ['quantities', 'units', 'physical', 'constants'], - license = 'BSD', - long_description = """Quantities is designed to handle arithmetic and - conversions of physical quantities, which have a magnitude, dimensionality - specified by various units, and possibly an uncertainty. See the tutorial_ - for examples. Quantities builds on the popular numpy library and is - designed to work with numpy ufuncs, many of which are already - supported. Quantities is actively developed, and while the current features - and API are stable, test coverage is incomplete so the package is not - suggested for mission-critical applications. - - .. _tutorial: http://packages.python.org/quantities/user/tutorial.html - """, - name = 'quantities', - packages = packages, - platforms = 'Any', - requires = [ - 'python (>=2.6.0)', - 'numpy (>=1.4.0)', - ], - url = 'http://packages.python.org/quantities', - version = __version__, -) From 66e7d911beb18253276bb002722f3074297503c6 Mon Sep 17 00:00:00 2001 From: mjn Date: Sat, 23 Aug 2014 00:02:58 +0200 Subject: [PATCH 030/212] Remove an obsolete comment Commit 86424f12e1be503b3c6d57a1ac2317d78d337376 added a regexp to address "TODO: use a regexp", but the TODO comment didn't get removed. --- quantities/markup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/quantities/markup.py b/quantities/markup.py index 88872b8..bc98e04 100644 --- a/quantities/markup.py +++ b/quantities/markup.py @@ -32,7 +32,6 @@ def __init__(self): superscripts = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'] def superscript(val): - # TODO: use a regexp: items = re.split(r'\*{2}([\d]+)(?!\.)', val) ret = [] while items: From 073eba2a0253267207a77382aab55fb479e07633 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 9 Jun 2015 11:05:31 +0200 Subject: [PATCH 031/212] added tests for axis and out parameters of the array methods --- quantities/tests/test_methods.py | 149 ++++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 23 deletions(-) diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index abbf487..cf05199 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -100,64 +100,167 @@ def test_nonzero(self): q = [1, 0, 5, 6, 0, 9] * pq.m self.assertQuantityEqual(q.nonzero()[0], [0, 2, 3, 5]) + def methodWithOut(self, name, result, q=None, *args, **kw): + import numpy as np + from .. import Quantity + + if q is None: + q = self.q + + self.assertQuantityEqual( + getattr(q.copy(), name)(*args,**kw), + result + ) + if isinstance(result, Quantity): + # deliberately using an incompatible unit + out = Quantity(np.empty_like(result.magnitude), pq.s, copy=False) + else: + out = np.empty_like(result) + ret = getattr(q.copy(), name)(*args, out=out, **kw) + self.assertQuantityEqual( + ret, + result + ) + # returned array should be the same as out + self.assertEqual(id(ret),id(out)) + # but the units had to be adjusted + if isinstance(result, Quantity): + self.assertEqual(ret.units,result.units) + else: + self.assertEqual( + getattr(ret, 'units', pq.dimensionless), + pq.dimensionless + ) + + def test_max(self): - self.assertQuantityEqual(self.q.max(), 4*pq.m) + self.methodWithOut('max', 4 * pq.m) + self.methodWithOut('max', [3, 4] * pq.m, axis=0) + self.methodWithOut('max', [2, 4] * pq.m, axis=1) def test_argmax(self): - self.assertEqual(self.q.argmax(), 3) + import numpy as np + self.assertQuantityEqual(self.q.argmax(), 3) + self.assertQuantityEqual(self.q.argmax(axis=0), [1, 1]) + self.assertQuantityEqual(self.q.argmax(axis=1), [1, 1]) + # apparently, numpy's argmax does not return the same object when out is specified. + # instead, we test here for shared data + out = np.r_[0, 0] + ret = self.q.argmax(axis=0,out=out) + self.assertQuantityEqual(ret, [1, 1]) + self.assertEqual(ret.ctypes.data, out.ctypes.data) def test_min(self): - self.assertEqual(self.q.min(), 1 * pq.m) + self.methodWithOut('min', 1 * pq.m) + self.methodWithOut('min', [1, 2] * pq.m, axis=0) + self.methodWithOut('min', [1, 3] * pq.m, axis=1) def test_argmin(self): - self.assertEqual(self.q.argmin(), 0) + import numpy as np + self.assertQuantityEqual(self.q.argmin(), 0) + self.assertQuantityEqual(self.q.argmin(axis=0), [0, 0]) + self.assertQuantityEqual(self.q.argmin(axis=1), [0, 0]) + # apparently, numpy's argmax does not return the same object when out is specified. + # instead, we test here for shared data + out = np.r_[2, 2] + ret = self.q.argmin(axis=0,out=out) + self.assertQuantityEqual(ret, [0, 0]) + self.assertEqual(ret.ctypes.data, out.ctypes.data) def test_ptp(self): - self.assertQuantityEqual(self.q.ptp(), 3 * pq.m) + self.methodWithOut('ptp', 3 * pq.m) + self.methodWithOut('ptp', [2, 2] * pq.m, axis=0) + self.methodWithOut('ptp', [1, 1] * pq.m, axis=1) def test_clip(self): - self.assertQuantityEqual( - self.q.copy().clip(max=2*pq.m), - [[1, 2], [2, 2]] * pq.m + self.methodWithOut( + 'clip', + [[1, 2], [2, 2]] * pq.m, + max=2*pq.m, ) - self.assertQuantityEqual( - self.q.copy().clip(min=3*pq.m), - [[3, 3], [3, 4]] * pq.m + self.methodWithOut( + 'clip', + [[3, 3], [3, 4]] * pq.m, + min=3*pq.m, ) - self.assertQuantityEqual( - self.q.copy().clip(min=2*pq.m, max=3*pq.m), - [[2, 2], [3, 3]] * pq.m + self.methodWithOut( + 'clip', + [[2, 2], [3, 3]] * pq.m, + min=2*pq.m, max=3*pq.m ) self.assertRaises(ValueError, self.q.clip, pq.J) self.assertRaises(ValueError, self.q.clip, 1) def test_round(self): q = [1, 1.33, 5.67, 22] * pq.m - self.assertQuantityEqual(q.round(0), [1, 1, 6, 22] * pq.m) - self.assertQuantityEqual(q.round(-1), [0, 0, 10, 20] * pq.m) - self.assertQuantityEqual(q.round(1), [1, 1.3, 5.7, 22] * pq.m) + self.methodWithOut( + 'round', + [1, 1, 6, 22] * pq.m, + q=q, + decimals=0, + ) + self.methodWithOut( + 'round', + [0, 0, 10, 20] * pq.m, + q=q, + decimals=-1, + ) + self.methodWithOut( + 'round', + [1, 1.3, 5.7, 22] * pq.m, + q=q, + decimals=1, + ) def test_trace(self): - self.assertQuantityEqual(self.q.trace(), (1+4) * pq.m) + self.methodWithOut('trace', (1+4) * pq.m) def test_cumsum(self): - self.assertQuantityEqual(self.q.cumsum(), [1, 3, 6, 10] * pq.m) + self.methodWithOut('cumsum', [1, 3, 6, 10] * pq.m) + self.methodWithOut('cumsum', [[1, 2], [4, 6]] * pq.m, axis=0) + self.methodWithOut('cumsum', [[1, 3], [3, 7]] * pq.m, axis=1) def test_mean(self): - self.assertQuantityEqual(self.q.mean(), 2.5 * pq.m) + self.methodWithOut('mean', 2.5 * pq.m) + self.methodWithOut('mean', [2, 3] * pq.m, axis=0) + self.methodWithOut('mean', [1.5, 3.5] * pq.m, axis=1) def test_var(self): - self.assertQuantityEqual(self.q.var(), 1.25*pq.m**2) + self.methodWithOut('var', 1.25 * pq.m**2) + self.methodWithOut('var', [1, 1] * pq.m**2, axis=0) + self.methodWithOut('var', [0.25, 0.25] * pq.m**2, axis=1) def test_std(self): - self.assertQuantityEqual(self.q.std(), 1.11803*pq.m, delta=1e-5) + self.methodWithOut('std', 1.1180339887498949 * pq.m) + self.methodWithOut('std', [1, 1] * pq.m, axis=0) + self.methodWithOut('std', [0.5, 0.5] * pq.m, axis=1) def test_prod(self): - self.assertQuantityEqual(self.q.prod(), 24 * pq.m**4) + self.methodWithOut('prod', 24 * pq.m**4) + self.methodWithOut('prod', [3, 8] * pq.m**2, axis=0) + self.methodWithOut('prod', [2, 12] * pq.m**2, axis=1) def test_cumprod(self): self.assertRaises(ValueError, self.q.cumprod) self.assertQuantityEqual((self.q/pq.m).cumprod(), [1, 2, 6, 24]) + q = self.q/pq.m + self.methodWithOut( + 'cumprod', + [1, 2, 6, 24], + q=q, + ) + self.methodWithOut( + 'cumprod', + [[1, 2], [3, 8]], + q=q, + axis=0, + ) + self.methodWithOut( + 'cumprod', + [[1, 2], [3, 12]], + q=q, + axis=1, + ) def test_conj(self): self.assertQuantityEqual((self.q*(1+1j)).conj(), self.q*(1-1j)) From 4e1a3eedb05c789a5b6bbc22826d79ecf51813e3 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 9 Jun 2015 11:06:32 +0200 Subject: [PATCH 032/212] fixed unused axis parameters on min and max, and out parameters on all methods --- quantities/quantity.py | 164 +++++++++++++++++++++++++++-------------- 1 file changed, 109 insertions(+), 55 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index c2b7875..98e696f 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -422,11 +422,14 @@ def _tolist(self, work_list): @with_doc(np.ndarray.sum) def sum(self, axis=None, dtype=None, out=None): - return Quantity( - self.magnitude.sum(axis, dtype, out), - self.dimensionality, - copy=False - ) + ret = self.magnitude.sum(axis, dtype, None if out is None else out.magnitude) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.fill) def fill(self, value): @@ -471,31 +474,44 @@ def nonzero(self): @with_doc(np.ndarray.max) def max(self, axis=None, out=None): - return Quantity( - self.magnitude.max(), - self.dimensionality, - copy=False - ) + ret = self.magnitude.max(axis, None if out is None else out.magnitude) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out + + @with_doc(np.ndarray.argmin) + def argmax(self, axis=None, out=None): + return self.magnitude.argmax(axis, out) @with_doc(np.ndarray.min) def min(self, axis=None, out=None): - return Quantity( - self.magnitude.min(), - self.dimensionality, - copy=False - ) + ret = self.magnitude.min(axis, None if out is None else out.magnitude) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.argmin) - def argmin(self,axis=None, out=None): - return self.magnitude.argmin() + def argmin(self, axis=None, out=None): + return self.magnitude.argmin(axis, out) @with_doc(np.ndarray.ptp) def ptp(self, axis=None, out=None): - return Quantity( - self.magnitude.ptp(), - self.dimensionality, - copy=False - ) + ret = self.magnitude.ptp(axis, None if out is None else out.magnitude) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.clip) def clip(self, min=None, max=None, out=None): @@ -516,23 +532,35 @@ def clip(self, min=None, max=None, out=None): max.rescale(self._dimensionality).magnitude, out ) - return Quantity(clipped, self.dimensionality, copy=False) + dim = self.dimensionality + if out is None: + return Quantity(clipped, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.round) def round(self, decimals=0, out=None): - return Quantity( - self.magnitude.round(decimals, out), - self.dimensionality, - copy=False - ) + ret = self.magnitude.round(decimals, None if out is None else out.magnitude) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.trace) def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None): - return Quantity( - self.magnitude.trace(offset, axis1, axis2, dtype, out), - self.dimensionality, - copy=False - ) + ret = self.magnitude.trace(offset, axis1, axis2, dtype, None if out is None else out.magnitude) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.squeeze) def squeeze(self, axis=None): @@ -544,26 +572,36 @@ def squeeze(self, axis=None): @with_doc(np.ndarray.mean) def mean(self, axis=None, dtype=None, out=None): - return Quantity( - self.magnitude.mean(axis, dtype, out), - self.dimensionality, - copy=False) + ret = self.magnitude.mean(axis, dtype, None if out is None else out.magnitude) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.var) def var(self, axis=None, dtype=None, out=None, ddof=0): - return Quantity( - self.magnitude.var(axis, dtype, out, ddof), - self._dimensionality**2, - copy=False - ) + ret = self.magnitude.var(axis, dtype, out, ddof) + dim = self._dimensionality**2 + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.std) def std(self, axis=None, dtype=None, out=None, ddof=0): - return Quantity( - self.magnitude.std(axis, dtype, out, ddof), - self._dimensionality, - copy=False - ) + ret = self.magnitude.std(axis, dtype, out, ddof) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.prod) def prod(self, axis=None, dtype=None, out=None): @@ -572,15 +610,25 @@ def prod(self, axis=None, dtype=None, out=None): else: power = self.shape[axis] - return Quantity( - self.magnitude.prod(axis, dtype, out), - self._dimensionality**power, - copy=False - ) + ret = self.magnitude.prod(axis, dtype, None if out is None else out.magnitude) + dim = self._dimensionality**power + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.cumsum) def cumsum(self, axis=None, dtype=None, out=None): - return super(Quantity, self).cumsum(axis, dtype, out)*self.units + ret = self.magnitude.cumsum(axis, dtype, None if out is None else out.magnitude) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if not isinstance(out, Quantity): + raise TypeError("out parameter must be a Quantity") + out._dimensionality = dim + return out @with_doc(np.ndarray.cumprod) def cumprod(self, axis=None, dtype=None, out=None): @@ -589,8 +637,14 @@ def cumprod(self, axis=None, dtype=None, out=None): raise ValueError( "Quantity must be dimensionless, try using simplified" ) - else: - return super(Quantity, self).cumprod(axis, dtype, out) + + ret = self.magnitude.cumprod(axis, dtype, out) + dim = self.dimensionality + if out is None: + return Quantity(ret, dim, copy=False) + if isinstance(out, Quantity): + out._dimensionality = dim + return out # list of unsupported functions: [choose] From afcc0e9a8d3bb5b2c84bf683baf030e6c71944e7 Mon Sep 17 00:00:00 2001 From: p2jones Date: Sat, 27 Jun 2015 15:00:41 +0200 Subject: [PATCH 033/212] Update substance.py Add units of millimole and micromole. --- quantities/units/substance.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/quantities/units/substance.py b/quantities/units/substance.py index cc6206e..68c102d 100644 --- a/quantities/units/substance.py +++ b/quantities/units/substance.py @@ -8,3 +8,14 @@ 'mole', symbol='mol' ) +mmol = UnitSubstance( + 'millimole', + mol/1000, + symbol='mmol' +) +umol = UnitSubstance( + 'micromole', + mmol/1000, + symbol='umol', + u_symbol='µmol' +) From 28e0d15affb30162a28a563077214fad3ad7d4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Mon, 12 Oct 2015 13:28:51 +0200 Subject: [PATCH 034/212] Updated README and travis script --- .travis.yml | 9 +++++- README.rst | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.txt | 3 -- 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 README.rst delete mode 100644 README.txt diff --git a/.travis.yml b/.travis.yml index 8a62681..b292ede 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,23 @@ language: python +sudo: false +env: + matrix: + - NUMPY_VERSION="1.7.1" + - NUMPY_VERSION="1.9.1" python: - 2.6 - 2.7 - 3.3 + - 3.4 before_install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi - - pip install "numpy==1.7.0" + - pip install "numpy==$NUMPY_VERSION" install: - python setup.py install script: - python setup.py test + - python -m doctest README.rst notifications: email: false diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..7430066 --- /dev/null +++ b/README.rst @@ -0,0 +1,84 @@ +========== +quantities +========== + +|pypi version| |pypi download| |Build status| + +.. |pypi version| image:: https://img.shields.io/pypi/v/quantities.png + :target: https://pypi.python.org/pypi/quantities +.. |pypi download| image:: https://img.shields.io/pypi/dm/quantities.png + :target: https://pypi.python.org/pypi/quantities +.. |Build status| image:: https://secure.travis-ci.org/python-quantities/python-quantities.png?branch=master + :target: http://travis-ci.org/python-quantities/python-quantities + +A Python package for handling physical quantities. The source code and issue +tracker are hosted on github: + +https://www.github.com/python-quantities/python-quantities + +Download +-------- +Get the latest version of quantities from +https://pypi.python.org/pypi/quantities/ + +To get the git version do:: + + $ git clone git://github.com/python-quantities/python-quantities.git + + +Documentation and usage +----------------------- +You can find the official documenation at: + + http://packages.python.org/quantities + +Here is a simple example: + +.. code:: python + + >>> import quantities as pq + >>> distance = 42*pq.metre + >>> time = 17*pq.second + >>> velocity = distance / time + >>> velocity + array(2.4705882352941178) * m/s + >>> velocity + 3 + Traceback (most recent call last): + ... + ValueError: Unable to convert between units of "dimensionless" and "m/s" + +Installation +------------ +quantities has a hard dependency on the `NumPy `_ library. +You should install it first, please refer to the NumPy installation guide: + +http://docs.scipy.org/doc/numpy/user/install.html + +To install quantities itself, then simply run:: + + $ python setup.py install --user + +If you install it system-wide, you may need to prefix the previous command with ``sudo``:: + + $ sudo python setup.py install + +Tests +----- +To execute all tests, run:: + + $ python setup.py test + +in the current directory. The master branch is automatically tested by +Travis CI. + +Author +------ +quantities is written by Darren Dale + +License +------- +Quantities only uses BSD compatible code. See the Open Source +Initiative `licenses page `_ +for details on individual licenses. + +See `doc/user/license.rst `_ for further details on the license of quantities diff --git a/README.txt b/README.txt deleted file mode 100644 index 1506026..0000000 --- a/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -to install quantities, run the following in the source directory: - -python setup.py install From abf2558719584cc8b4e0723f2e734ca69b90828d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Mon, 12 Oct 2015 14:24:45 +0200 Subject: [PATCH 035/212] Set correct exit status for 'python setup.py test' (was allways successful) --- setup.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 03d81e8..86410a6 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,9 @@ from distutils.command.sdist import sdist as _sdist from distutils.command.build import build as _build import os +import sys +TEST_RESULT = None class data(Command): @@ -77,7 +79,8 @@ def run(self): else: import unittest suite = unittest.TestLoader().discover('.') - unittest.TextTestRunner(verbosity=self.verbosity+1).run(suite) + global TEST_RESULT + TEST_RESULT = unittest.TextTestRunner(verbosity=self.verbosity+1).run(suite) packages = [] @@ -138,3 +141,9 @@ def run(self): url = 'http://packages.python.org/quantities', version = __version__, ) + +if __name__ == '__main__': + if TEST_RESULT is not None: + if len(TEST_RESULT.errors) > 0: + # failing test -> Set non-success exit-code + sys.exit(os.EX_OK+1) From 0a4033895b74c8f9814834c28cc6b79319c5223d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Mon, 12 Oct 2015 14:32:13 +0200 Subject: [PATCH 036/212] Add (failing) tests for comparison ufuncs, see #76 --- quantities/tests/test_umath.py | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 24e2470..7d776ee 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -242,3 +242,39 @@ def test_radians(self): def test_unwrap(self): self.assertQuantityEqual(np.unwrap([0,3*np.pi]*pq.radians), [0,np.pi]) self.assertQuantityEqual(np.unwrap([0,540]*pq.deg), [0,180]*pq.deg) + + def test_equal(self): + arr1 = (1, 1) * pq.m + arr2 = (1.0, 1.0) * pq.m + self.assertTrue(np.all(np.equal(arr1, arr2))) + self.assertFalse(np.all(np.equal(arr1, arr2 * 2))) + + def test_not_equal(self): + arr1 = (1, 1) * pq.m + arr2 = (1.0, 1.0) * pq.m + self.assertTrue(np.all(np.not_equal(arr1, arr2*2))) + self.assertFalse(np.all(np.not_equal(arr1, arr2))) + + def test_less(self): + arr1 = (1, 1) * pq.m + arr2 = (1.0, 1.0) * pq.m + self.assertTrue(np.all(np.less(arr1, arr2*2))) + self.assertFalse(np.all(np.less(arr1*2, arr2))) + + def test_less_equal(self): + arr1 = (1, 1) * pq.m + arr2 = (1.0, 2.0) * pq.m + self.assertTrue(np.all(np.less_equal(arr1, arr2))) + self.assertFalse(np.all(np.less_equal(arr2, arr1))) + + def test_greater(self): + arr1 = (1, 1) * pq.m + arr2 = (1.0, 2.0) * pq.m + self.assertTrue(np.all(np.greater(arr2*1.01, arr1))) + self.assertFalse(np.all(np.greater(arr2, arr1))) + + def test_greater_equal(self): + arr1 = (1, 1) * pq.m + arr2 = (1.0, 2.0) * pq.m + self.assertTrue(np.all(np.greater_equal(arr2, arr1))) + self.assertFalse(np.all(np.greater_equal(arr2*0.99, arr1))) From b1ab52dd48b23a31a286a9ffc1558646b3a731d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Mon, 12 Oct 2015 14:44:35 +0200 Subject: [PATCH 037/212] Add ufuncs: equal, not_equal, less, less_equal, greater, greater_equal --- quantities/dimensionality.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index 73735fc..6bccf89 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -47,7 +47,7 @@ def string(self): @property def unicode(self): return markup.format_units_unicode(self) - + @property def latex(self): return markup.format_units_latex(self) @@ -285,6 +285,12 @@ def _d_check_uniform(q1, q2, out=None): p_dict[np.floor_divide] = _d_check_uniform p_dict[np.arctan2] = _d_check_uniform p_dict[np.hypot] = _d_check_uniform +p_dict[np.equal] = _d_check_uniform +p_dict[np.not_equal] = _d_check_uniform +p_dict[np.less] = _d_check_uniform +p_dict[np.less_equal] = _d_check_uniform +p_dict[np.greater] = _d_check_uniform +p_dict[np.greater_equal] = _d_check_uniform def _d_power(q1, q2, out=None): if getattr(q2, 'dimensionality', None): From a523a9bc372b5cf6c8b64ea209f296f65668f5c7 Mon Sep 17 00:00:00 2001 From: Takashi Date: Wed, 16 Dec 2015 23:24:30 +0900 Subject: [PATCH 038/212] Proposed correction from kilobar to megabar Hi, I would like to propose correcting an object name in line 66 as followings, considering the meanings of kilobar and megabar. From: Mbar = kilobar = UnitQuantity( To: Mbar = megabar = UnitQuantity( Best regards, --- quantities/units/pressure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/units/pressure.py b/quantities/units/pressure.py index 81561c3..02c092f 100644 --- a/quantities/units/pressure.py +++ b/quantities/units/pressure.py @@ -63,7 +63,7 @@ symbol='kbar', aliases=['kilobars'] ) -Mbar = kilobar = UnitQuantity( +Mbar = megabar = UnitQuantity( 'megabar', 1000*kbar, symbol='Mbar', From a95cda61f77d15ae338a342193b4d57dcbf0c12f Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 10:07:06 -0500 Subject: [PATCH 039/212] uses versioneer --- conda.recipe/bld.bat | 3 +++ conda.recipe/build.sh | 5 ++++- conda.recipe/format_version.py | 8 ++++++++ doc/conf.py | 7 ++----- doc/rtd-requirements.txt | 1 + quantities/__init__.py | 13 +++++++------ setup.cfg | 7 +++++++ setup.py | 20 ++++++++++++++------ 8 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 conda.recipe/format_version.py create mode 100644 doc/rtd-requirements.txt create mode 100644 setup.cfg diff --git a/conda.recipe/bld.bat b/conda.recipe/bld.bat index 85a7bc3..cf9541b 100644 --- a/conda.recipe/bld.bat +++ b/conda.recipe/bld.bat @@ -1,2 +1,5 @@ +git describe --tags --dirty > %SRC_DIR%/__conda_version__.txt +%PYTHON% %RECIPE_DIR%/format_version.py %SRC_DIR%/__conda_version__.txt + %PYTHON% setup.py install if errorlevel 1 exit 1 diff --git a/conda.recipe/build.sh b/conda.recipe/build.sh index ee23756..7a25c28 100755 --- a/conda.recipe/build.sh +++ b/conda.recipe/build.sh @@ -1 +1,4 @@ -$PYTHON setup.py install \ No newline at end of file +git describe --tags --dirty > $SRC_DIR/__conda_version__.txt +$PYTHON $RECIPE_DIR/format_version.py $SRC_DIR/__conda_version__.txt + +$PYTHON setup.py install diff --git a/conda.recipe/format_version.py b/conda.recipe/format_version.py new file mode 100644 index 0000000..b96d641 --- /dev/null +++ b/conda.recipe/format_version.py @@ -0,0 +1,8 @@ +import os +import sys + +fn = sys.argv[1] +with open(fn) as f: + s = f.read().lstrip('v').replace('-', '+', 1).replace('-', '.') +with open(fn, 'w') as f: + f.write(s) diff --git a/doc/conf.py b/doc/conf.py index 7d8c44a..c4fad27 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,14 +48,11 @@ # |version| and |release|, also used in various other places throughout the # built documents. # -with open('../quantities/version.py') as f: - for line in f: - if line.startswith('__version__'): - exec(line) +from quantities import __version__ # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags. -release = version +release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/rtd-requirements.txt b/doc/rtd-requirements.txt new file mode 100644 index 0000000..a2954e3 --- /dev/null +++ b/doc/rtd-requirements.txt @@ -0,0 +1 @@ +numpydoc diff --git a/quantities/__init__.py b/quantities/__init__.py index f807766..5d43e20 100644 --- a/quantities/__init__.py +++ b/quantities/__init__.py @@ -213,7 +213,7 @@ >>> print pq.constants.proton_mass.simplified 1.672621637e-27 kg +/-8.3e-35 kg (1 sigma) - + A Latex representation of the dimensionality may be obtained in the following fashion:: >>> g = pq.Quantity(9.80665,'m/s**2') @@ -225,12 +225,12 @@ >>> print weight.dimensionality.latex $\\mathrm{N}$ -The Latex output is compliant with the MathText subset used by Matplotlib. To add +The Latex output is compliant with the MathText subset used by Matplotlib. To add formatted units to the axis label of a Matplotlib figure, one could use:: >>> ax.set_ylabel('Weight ' + weight.dimensionality.latex) - -Greater customization is available via the markup.format_units_latex function. It allows + +Greater customization is available via the markup.format_units_latex function. It allows the user to modify the font, the multiplication symbol, or to encapsulate the latex string in parentheses. Due to the complexity of CompoundUnits, the latex rendering of CompoundUnits will utilize the latex \\frac{num}{den} construct. @@ -267,7 +267,9 @@ from __future__ import absolute_import -from .version import __version__ +from ._version import get_versions +__version__ = get_versions()['version'] +del get_versions from .registry import unit_registry @@ -290,4 +292,3 @@ def test(verbosity=1): import unittest suite = unittest.TestLoader().discover('quantities') unittest.TextTestRunner(verbosity=verbosity).run(suite) - diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..5abcbe6 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,7 @@ +[versioneer] +VCS = git +style = pep440 +versionfile_source = hexedd/_version.py +versionfile_build = hexedd/_version.py +tag_prefix = v +parentdir_prefix = hexedd- \ No newline at end of file diff --git a/setup.py b/setup.py index 03d81e8..dfce2b0 100755 --- a/setup.py +++ b/setup.py @@ -4,6 +4,11 @@ from distutils.command.build import build as _build import os +import versioneer + + +cmdclass = versioneer.get_cmdclass() + class data(Command): @@ -38,6 +43,8 @@ def run(self): %(val, prec, unit) f.write("physical_constants['%s'] = %s\n"%(name, d)) +cmdclass['data'] = data + class sdist(_sdist): @@ -45,6 +52,8 @@ def run(self): self.run_command('data') _sdist.run(self) +cmdclass['sdist'] = sdist + class build(_build): @@ -52,6 +61,8 @@ def run(self): self.run_command('data') _build.run(self) +cmdclass['build'] = build + class test(Command): @@ -79,6 +90,8 @@ def run(self): suite = unittest.TestLoader().discover('.') unittest.TextTestRunner(verbosity=self.verbosity+1).run(suite) +cmdclass['test'] = test + packages = [] for dirpath, dirnames, filenames in os.walk('quantities'): @@ -107,12 +120,7 @@ def run(self): # Topic :: Education # Topic :: Scientific/Engineering # """, - cmdclass = { - 'build': build, - 'data': data, - 'sdist': sdist, - 'test': test, - }, + cmdclass = cmdclass, description = "Support for physical quantities with units, based on numpy", download_url = "http://pypi.python.org/pypi/quantities", keywords = ['quantities', 'units', 'physical', 'constants'], From bdc5cca024ee87426fe090c6ae849816d62ef899 Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 10:11:37 -0500 Subject: [PATCH 040/212] more versioneer work --- .gitattributes | 1 + MANIFEST.in | 2 + quantities/_version.py | 460 +++++++++++ quantities/version.py | 2 - setup.cfg | 6 +- setup.py | 6 +- versioneer.py | 1699 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 2166 insertions(+), 10 deletions(-) create mode 100644 .gitattributes create mode 100644 quantities/_version.py delete mode 100644 quantities/version.py create mode 100644 versioneer.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9e3740a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +quantities/_version.py export-subst diff --git a/MANIFEST.in b/MANIFEST.in index ff7a261..0a19b7f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,5 @@ include distribute_setup.py include CHANGES.txt include py3tool.py include quantities/constants/NIST_codata.txt +include versioneer.py +include quantities/_version.py diff --git a/quantities/_version.py b/quantities/_version.py new file mode 100644 index 0000000..0ac34ba --- /dev/null +++ b/quantities/_version.py @@ -0,0 +1,460 @@ + +# This file helps to compute a version number in source trees obtained from +# git-archive tarball (such as those provided by githubs download-from-tag +# feature). Distribution tarballs (built by setup.py sdist) and build +# directories (produced by setup.py build) will contain a much shorter file +# that just contains the computed version number. + +# This file is released into the public domain. Generated by +# versioneer-0.15 (https://github.com/warner/python-versioneer) + +import errno +import os +import re +import subprocess +import sys + + +def get_keywords(): + # these strings will be replaced by git during git-archive. + # setup.py/versioneer.py will grep for the variable names, so they must + # each be defined on a line of their own. _version.py will just call + # get_keywords(). + git_refnames = "$Format:%d$" + git_full = "$Format:%H$" + keywords = {"refnames": git_refnames, "full": git_full} + return keywords + + +class VersioneerConfig: + pass + + +def get_config(): + # these strings are filled in when 'setup.py versioneer' creates + # _version.py + cfg = VersioneerConfig() + cfg.VCS = "git" + cfg.style = "pep440" + cfg.tag_prefix = "v" + cfg.parentdir_prefix = "quantities-" + cfg.versionfile_source = "quantities/_version.py" + cfg.verbose = False + return cfg + + +class NotThisMethod(Exception): + pass + + +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + def decorate(f): + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % dispcmd) + print(e) + return None + else: + if verbose: + print("unable to find command, tried %s" % (commands,)) + return None + stdout = p.communicate()[0].strip() + if sys.version_info[0] >= 3: + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %s (error)" % dispcmd) + return None + return stdout + + +def versions_from_parentdir(parentdir_prefix, root, verbose): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None} + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): + # the code embedded in _version.py can just fetch the value of these + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} + try: + f = open(versionfile_abs, "r") + for line in f.readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + f.close() + except EnvironmentError: + pass + return keywords + + +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + if not keywords: + raise NotThisMethod("no keywords at all, weird") + refnames = keywords["refnames"].strip() + if refnames.startswith("$Format"): + if verbose: + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs-tags)) + if verbose: + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + if verbose: + print("picking %s" % r) + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None + } + # no suitable tags, so version is "0+unknown", but full hex is still there + if verbose: + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags"} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. + + if not os.path.exists(os.path.join(root, ".git")): + if verbose: + print("no .git in %s" % root) + raise NotThisMethod("no .git directory") + + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] + # if there is a tag, this yields TAG-NUM-gHEX[-dirty] + # if there are no tags, this yields HEX[-dirty] (no NUM) + describe_out = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long"], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (full_tag, tag_prefix)) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + + else: + # HEX: no tags + pieces["closest-tag"] = None + count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits + + return pieces + + +def plus_or_dot(pieces): + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + # now build up version string, with post-release "local version + # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + # exceptions: + # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + # TAG[.post.devDISTANCE] . No -dirty + + # exceptions: + # 1: no tags. 0.post.devDISTANCE + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%d" % pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%d" % pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that + # .dev0 sorts backwards (a dirty tree will appear "older" than the + # corresponding clean one), but you shouldn't be releasing software with + # -dirty anyways. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + return rendered + + +def render_pep440_old(pieces): + # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty + # --always' + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty + # --always -long'. The distance/hash is unconditional. + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"]} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%s'" % style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None} + + +def get_versions(): + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. + + cfg = get_config() + verbose = cfg.verbose + + try: + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, + verbose) + except NotThisMethod: + pass + + try: + root = os.path.realpath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in cfg.versionfile_source.split('/'): + root = os.path.dirname(root) + except NameError: + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree"} + + try: + pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) + return render(pieces, cfg.style) + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + except NotThisMethod: + pass + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to compute version"} diff --git a/quantities/version.py b/quantities/version.py deleted file mode 100644 index d6c6ab5..0000000 --- a/quantities/version.py +++ /dev/null @@ -1,2 +0,0 @@ - -__version__ = '0.10.1' diff --git a/setup.cfg b/setup.cfg index 5abcbe6..4b37c9d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [versioneer] VCS = git style = pep440 -versionfile_source = hexedd/_version.py -versionfile_build = hexedd/_version.py +versionfile_source = quantities/_version.py +versionfile_build = quantities/_version.py tag_prefix = v -parentdir_prefix = hexedd- \ No newline at end of file +parentdir_prefix = quantities- \ No newline at end of file diff --git a/setup.py b/setup.py index dfce2b0..f02e8c8 100755 --- a/setup.py +++ b/setup.py @@ -100,10 +100,6 @@ def run(self): else: del(dirnames[:]) -with open('quantities/version.py') as f: - for line in f: - if line.startswith('__version__'): - exec(line) setup( author = 'Darren Dale', @@ -144,5 +140,5 @@ def run(self): 'numpy (>=1.4.0)', ], url = 'http://packages.python.org/quantities', - version = __version__, + version = versioneer.get_version(), ) diff --git a/versioneer.py b/versioneer.py new file mode 100644 index 0000000..c010f63 --- /dev/null +++ b/versioneer.py @@ -0,0 +1,1699 @@ + +# Version: 0.15 + +""" +The Versioneer +============== + +* like a rocketeer, but for versions! +* https://github.com/warner/python-versioneer +* Brian Warner +* License: Public Domain +* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, and pypy +* [![Latest Version] +(https://pypip.in/version/versioneer/badge.svg?style=flat) +](https://pypi.python.org/pypi/versioneer/) +* [![Build Status] +(https://travis-ci.org/warner/python-versioneer.png?branch=master) +](https://travis-ci.org/warner/python-versioneer) + +This is a tool for managing a recorded version number in distutils-based +python projects. The goal is to remove the tedious and error-prone "update +the embedded version string" step from your release process. Making a new +release should be as easy as recording a new tag in your version-control +system, and maybe making new tarballs. + + +## Quick Install + +* `pip install versioneer` to somewhere to your $PATH +* add a `[versioneer]` section to your setup.cfg (see below) +* run `versioneer install` in your source tree, commit the results + +## Version Identifiers + +Source trees come from a variety of places: + +* a version-control system checkout (mostly used by developers) +* a nightly tarball, produced by build automation +* a snapshot tarball, produced by a web-based VCS browser, like github's + "tarball from tag" feature +* a release tarball, produced by "setup.py sdist", distributed through PyPI + +Within each source tree, the version identifier (either a string or a number, +this tool is format-agnostic) can come from a variety of places: + +* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows + about recent "tags" and an absolute revision-id +* the name of the directory into which the tarball was unpacked +* an expanded VCS keyword ($Id$, etc) +* a `_version.py` created by some earlier build step + +For released software, the version identifier is closely related to a VCS +tag. Some projects use tag names that include more than just the version +string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool +needs to strip the tag prefix to extract the version identifier. For +unreleased software (between tags), the version identifier should provide +enough information to help developers recreate the same tree, while also +giving them an idea of roughly how old the tree is (after version 1.2, before +version 1.3). Many VCS systems can report a description that captures this, +for example `git describe --tags --dirty --always` reports things like +"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the +0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has +uncommitted changes. + +The version identifier is used for multiple purposes: + +* to allow the module to self-identify its version: `myproject.__version__` +* to choose a name and prefix for a 'setup.py sdist' tarball + +## Theory of Operation + +Versioneer works by adding a special `_version.py` file into your source +tree, where your `__init__.py` can import it. This `_version.py` knows how to +dynamically ask the VCS tool for version information at import time. + +`_version.py` also contains `$Revision$` markers, and the installation +process marks `_version.py` to have this marker rewritten with a tag name +during the `git archive` command. As a result, generated tarballs will +contain enough information to get the proper version. + +To allow `setup.py` to compute a version too, a `versioneer.py` is added to +the top level of your source tree, next to `setup.py` and the `setup.cfg` +that configures it. This overrides several distutils/setuptools commands to +compute the version when invoked, and changes `setup.py build` and `setup.py +sdist` to replace `_version.py` with a small static file that contains just +the generated version data. + +## Installation + +First, decide on values for the following configuration variables: + +* `VCS`: the version control system you use. Currently accepts "git". + +* `style`: the style of version string to be produced. See "Styles" below for + details. Defaults to "pep440", which looks like + `TAG[+DISTANCE.gSHORTHASH[.dirty]]`. + +* `versionfile_source`: + + A project-relative pathname into which the generated version strings should + be written. This is usually a `_version.py` next to your project's main + `__init__.py` file, so it can be imported at runtime. If your project uses + `src/myproject/__init__.py`, this should be `src/myproject/_version.py`. + This file should be checked in to your VCS as usual: the copy created below + by `setup.py setup_versioneer` will include code that parses expanded VCS + keywords in generated tarballs. The 'build' and 'sdist' commands will + replace it with a copy that has just the calculated version string. + + This must be set even if your project does not have any modules (and will + therefore never import `_version.py`), since "setup.py sdist" -based trees + still need somewhere to record the pre-calculated version strings. Anywhere + in the source tree should do. If there is a `__init__.py` next to your + `_version.py`, the `setup.py setup_versioneer` command (described below) + will append some `__version__`-setting assignments, if they aren't already + present. + +* `versionfile_build`: + + Like `versionfile_source`, but relative to the build directory instead of + the source directory. These will differ when your setup.py uses + 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, + then you will probably have `versionfile_build='myproject/_version.py'` and + `versionfile_source='src/myproject/_version.py'`. + + If this is set to None, then `setup.py build` will not attempt to rewrite + any `_version.py` in the built tree. If your project does not have any + libraries (e.g. if it only builds a script), then you should use + `versionfile_build = None` and override `distutils.command.build_scripts` + to explicitly insert a copy of `versioneer.get_version()` into your + generated script. + +* `tag_prefix`: + + a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. + If your tags look like 'myproject-1.2.0', then you should use + tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this + should be an empty string. + +* `parentdir_prefix`: + + a optional string, frequently the same as tag_prefix, which appears at the + start of all unpacked tarball filenames. If your tarball unpacks into + 'myproject-1.2.0', this should be 'myproject-'. To disable this feature, + just omit the field from your `setup.cfg`. + +This tool provides one script, named `versioneer`. That script has one mode, +"install", which writes a copy of `versioneer.py` into the current directory +and runs `versioneer.py setup` to finish the installation. + +To versioneer-enable your project: + +* 1: Modify your `setup.cfg`, adding a section named `[versioneer]` and + populating it with the configuration values you decided earlier (note that + the option names are not case-sensitive): + + ```` + [versioneer] + VCS = git + style = pep440 + versionfile_source = src/myproject/_version.py + versionfile_build = myproject/_version.py + tag_prefix = "" + parentdir_prefix = myproject- + ```` + +* 2: Run `versioneer install`. This will do the following: + + * copy `versioneer.py` into the top of your source tree + * create `_version.py` in the right place (`versionfile_source`) + * modify your `__init__.py` (if one exists next to `_version.py`) to define + `__version__` (by calling a function from `_version.py`) + * modify your `MANIFEST.in` to include both `versioneer.py` and the + generated `_version.py` in sdist tarballs + + `versioneer install` will complain about any problems it finds with your + `setup.py` or `setup.cfg`. Run it multiple times until you have fixed all + the problems. + +* 3: add a `import versioneer` to your setup.py, and add the following + arguments to the setup() call: + + version=versioneer.get_version(), + cmdclass=versioneer.get_cmdclass(), + +* 4: commit these changes to your VCS. To make sure you won't forget, + `versioneer install` will mark everything it touched for addition using + `git add`. Don't forget to add `setup.py` and `setup.cfg` too. + +## Post-Installation Usage + +Once established, all uses of your tree from a VCS checkout should get the +current version string. All generated tarballs should include an embedded +version string (so users who unpack them will not need a VCS tool installed). + +If you distribute your project through PyPI, then the release process should +boil down to two steps: + +* 1: git tag 1.0 +* 2: python setup.py register sdist upload + +If you distribute it through github (i.e. users use github to generate +tarballs with `git archive`), the process is: + +* 1: git tag 1.0 +* 2: git push; git push --tags + +Versioneer will report "0+untagged.NUMCOMMITS.gHASH" until your tree has at +least one tag in its history. + +## Version-String Flavors + +Code which uses Versioneer can learn about its version string at runtime by +importing `_version` from your main `__init__.py` file and running the +`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can +import the top-level `versioneer.py` and run `get_versions()`. + +Both functions return a dictionary with different flavors of version +information: + +* `['version']`: A condensed version string, rendered using the selected + style. This is the most commonly used value for the project's version + string. The default "pep440" style yields strings like `0.11`, + `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section + below for alternative styles. + +* `['full-revisionid']`: detailed revision identifier. For Git, this is the + full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". + +* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that + this is only accurate if run in a VCS checkout, otherwise it is likely to + be False or None + +* `['error']`: if the version string could not be computed, this will be set + to a string describing the problem, otherwise it will be None. It may be + useful to throw an exception in setup.py if this is set, to avoid e.g. + creating tarballs with a version string of "unknown". + +Some variants are more useful than others. Including `full-revisionid` in a +bug report should allow developers to reconstruct the exact code being tested +(or indicate the presence of local changes that should be shared with the +developers). `version` is suitable for display in an "about" box or a CLI +`--version` output: it can be easily compared against release notes and lists +of bugs fixed in various releases. + +The installer adds the following text to your `__init__.py` to place a basic +version in `YOURPROJECT.__version__`: + + from ._version import get_versions + __version__ = get_versions()['version'] + del get_versions + +## Styles + +The setup.cfg `style=` configuration controls how the VCS information is +rendered into a version string. + +The default style, "pep440", produces a PEP440-compliant string, equal to the +un-prefixed tag name for actual releases, and containing an additional "local +version" section with more detail for in-between builds. For Git, this is +TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags +--dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the +tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and +that this commit is two revisions ("+2") beyond the "0.11" tag. For released +software (exactly equal to a known tag), the identifier will only contain the +stripped tag, e.g. "0.11". + +Other styles are available. See details.md in the Versioneer source tree for +descriptions. + +## Debugging + +Versioneer tries to avoid fatal errors: if something goes wrong, it will tend +to return a version of "0+unknown". To investigate the problem, run `setup.py +version`, which will run the version-lookup code in a verbose mode, and will +display the full contents of `get_versions()` (including the `error` string, +which may help identify what went wrong). + +## Updating Versioneer + +To upgrade your project to a new release of Versioneer, do the following: + +* install the new Versioneer (`pip install -U versioneer` or equivalent) +* edit `setup.cfg`, if necessary, to include any new configuration settings + indicated by the release notes +* re-run `versioneer install` in your source tree, to replace + `SRC/_version.py` +* commit any changed files + +### Upgrading to 0.15 + +Starting with this version, Versioneer is configured with a `[versioneer]` +section in your `setup.cfg` file. Earlier versions required the `setup.py` to +set attributes on the `versioneer` module immediately after import. The new +version will refuse to run (raising an exception during import) until you +have provided the necessary `setup.cfg` section. + +In addition, the Versioneer package provides an executable named +`versioneer`, and the installation process is driven by running `versioneer +install`. In 0.14 and earlier, the executable was named +`versioneer-installer` and was run without an argument. + +### Upgrading to 0.14 + +0.14 changes the format of the version string. 0.13 and earlier used +hyphen-separated strings like "0.11-2-g1076c97-dirty". 0.14 and beyond use a +plus-separated "local version" section strings, with dot-separated +components, like "0.11+2.g1076c97". PEP440-strict tools did not like the old +format, but should be ok with the new one. + +### Upgrading from 0.11 to 0.12 + +Nothing special. + +### Upgrading from 0.10 to 0.11 + +You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running +`setup.py setup_versioneer`. This will enable the use of additional +version-control systems (SVN, etc) in the future. + +## Future Directions + +This tool is designed to make it easily extended to other version-control +systems: all VCS-specific components are in separate directories like +src/git/ . The top-level `versioneer.py` script is assembled from these +components by running make-versioneer.py . In the future, make-versioneer.py +will take a VCS name as an argument, and will construct a version of +`versioneer.py` that is specific to the given VCS. It might also take the +configuration arguments that are currently provided manually during +installation by editing setup.py . Alternatively, it might go the other +direction and include code from all supported VCS systems, reducing the +number of intermediate scripts. + + +## License + +To make Versioneer easier to embed, all its code is hereby released into the +public domain. The `_version.py` that it creates is also in the public +domain. + +""" + +from __future__ import print_function +try: + import configparser +except ImportError: + import ConfigParser as configparser +import errno +import json +import os +import re +import subprocess +import sys + + +class VersioneerConfig: + pass + + +def get_root(): + # we require that all commands are run from the project root, i.e. the + # directory that contains setup.py, setup.cfg, and versioneer.py . + root = os.path.realpath(os.path.abspath(os.getcwd())) + setup_py = os.path.join(root, "setup.py") + versioneer_py = os.path.join(root, "versioneer.py") + if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): + # allow 'python path/to/setup.py COMMAND' + root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) + setup_py = os.path.join(root, "setup.py") + versioneer_py = os.path.join(root, "versioneer.py") + if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): + err = ("Versioneer was unable to run the project root directory. " + "Versioneer requires setup.py to be executed from " + "its immediate directory (like 'python setup.py COMMAND'), " + "or in a way that lets it use sys.argv[0] to find the root " + "(like 'python path/to/setup.py COMMAND').") + raise VersioneerBadRootError(err) + try: + # Certain runtime workflows (setup.py install/develop in a setuptools + # tree) execute all dependencies in a single python process, so + # "versioneer" may be imported multiple times, and python's shared + # module-import table will cache the first one. So we can't use + # os.path.dirname(__file__), as that will find whichever + # versioneer.py was first imported, even in later projects. + me = os.path.realpath(os.path.abspath(__file__)) + if os.path.splitext(me)[0] != os.path.splitext(versioneer_py)[0]: + print("Warning: build in %s is using versioneer.py from %s" + % (os.path.dirname(me), versioneer_py)) + except NameError: + pass + return root + + +def get_config_from_root(root): + # This might raise EnvironmentError (if setup.cfg is missing), or + # configparser.NoSectionError (if it lacks a [versioneer] section), or + # configparser.NoOptionError (if it lacks "VCS="). See the docstring at + # the top of versioneer.py for instructions on writing your setup.cfg . + setup_cfg = os.path.join(root, "setup.cfg") + parser = configparser.SafeConfigParser() + with open(setup_cfg, "r") as f: + parser.readfp(f) + VCS = parser.get("versioneer", "VCS") # mandatory + + def get(parser, name): + if parser.has_option("versioneer", name): + return parser.get("versioneer", name) + return None + cfg = VersioneerConfig() + cfg.VCS = VCS + cfg.style = get(parser, "style") or "" + cfg.versionfile_source = get(parser, "versionfile_source") + cfg.versionfile_build = get(parser, "versionfile_build") + cfg.tag_prefix = get(parser, "tag_prefix") + cfg.parentdir_prefix = get(parser, "parentdir_prefix") + cfg.verbose = get(parser, "verbose") + return cfg + + +class NotThisMethod(Exception): + pass + +# these dictionaries contain VCS-specific tools +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + def decorate(f): + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % dispcmd) + print(e) + return None + else: + if verbose: + print("unable to find command, tried %s" % (commands,)) + return None + stdout = p.communicate()[0].strip() + if sys.version_info[0] >= 3: + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %s (error)" % dispcmd) + return None + return stdout +LONG_VERSION_PY['git'] = ''' +# This file helps to compute a version number in source trees obtained from +# git-archive tarball (such as those provided by githubs download-from-tag +# feature). Distribution tarballs (built by setup.py sdist) and build +# directories (produced by setup.py build) will contain a much shorter file +# that just contains the computed version number. + +# This file is released into the public domain. Generated by +# versioneer-0.15 (https://github.com/warner/python-versioneer) + +import errno +import os +import re +import subprocess +import sys + + +def get_keywords(): + # these strings will be replaced by git during git-archive. + # setup.py/versioneer.py will grep for the variable names, so they must + # each be defined on a line of their own. _version.py will just call + # get_keywords(). + git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" + git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" + keywords = {"refnames": git_refnames, "full": git_full} + return keywords + + +class VersioneerConfig: + pass + + +def get_config(): + # these strings are filled in when 'setup.py versioneer' creates + # _version.py + cfg = VersioneerConfig() + cfg.VCS = "git" + cfg.style = "%(STYLE)s" + cfg.tag_prefix = "%(TAG_PREFIX)s" + cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" + cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" + cfg.verbose = False + return cfg + + +class NotThisMethod(Exception): + pass + + +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + def decorate(f): + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %%s" %% dispcmd) + print(e) + return None + else: + if verbose: + print("unable to find command, tried %%s" %% (commands,)) + return None + stdout = p.communicate()[0].strip() + if sys.version_info[0] >= 3: + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %%s (error)" %% dispcmd) + return None + return stdout + + +def versions_from_parentdir(parentdir_prefix, root, verbose): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%%s', but '%%s' doesn't start with " + "prefix '%%s'" %% (root, dirname, parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None} + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): + # the code embedded in _version.py can just fetch the value of these + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} + try: + f = open(versionfile_abs, "r") + for line in f.readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + f.close() + except EnvironmentError: + pass + return keywords + + +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + if not keywords: + raise NotThisMethod("no keywords at all, weird") + refnames = keywords["refnames"].strip() + if refnames.startswith("$Format"): + if verbose: + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %%d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%%s', no digits" %% ",".join(refs-tags)) + if verbose: + print("likely tags: %%s" %% ",".join(sorted(tags))) + for ref in sorted(tags): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + if verbose: + print("picking %%s" %% r) + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None + } + # no suitable tags, so version is "0+unknown", but full hex is still there + if verbose: + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags"} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. + + if not os.path.exists(os.path.join(root, ".git")): + if verbose: + print("no .git in %%s" %% root) + raise NotThisMethod("no .git directory") + + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] + # if there is a tag, this yields TAG-NUM-gHEX[-dirty] + # if there are no tags, this yields HEX[-dirty] (no NUM) + describe_out = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long"], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%%s'" + %% describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%%s' doesn't start with prefix '%%s'" + print(fmt %% (full_tag, tag_prefix)) + pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" + %% (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + + else: + # HEX: no tags + pieces["closest-tag"] = None + count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits + + return pieces + + +def plus_or_dot(pieces): + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + # now build up version string, with post-release "local version + # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + # exceptions: + # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + # TAG[.post.devDISTANCE] . No -dirty + + # exceptions: + # 1: no tags. 0.post.devDISTANCE + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%%d" %% pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%%d" %% pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that + # .dev0 sorts backwards (a dirty tree will appear "older" than the + # corresponding clean one), but you shouldn't be releasing software with + # -dirty anyways. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%%s" %% pieces["short"] + else: + # exception #1 + rendered = "0.post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%%s" %% pieces["short"] + return rendered + + +def render_pep440_old(pieces): + # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty + # --always' + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty + # --always -long'. The distance/hash is unconditional. + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"]} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%%s'" %% style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None} + + +def get_versions(): + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. + + cfg = get_config() + verbose = cfg.verbose + + try: + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, + verbose) + except NotThisMethod: + pass + + try: + root = os.path.realpath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in cfg.versionfile_source.split('/'): + root = os.path.dirname(root) + except NameError: + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree"} + + try: + pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) + return render(pieces, cfg.style) + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + except NotThisMethod: + pass + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to compute version"} +''' + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): + # the code embedded in _version.py can just fetch the value of these + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} + try: + f = open(versionfile_abs, "r") + for line in f.readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + f.close() + except EnvironmentError: + pass + return keywords + + +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + if not keywords: + raise NotThisMethod("no keywords at all, weird") + refnames = keywords["refnames"].strip() + if refnames.startswith("$Format"): + if verbose: + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs-tags)) + if verbose: + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + if verbose: + print("picking %s" % r) + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None + } + # no suitable tags, so version is "0+unknown", but full hex is still there + if verbose: + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags"} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. + + if not os.path.exists(os.path.join(root, ".git")): + if verbose: + print("no .git in %s" % root) + raise NotThisMethod("no .git directory") + + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] + # if there is a tag, this yields TAG-NUM-gHEX[-dirty] + # if there are no tags, this yields HEX[-dirty] (no NUM) + describe_out = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long"], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (full_tag, tag_prefix)) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + + else: + # HEX: no tags + pieces["closest-tag"] = None + count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits + + return pieces + + +def do_vcs_install(manifest_in, versionfile_source, ipy): + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] + files = [manifest_in, versionfile_source] + if ipy: + files.append(ipy) + try: + me = __file__ + if me.endswith(".pyc") or me.endswith(".pyo"): + me = os.path.splitext(me)[0] + ".py" + versioneer_file = os.path.relpath(me) + except NameError: + versioneer_file = "versioneer.py" + files.append(versioneer_file) + present = False + try: + f = open(".gitattributes", "r") + for line in f.readlines(): + if line.strip().startswith(versionfile_source): + if "export-subst" in line.strip().split()[1:]: + present = True + f.close() + except EnvironmentError: + pass + if not present: + f = open(".gitattributes", "a+") + f.write("%s export-subst\n" % versionfile_source) + f.close() + files.append(".gitattributes") + run_command(GITS, ["add", "--"] + files) + + +def versions_from_parentdir(parentdir_prefix, root, verbose): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None} + +SHORT_VERSION_PY = """ +# This file was generated by 'versioneer.py' (0.15) from +# revision-control system data, or from the parent directory name of an +# unpacked source archive. Distribution tarballs contain a pre-generated copy +# of this file. + +import json +import sys + +version_json = ''' +%s +''' # END VERSION_JSON + + +def get_versions(): + return json.loads(version_json) +""" + + +def versions_from_file(filename): + try: + with open(filename) as f: + contents = f.read() + except EnvironmentError: + raise NotThisMethod("unable to read _version.py") + mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", + contents, re.M | re.S) + if not mo: + raise NotThisMethod("no version_json in _version.py") + return json.loads(mo.group(1)) + + +def write_to_version_file(filename, versions): + os.unlink(filename) + contents = json.dumps(versions, sort_keys=True, + indent=1, separators=(",", ": ")) + with open(filename, "w") as f: + f.write(SHORT_VERSION_PY % contents) + + print("set %s to '%s'" % (filename, versions["version"])) + + +def plus_or_dot(pieces): + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + # now build up version string, with post-release "local version + # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + # exceptions: + # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + # TAG[.post.devDISTANCE] . No -dirty + + # exceptions: + # 1: no tags. 0.post.devDISTANCE + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%d" % pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%d" % pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that + # .dev0 sorts backwards (a dirty tree will appear "older" than the + # corresponding clean one), but you shouldn't be releasing software with + # -dirty anyways. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + return rendered + + +def render_pep440_old(pieces): + # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + + # exceptions: + # 1: no tags. 0.postDISTANCE[.dev0] + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty + # --always' + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty + # --always -long'. The distance/hash is unconditional. + + # exceptions: + # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"]} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%s'" % style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None} + + +class VersioneerBadRootError(Exception): + pass + + +def get_versions(verbose=False): + # returns dict with two keys: 'version' and 'full' + + if "versioneer" in sys.modules: + # see the discussion in cmdclass.py:get_cmdclass() + del sys.modules["versioneer"] + + root = get_root() + cfg = get_config_from_root(root) + + assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" + handlers = HANDLERS.get(cfg.VCS) + assert handlers, "unrecognized VCS '%s'" % cfg.VCS + verbose = verbose or cfg.verbose + assert cfg.versionfile_source is not None, \ + "please set versioneer.versionfile_source" + assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" + + versionfile_abs = os.path.join(root, cfg.versionfile_source) + + # extract version from first of: _version.py, VCS command (e.g. 'git + # describe'), parentdir. This is meant to work for developers using a + # source checkout, for users of a tarball created by 'setup.py sdist', + # and for users of a tarball/zipball created by 'git archive' or github's + # download-from-tag feature or the equivalent in other VCSes. + + get_keywords_f = handlers.get("get_keywords") + from_keywords_f = handlers.get("keywords") + if get_keywords_f and from_keywords_f: + try: + keywords = get_keywords_f(versionfile_abs) + ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) + if verbose: + print("got version from expanded keyword %s" % ver) + return ver + except NotThisMethod: + pass + + try: + ver = versions_from_file(versionfile_abs) + if verbose: + print("got version from file %s %s" % (versionfile_abs, ver)) + return ver + except NotThisMethod: + pass + + from_vcs_f = handlers.get("pieces_from_vcs") + if from_vcs_f: + try: + pieces = from_vcs_f(cfg.tag_prefix, root, verbose) + ver = render(pieces, cfg.style) + if verbose: + print("got version from VCS %s" % ver) + return ver + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + if verbose: + print("got version from parentdir %s" % ver) + return ver + except NotThisMethod: + pass + + if verbose: + print("unable to compute version") + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, "error": "unable to compute version"} + + +def get_version(): + return get_versions()["version"] + + +def get_cmdclass(): + if "versioneer" in sys.modules: + del sys.modules["versioneer"] + # this fixes the "python setup.py develop" case (also 'install' and + # 'easy_install .'), in which subdependencies of the main project are + # built (using setup.py bdist_egg) in the same python process. Assume + # a main project A and a dependency B, which use different versions + # of Versioneer. A's setup.py imports A's Versioneer, leaving it in + # sys.modules by the time B's setup.py is executed, causing B to run + # with the wrong versioneer. Setuptools wraps the sub-dep builds in a + # sandbox that restores sys.modules to it's pre-build state, so the + # parent is protected against the child's "import versioneer". By + # removing ourselves from sys.modules here, before the child build + # happens, we protect the child from the parent's versioneer too. + # Also see https://github.com/warner/python-versioneer/issues/52 + + cmds = {} + + # we add "version" to both distutils and setuptools + from distutils.core import Command + + class cmd_version(Command): + description = "report generated version string" + user_options = [] + boolean_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + vers = get_versions(verbose=True) + print("Version: %s" % vers["version"]) + print(" full-revisionid: %s" % vers.get("full-revisionid")) + print(" dirty: %s" % vers.get("dirty")) + if vers["error"]: + print(" error: %s" % vers["error"]) + cmds["version"] = cmd_version + + # we override "build_py" in both distutils and setuptools + # + # most invocation pathways end up running build_py: + # distutils/build -> build_py + # distutils/install -> distutils/build ->.. + # setuptools/bdist_wheel -> distutils/install ->.. + # setuptools/bdist_egg -> distutils/install_lib -> build_py + # setuptools/install -> bdist_egg ->.. + # setuptools/develop -> ? + + from distutils.command.build_py import build_py as _build_py + + class cmd_build_py(_build_py): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + _build_py.run(self) + # now locate _version.py in the new build/ directory and replace + # it with an updated value + if cfg.versionfile_build: + target_versionfile = os.path.join(self.build_lib, + cfg.versionfile_build) + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + cmds["build_py"] = cmd_build_py + + if "cx_Freeze" in sys.modules: # cx_freeze enabled? + from cx_Freeze.dist import build_exe as _build_exe + + class cmd_build_exe(_build_exe): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + target_versionfile = cfg.versionfile_source + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + + _build_exe.run(self) + os.unlink(target_versionfile) + with open(cfg.versionfile_source, "w") as f: + LONG = LONG_VERSION_PY[cfg.VCS] + f.write(LONG % + {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) + cmds["build_exe"] = cmd_build_exe + del cmds["build_py"] + + # we override different "sdist" commands for both environments + if "setuptools" in sys.modules: + from setuptools.command.sdist import sdist as _sdist + else: + from distutils.command.sdist import sdist as _sdist + + class cmd_sdist(_sdist): + def run(self): + versions = get_versions() + self._versioneer_generated_versions = versions + # unless we update this, the command will keep using the old + # version + self.distribution.metadata.version = versions["version"] + return _sdist.run(self) + + def make_release_tree(self, base_dir, files): + root = get_root() + cfg = get_config_from_root(root) + _sdist.make_release_tree(self, base_dir, files) + # now locate _version.py in the new base_dir directory + # (remembering that it may be a hardlink) and replace it with an + # updated value + target_versionfile = os.path.join(base_dir, cfg.versionfile_source) + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, + self._versioneer_generated_versions) + cmds["sdist"] = cmd_sdist + + return cmds + + +CONFIG_ERROR = """ +setup.cfg is missing the necessary Versioneer configuration. You need +a section like: + + [versioneer] + VCS = git + style = pep440 + versionfile_source = src/myproject/_version.py + versionfile_build = myproject/_version.py + tag_prefix = "" + parentdir_prefix = myproject- + +You will also need to edit your setup.py to use the results: + + import versioneer + setup(version=versioneer.get_version(), + cmdclass=versioneer.get_cmdclass(), ...) + +Please read the docstring in ./versioneer.py for configuration instructions, +edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. +""" + +SAMPLE_CONFIG = """ +# See the docstring in versioneer.py for instructions. Note that you must +# re-run 'versioneer.py setup' after changing this section, and commit the +# resulting files. + +[versioneer] +#VCS = git +#style = pep440 +#versionfile_source = +#versionfile_build = +#tag_prefix = +#parentdir_prefix = + +""" + +INIT_PY_SNIPPET = """ +from ._version import get_versions +__version__ = get_versions()['version'] +del get_versions +""" + + +def do_setup(): + root = get_root() + try: + cfg = get_config_from_root(root) + except (EnvironmentError, configparser.NoSectionError, + configparser.NoOptionError) as e: + if isinstance(e, (EnvironmentError, configparser.NoSectionError)): + print("Adding sample versioneer config to setup.cfg", + file=sys.stderr) + with open(os.path.join(root, "setup.cfg"), "a") as f: + f.write(SAMPLE_CONFIG) + print(CONFIG_ERROR, file=sys.stderr) + return 1 + + print(" creating %s" % cfg.versionfile_source) + with open(cfg.versionfile_source, "w") as f: + LONG = LONG_VERSION_PY[cfg.VCS] + f.write(LONG % {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) + + ipy = os.path.join(os.path.dirname(cfg.versionfile_source), + "__init__.py") + if os.path.exists(ipy): + try: + with open(ipy, "r") as f: + old = f.read() + except EnvironmentError: + old = "" + if INIT_PY_SNIPPET not in old: + print(" appending to %s" % ipy) + with open(ipy, "a") as f: + f.write(INIT_PY_SNIPPET) + else: + print(" %s unmodified" % ipy) + else: + print(" %s doesn't exist, ok" % ipy) + ipy = None + + # Make sure both the top-level "versioneer.py" and versionfile_source + # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so + # they'll be copied into source distributions. Pip won't be able to + # install the package without this. + manifest_in = os.path.join(root, "MANIFEST.in") + simple_includes = set() + try: + with open(manifest_in, "r") as f: + for line in f: + if line.startswith("include "): + for include in line.split()[1:]: + simple_includes.add(include) + except EnvironmentError: + pass + # That doesn't cover everything MANIFEST.in can do + # (http://docs.python.org/2/distutils/sourcedist.html#commands), so + # it might give some false negatives. Appending redundant 'include' + # lines is safe, though. + if "versioneer.py" not in simple_includes: + print(" appending 'versioneer.py' to MANIFEST.in") + with open(manifest_in, "a") as f: + f.write("include versioneer.py\n") + else: + print(" 'versioneer.py' already in MANIFEST.in") + if cfg.versionfile_source not in simple_includes: + print(" appending versionfile_source ('%s') to MANIFEST.in" % + cfg.versionfile_source) + with open(manifest_in, "a") as f: + f.write("include %s\n" % cfg.versionfile_source) + else: + print(" versionfile_source already in MANIFEST.in") + + # Make VCS-specific changes. For git, this means creating/changing + # .gitattributes to mark _version.py for export-time keyword + # substitution. + do_vcs_install(manifest_in, cfg.versionfile_source, ipy) + return 0 + + +def scan_setup_py(): + found = set() + setters = False + errors = 0 + with open("setup.py", "r") as f: + for line in f.readlines(): + if "import versioneer" in line: + found.add("import") + if "versioneer.get_cmdclass()" in line: + found.add("cmdclass") + if "versioneer.get_version()" in line: + found.add("get_version") + if "versioneer.VCS" in line: + setters = True + if "versioneer.versionfile_source" in line: + setters = True + if len(found) != 3: + print("") + print("Your setup.py appears to be missing some important items") + print("(but I might be wrong). Please make sure it has something") + print("roughly like the following:") + print("") + print(" import versioneer") + print(" setup( version=versioneer.get_version(),") + print(" cmdclass=versioneer.get_cmdclass(), ...)") + print("") + errors += 1 + if setters: + print("You should remove lines like 'versioneer.VCS = ' and") + print("'versioneer.versionfile_source = ' . This configuration") + print("now lives in setup.cfg, and should be removed from setup.py") + print("") + errors += 1 + return errors + +if __name__ == "__main__": + cmd = sys.argv[1] + if cmd == "setup": + errors = do_setup() + errors += scan_setup_py() + if errors: + sys.exit(1) From 46d8d4b8dc9b21abf17eacf946dad0bc1ceb1a8f Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 10:14:49 -0500 Subject: [PATCH 041/212] use local repository to build conda packages --- conda.recipe/meta.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 242fb4c..3b416ca 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -3,7 +3,7 @@ package: version: master source: - git_url: https://github.com/python-quantities/python-quantities.git + git_url: ../ git_tag: master build: @@ -24,4 +24,3 @@ about: license: BSD home: http://pythonhosted.org//quantities/ summary: Physical quantities with units, based upon Numpy - From 4518bd6625771b5038a69c67d839b9c5020fdc40 Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 10:15:54 -0500 Subject: [PATCH 042/212] tweak doc html theme --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index c4fad27..0589cbd 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -97,7 +97,7 @@ # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'sphinxdoc' +#html_theme = 'sphinxdoc' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the From fafb3d08fef3023fbefea1e8196736cee4bb18dc Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 10:23:06 -0500 Subject: [PATCH 043/212] fixes comprehension of % in compound units, closes #35 --- quantities/registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quantities/registry.py b/quantities/registry.py index 0cd57bc..76eadd4 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -33,7 +33,7 @@ def __setitem__(self, string, val): if val == self.__context[string]: return raise KeyError( - '%s has already been registered for %s' + '%s has already been registered for %s' % (string, self.__context[string]) ) self.__context[string] = val @@ -55,7 +55,7 @@ def __getitem__(self, label): # make sure we can parse the label .... if label == '': label = 'dimensionless' - if label == "%": label = "percent" + if "%" in label: label = label.replace("%", "percent") if label.lower() == "in": label = "inch" return self.__registry[label] From 3a06e323e31de5d93ca7f21bd8c5fea4d622787f Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 10:31:58 -0500 Subject: [PATCH 044/212] adds mbar alias, closes #72 --- quantities/units/pressure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/units/pressure.py b/quantities/units/pressure.py index 81561c3..b5b76cf 100644 --- a/quantities/units/pressure.py +++ b/quantities/units/pressure.py @@ -52,7 +52,7 @@ 100000*pascal, aliases=['bars'] ) -mb = millibar = UnitQuantity( +mb = mbar = millibar = UnitQuantity( 'millibar', 0.001*bar, symbol='mb', From fe3effb20884a4972c0f3678a4433721d647624a Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 10:35:42 -0500 Subject: [PATCH 045/212] adds support for deepcopy, closes #27 --- quantities/quantity.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quantities/quantity.py b/quantities/quantity.py index c2b7875..fa4fa32 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -623,6 +623,10 @@ def __reduce__(self): (self.__class__, np.ndarray, (0, ), 'b', ), self.__getstate__()) + def __deepcopy__(self, memo_dict): + # constructor copies by default + return Quantity(self.magnitude, self.dimensionality) + def _reconstruct_quantity(subtype, baseclass, baseshape, basetype,): """Internal function that builds a new MaskedArray from the From 4920d35e29137e85328fb060466cf02424ef32be Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 10:49:40 -0500 Subject: [PATCH 046/212] fixes encoding problem --- quantities/units/substance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quantities/units/substance.py b/quantities/units/substance.py index 68c102d..0dad28b 100644 --- a/quantities/units/substance.py +++ b/quantities/units/substance.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ """ from __future__ import absolute_import From 1ebf81e445c22c668c938ca275856a74afa0e1f0 Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 10:59:35 -0500 Subject: [PATCH 047/212] fixes argmax --- quantities/quantity.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 1ef50ef..6c2997b 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -439,7 +439,7 @@ def nansum(self, axis=None, dtype=None, out=None): self.dimensionality, copy=False ) - + @with_doc(np.ndarray.fill) def fill(self, value): self.magnitude.fill(value) @@ -492,7 +492,7 @@ def max(self, axis=None, out=None): out._dimensionality = dim return out - @with_doc(np.ndarray.argmin) + @with_doc(np.ndarray.argmax) def argmax(self, axis=None, out=None): return self.magnitude.argmax(axis, out) @@ -527,18 +527,14 @@ def nanmin(self, axis=None, out=None): def argmin(self, axis=None, out=None): return self.magnitude.argmin(axis, out) - @with_doc(np.ndarray.argmax) - def argmax(self,axis=None, out=None): - return self.magnitude.argmax() - @with_doc(np.nanargmin) def nanargmin(self,axis=None, out=None): return np.nanargmin(self.magnitude) - + @with_doc(np.nanargmax) def nanargmax(self,axis=None, out=None): return np.nanargmax(self.magnitude) - + @with_doc(np.ndarray.ptp) def ptp(self, axis=None, out=None): ret = self.magnitude.ptp(axis, None if out is None else out.magnitude) @@ -655,7 +651,7 @@ def nanstd(self, axis=None, dtype=None, out=None, ddof=0): self._dimensionality, copy=False ) - + @with_doc(np.ndarray.prod) def prod(self, axis=None, dtype=None, out=None): if axis == None: From aa462812bd2cf4460e1631113cc8a1781ac0d5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlhren?= Date: Wed, 16 Dec 2015 17:00:10 +0100 Subject: [PATCH 048/212] Make setup.py test exit with exit status 1 when failing --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e34ec28..0ea08b9 100755 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ import versioneer +TEST_RESULT = None cmdclass = versioneer.get_cmdclass() From 59f5c8a2c84dc2273e393e006aee6c45df794208 Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Wed, 16 Dec 2015 11:19:45 -0500 Subject: [PATCH 049/212] fixes a unit test, use q.nansum method, not np.nansum func --- quantities/tests/test_uncertainty.py | 7 ++----- quantities/uncertainquantity.py | 5 ++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index 6ffa3c4..bbec1a1 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -72,7 +72,7 @@ def test_uncertaintity_nanmean(self): a = UncertainQuantity([1,2], 'm', [.1,.2]) b = UncertainQuantity([1,2,np.nan], 'm', [.1,.2,np.nan]) self.assertQuantityEqual(a.mean(),b.nanmean()) - + def test_uncertainquantity_subtract(self): a = UncertainQuantity(1, 'm', 1) b = a.copy() # different object @@ -87,7 +87,7 @@ def test_uncertainty_nansum(self): uq = UncertainQuantity([1,2], 'm', [1,1]) uq_nan = UncertainQuantity([1,2,np.nan], 'm', [1,1,np.nan]) self.assertQuantityEqual(np.sum(uq), np.nansum(uq)) - self.assertQuantityEqual(np.sum(uq), np.nansum(uq_nan)) + self.assertQuantityEqual(np.sum(uq), uq_nan.nansum()) def test_uncertainty_minmax_nan_arg(self): q = [[1, 2], [3, 4]] * pq.m # quantity @@ -114,6 +114,3 @@ def test_uncertainty_minmax_nan_arg(self): self.assertQuantityEqual(nanuq.nanmax(), 4*pq.m) # max self.assertQuantityEqual(nanuq.nanargmin(), 0) # argmin self.assertQuantityEqual(nanuq.nanargmax(), 3) # argmax - - - diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index 2c08232..ff20f2a 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -212,7 +212,6 @@ def sum(self, axis=None, dtype=None, out=None): @with_doc(np.nansum) def nansum(self, axis=None, dtype=None, out=None): - import numpy as np return UncertainQuantity( np.nansum(self.magnitude, axis, dtype, out), self.dimensionality, @@ -272,11 +271,11 @@ def argmax(self,axis=None, out=None): @with_doc(np.nanargmin) def nanargmin(self,axis=None, out=None): return np.nanargmin(self.magnitude) - + @with_doc(np.nanargmax) def nanargmax(self,axis=None, out=None): return np.nanargmax(self.magnitude) - + def __getstate__(self): """ Return the internal state of the quantity, for pickling From 1948072e6e744672895cc406e9b14b5a7be4d7ec Mon Sep 17 00:00:00 2001 From: Darren Dale Date: Thu, 17 Dec 2015 16:40:59 -0500 Subject: [PATCH 050/212] reverts 46c2efd, closes issue #100 --- quantities/tests/test_uncertainty.py | 6 ------ quantities/uncertainquantity.py | 5 +---- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index bbec1a1..3733725 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -73,12 +73,6 @@ def test_uncertaintity_nanmean(self): b = UncertainQuantity([1,2,np.nan], 'm', [.1,.2,np.nan]) self.assertQuantityEqual(a.mean(),b.nanmean()) - def test_uncertainquantity_subtract(self): - a = UncertainQuantity(1, 'm', 1) - b = a.copy() # different object - self.assertQuantityEqual(a-a, UncertainQuantity(0, 'm', 0)) - self.assertQuantityEqual(a-b, UncertainQuantity(0, 'm', np.sqrt(2))) - def test_uncertainty_sqrt(self): a = UncertainQuantity([1,2], 'm', [.1,.2]) self.assertQuantityEqual(a**0.5, a.sqrt()) diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index ff20f2a..a09df63 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -102,10 +102,7 @@ def __radd__(self, other): @scale_other_units def __sub__(self, other): res = super(UncertainQuantity, self).__sub__(other) - if self is not other: - u = (self.uncertainty**2+other.uncertainty**2)**0.5 - else: - u = self.uncertainty*0 + u = (self.uncertainty**2+other.uncertainty**2)**0.5 return UncertainQuantity(res, uncertainty=u, copy=False) @with_doc(Quantity.__rsub__, use_header=False) From c12292341d3762ffa280eb2b3ef8c05134ff51d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlhren?= Date: Tue, 22 Dec 2015 22:46:27 +0100 Subject: [PATCH 051/212] Bump NumPy requirement to 1.8.2 --- .travis.yml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b292ede..f1620ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: python sudo: false env: matrix: - - NUMPY_VERSION="1.7.1" + - NUMPY_VERSION="1.8.2" - NUMPY_VERSION="1.9.1" python: - 2.6 diff --git a/setup.py b/setup.py index 0ea08b9..1361a46 100755 --- a/setup.py +++ b/setup.py @@ -140,7 +140,7 @@ def run(self): platforms = 'Any', requires = [ 'python (>=2.6.0)', - 'numpy (>=1.4.0)', + 'numpy (>=1.8.2)', ], url = 'http://packages.python.org/quantities', version = versioneer.get_version(), From 20c58f6c1465745c009833973f496fe95130e4a7 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Mon, 14 May 2012 22:59:50 -0500 Subject: [PATCH 052/212] Wrap imag and real attributes. This allows easily writing setter methods for them so that we can handle units on set. --- quantities/quantity.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/quantities/quantity.py b/quantities/quantity.py index 6c2997b..83e053d 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -143,6 +143,22 @@ def _reference(self): def magnitude(self): return self.view(type=np.ndarray) + @property + def real(self): + return Quantity(self.magnitude.real, self.dimensionality) + + @real.setter + def real(self, r): + self.magnitude.real = Quantity(r, self.dimensionality).magnitude + + @property + def imag(self): + return Quantity(self.magnitude.imag, self.dimensionality) + + @imag.setter + def imag(self, i): + self.magnitude.imag = Quantity(i, self.dimensionality).magnitude + @property def simplified(self): rq = 1*unit_registry['dimensionless'] From d1a19a5d84f92a70488832d3b5cbb0927f11fcc5 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Mon, 14 May 2012 23:00:47 -0500 Subject: [PATCH 053/212] Fix use of call () on simplified. --- quantities/decorators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quantities/decorators.py b/quantities/decorators.py index 3cd2ec1..5c701f0 100644 --- a/quantities/decorators.py +++ b/quantities/decorators.py @@ -99,7 +99,7 @@ def wrapped_function(*args , **kwargs): #test if the argument is a quantity if isinstance(args[i], Quantity): #convert the units to the base units - args[i] = args[i].simplified() + args[i] = args[i].simplified #view the array as an ndarray args[i] = args[i].magnitude @@ -142,7 +142,7 @@ def wrapped_function(*args , **kwargs): result[i] = Quantity( result[i], handler_quantities[i] - .dimensionality.simplified() + .dimensionality.simplified ) #now convert the quantity to the appropriate units result[i] = result[i].rescale( From 92f914a71a71ee53b943c819e465917919636aa7 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Wed, 10 Feb 2016 11:24:03 -0700 Subject: [PATCH 054/212] Add tests for imag/real. --- quantities/tests/test_methods.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index c5c2313..039fb6d 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -299,6 +299,16 @@ def test_conj(self): self.assertQuantityEqual((self.q*(1+1j)).conj(), self.q*(1-1j)) self.assertQuantityEqual((self.q*(1+1j)).conjugate(), self.q*(1-1j)) + def test_real(self): + test_q = self.q * (1 + 1j) + test_q.real = [[39.3701, 39.3701], [39.3701, 39.3701]] * pq.inch + self.assertQuantityEqual(test_q.real, [[1., 1.], [1., 1.]] * pq.m) + + def test_imag(self): + test_q = self.q * (1 + 1j) + test_q.imag = [[39.3701, 39.3701], [39.3701, 39.3701]] * pq.inch + self.assertQuantityEqual(test_q.imag, [[1., 1.], [1., 1.]] * pq.m) + def test_getitem(self): self.assertRaises(IndexError, self.q.__getitem__, (0,10)) self.assertQuantityEqual(self.q[0], [1,2]*pq.m) From 134085473b8fc124bf6c3e717b2bfdfd1c50f33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Fri, 22 Apr 2016 14:29:12 +0200 Subject: [PATCH 055/212] Add property html to Dimensionality --- quantities/dimensionality.py | 4 ++ quantities/markup.py | 53 ++++++++++++++++++++----- quantities/tests/test_dimensionality.py | 2 + 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index 6bccf89..b2ab0e5 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -52,6 +52,10 @@ def unicode(self): def latex(self): return markup.format_units_latex(self) + @property + def html(self): + return markup.format_units_html(self) + def __hash__(self): res = hash(unit_registry['dimensionless']) for key in sorted(self.keys(), key=operator.attrgetter('format_order')): diff --git a/quantities/markup.py b/quantities/markup.py index bc98e04..8714cca 100644 --- a/quantities/markup.py +++ b/quantities/markup.py @@ -89,27 +89,27 @@ def format_units_unicode(udict): def format_units_latex(udict,font='mathrm',mult=r'\cdot',paren=False): ''' Replace the units string provided with an equivalent latex string. - + Division (a/b) will be replaced by \frac{a}{b}. - + Exponentiation (m**2) will be replaced with superscripts (m^{2}) - - The latex is set with the font argument, and the default is the normal, - non-italicized font mathrm. Other useful options include 'mathnormal', + + The latex is set with the font argument, and the default is the normal, + non-italicized font mathrm. Other useful options include 'mathnormal', 'mathit', 'mathsf', and 'mathtt'. - + Multiplication (*) are replaced with the symbol specified by the mult argument. By default this is the latex \cdot symbol. Other useful options may be '' or '*'. - + If paren=True, encapsulate the string in '\left(' and '\right)' - + The result of format_units_latex is encapsulated in $. This allows the result to be used directly in Latex in normal text mode, or in Matplotlib text via the MathText feature. - + Restrictions: - This routine will not put CompoundUnits into a fractional form. + This routine will not put CompoundUnits into a fractional form. ''' res = format_units(udict) if res.startswith('(') and res.endswith(')'): @@ -128,3 +128,36 @@ def format_units_latex(udict,font='mathrm',mult=r'\cdot',paren=False): res = r'\left(%s\right)' % res res = r'$\%s{%s}$' % (font,res) return res + + +def format_units_html(udict,font='%s',mult=r'⋅',paren=False): + ''' + Replace the units string provided with an equivalent html string. + + Exponentiation (m**2) will be replaced with superscripts (m2}) + + No formating is done, change `font` argument to e.g.: + '%s' to have text be colored blue. + + Multiplication (*) are replaced with the symbol specified by the mult + argument. By default this is the latex ⋅ symbol. Other useful options + may be '' or '*'. + + If paren=True, encapsulate the string in '(' and ')' + + ''' + res = format_units(udict) + if res.startswith('(') and res.endswith(')'): + # Compound Unit + compound = True + else: + # Not a compound unit + compound = False + # Replace exponentiation (**exp) with ^{exp} + res = re.sub(r'\*{2,2}(?P\d+)',r'\g',res) + # Remove multiplication signs + res = re.sub(r'\*',mult,res) + if paren and not compound: + res = '(%s)' % res + res = font % res + return res diff --git a/quantities/tests/test_dimensionality.py b/quantities/tests/test_dimensionality.py index 9fcc6d9..6b1597e 100644 --- a/quantities/tests/test_dimensionality.py +++ b/quantities/tests/test_dimensionality.py @@ -14,6 +14,7 @@ joule_str = 'kg*m**2/s**2' joule_uni = 'kg·m²/s²' joule_tex = r'$\mathrm{\frac{kg{\cdot}m^{2}}{s^{2}}}$' +joule_htm = 'kg⋅m2/s2' Joule = Dimensionality({pq.J: 1}) Joule_str = 'J' @@ -24,6 +25,7 @@ def test_dimensionality_str(self): self.assertEqual(joule.string, joule_str) self.assertEqual(joule.unicode, joule_uni) self.assertEqual(joule.latex, joule_tex) + self.assertEqual(joule.html, joule_htm) self.assertEqual(Joule.string, 'J') def test_equality(self): From dcbb872a0795883a82c241b172cf5a8429caf420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Fri, 27 May 2016 14:58:04 +0200 Subject: [PATCH 056/212] Fixes gh-115: UncertainQuantity.__neg__ --- quantities/tests/test_uncertainty.py | 7 +++++++ quantities/uncertainquantity.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index 3733725..bcac52d 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -51,6 +51,13 @@ def test_uncertainquantity_multiply(self): self.assertQuantityEqual(a*2, [2, 4]*pq.m) self.assertQuantityEqual((a*2).uncertainty, [0.2,0.4]*pq.m) + def test_uncertainquantity_negative(self): + a = UncertainQuantity([1, 2], 'm', [.1, .2]) + self.assertQuantityEqual(-a, [-1., -2.]*pq.m) + self.assertQuantityEqual((-a).uncertainty, [0.1, 0.2]*pq.m) + self.assertQuantityEqual(-a, a*-1) + self.assertQuantityEqual((-a).uncertainty, (a*-1).uncertainty) + def test_uncertainquantity_divide(self): a = UncertainQuantity([1, 2], 'm', [.1, .2]) self.assertQuantityEqual(a/a, [1., 1.]) diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index a09df63..b645950 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -132,6 +132,9 @@ def __mul__(self, other): def __rmul__(self, other): return self.__mul__(other) + def __neg__(self): + return self*-1 + @with_doc(Quantity.__truediv__, use_header=False) def __truediv__(self, other): res = super(UncertainQuantity, self).__truediv__(other) From 5f6a07a61a17b37b493ca44b2e99629d5409f922 Mon Sep 17 00:00:00 2001 From: Espen Hagen Date: Thu, 21 Jul 2016 12:23:23 +0200 Subject: [PATCH 057/212] fixed unit siemens, S, mS etc. --- quantities/units/electromagnetism.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quantities/units/electromagnetism.py b/quantities/units/electromagnetism.py index 38bf5e8..88dce95 100644 --- a/quantities/units/electromagnetism.py +++ b/quantities/units/electromagnetism.py @@ -151,23 +151,23 @@ A/V, symbol='S' ) -mS = siemens = UnitQuantity( +mS = millisiemens = UnitQuantity( 'millisiemens', S/1000, symbol='mS' ) -uS = siemens = UnitQuantity( +uS = microsiemens = UnitQuantity( 'microsiemens', mS/1000, symbol='uS', u_symbol='μS' ) -nS = siemens = UnitQuantity( +nS = nanosiemens = UnitQuantity( 'nanosiemens', uS/1000, symbol='nS' ) -pS = siemens = UnitQuantity( +pS = picosiemens = UnitQuantity( 'picosiemens', nS/1000, symbol='pS' From 5bc396969e738bc39ad6f281d26de14551db4169 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 6 Sep 2016 14:25:09 +0200 Subject: [PATCH 058/212] Added regression tests to test for issue #113 --- quantities/tests/test_persistence.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/quantities/tests/test_persistence.py b/quantities/tests/test_persistence.py index b173689..793d434 100644 --- a/quantities/tests/test_persistence.py +++ b/quantities/tests/test_persistence.py @@ -3,6 +3,7 @@ import pickle from .. import units as pq +from ..quantity import Quantity from ..uncertainquantity import UncertainQuantity from .. import constants from .common import TestCase @@ -33,3 +34,16 @@ def test_unitconstant_persistance(self): x = constants.m_e y = pickle.loads(pickle.dumps(x)) self.assertQuantityEqual(x, y) + + def test_quantity_object_dtype(self): + # Regression test for github issue #113 + x = Quantity(1,dtype=object) + y = pickle.loads(pickle.dumps(x)) + self.assertQuantityEqual(x, y) + + def test_uncertainquantity_object_dtype(self): + # Regression test for github issue #113 + x = UncertainQuantity(20, 'm', 0.2, dtype=object) + y = pickle.loads(pickle.dumps(x)) + self.assertQuantityEqual(x, y) + From 6bb991631b121641f83d24ecd1483ac27104a011 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 6 Sep 2016 14:50:00 +0200 Subject: [PATCH 059/212] fixed a typo in tests --- quantities/tests/test_persistence.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quantities/tests/test_persistence.py b/quantities/tests/test_persistence.py index 793d434..1ca9e72 100644 --- a/quantities/tests/test_persistence.py +++ b/quantities/tests/test_persistence.py @@ -11,7 +11,7 @@ class TestPersistence(TestCase): - def test_unitquantity_persistance(self): + def test_unitquantity_persistence(self): x = pq.m y = pickle.loads(pickle.dumps(x)) self.assertQuantityEqual(x, y) @@ -20,17 +20,17 @@ def test_unitquantity_persistance(self): y = pickle.loads(pickle.dumps(x)) self.assertQuantityEqual(x, y) - def test_quantity_persistance(self): + def test_quantity_persistence(self): x = 20*pq.m y = pickle.loads(pickle.dumps(x)) self.assertQuantityEqual(x, y) - def test_uncertainquantity_persistance(self): + def test_uncertainquantity_persistence(self): x = UncertainQuantity(20, 'm', 0.2) y = pickle.loads(pickle.dumps(x)) self.assertQuantityEqual(x, y) - def test_unitconstant_persistance(self): + def test_unitconstant_persistence(self): x = constants.m_e y = pickle.loads(pickle.dumps(x)) self.assertQuantityEqual(x, y) From 1b25e4d27adcf7138137498ca0ab5d09a205803e Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 6 Sep 2016 14:55:46 +0200 Subject: [PATCH 060/212] fixes issue #113 --- quantities/quantity.py | 25 ++++++------------------- quantities/tests/test_persistence.py | 12 ++++++++++++ quantities/uncertainquantity.py | 24 ++++++++++++------------ 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 6c2997b..33e3bf1 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -697,34 +697,21 @@ def cumprod(self, axis=None, dtype=None, out=None): # list of unsupported functions: [choose] - def __getstate__(self): - """ - Return the internal state of the quantity, for pickling - purposes. - - """ - cf = 'CF'[self.flags.fnc] - state = (1, - self.shape, - self.dtype, - self.flags.fnc, - self.tostring(cf), - self._dimensionality, - ) - return state - def __setstate__(self, state): - (ver, shp, typ, isf, raw, units) = state - np.ndarray.__setstate__(self, (shp, typ, isf, raw)) + ndarray_state = state[:-1] + units = state[-1] + np.ndarray.__setstate__(self, ndarray_state) self._dimensionality = units def __reduce__(self): """ Return a tuple for pickling a Quantity. """ + reconstruct,reconstruct_args,state = super(Quantity,self).__reduce__() + state = state + (self._dimensionality,) return (_reconstruct_quantity, (self.__class__, np.ndarray, (0, ), 'b', ), - self.__getstate__()) + state) def __deepcopy__(self, memo_dict): # constructor copies by default diff --git a/quantities/tests/test_persistence.py b/quantities/tests/test_persistence.py index 1ca9e72..e55b2b4 100644 --- a/quantities/tests/test_persistence.py +++ b/quantities/tests/test_persistence.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import pickle +import copy from .. import units as pq from ..quantity import Quantity @@ -47,3 +48,14 @@ def test_uncertainquantity_object_dtype(self): y = pickle.loads(pickle.dumps(x)) self.assertQuantityEqual(x, y) + def test_copy_quantity(self): + for dtype in [float,object]: + x = (20*pq.m).astype(dtype) + y = pickle.loads(pickle.dumps(x)) + self.assertQuantityEqual(x, y) + + def test_copy_uncertainquantity(self): + for dtype in [float, object]: + x = UncertainQuantity(20, 'm', 0.2).astype(dtype) + y = pickle.loads(pickle.dumps(x)) + self.assertQuantityEqual(x, y) \ No newline at end of file diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index a09df63..e7181bd 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -273,18 +273,18 @@ def nanargmin(self,axis=None, out=None): def nanargmax(self,axis=None, out=None): return np.nanargmax(self.magnitude) - def __getstate__(self): - """ - Return the internal state of the quantity, for pickling - purposes. - - """ - state = list(super(UncertainQuantity, self).__getstate__()) - state.append(self._uncertainty) - return tuple(state) - def __setstate__(self, state): - (ver, shp, typ, isf, raw, units, sigma) = state - np.ndarray.__setstate__(self, (shp, typ, isf, raw)) + ndarray_state = state[:-2] + units, sigma = state[-2:] + np.ndarray.__setstate__(self, ndarray_state) self._dimensionality = units self._uncertainty = sigma + + def __reduce__(self): + """ + Return a tuple for pickling a Quantity. + """ + reconstruct, reconstruct_args, state = super(UncertainQuantity, self).__reduce__() + state = state + (self._uncertainty,) + return reconstruct, reconstruct_args, state + From 1abb9617659f687c94e9d2d7a8e9d489e113f766 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 6 Sep 2016 15:43:21 +0200 Subject: [PATCH 061/212] added regression test to ensure backwards compatibility of changes to pickling --- quantities/tests/test_persistence.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/quantities/tests/test_persistence.py b/quantities/tests/test_persistence.py index e55b2b4..715ee80 100644 --- a/quantities/tests/test_persistence.py +++ b/quantities/tests/test_persistence.py @@ -48,6 +48,24 @@ def test_uncertainquantity_object_dtype(self): y = pickle.loads(pickle.dumps(x)) self.assertQuantityEqual(x, y) + def test_backward_compat(self): + """ A few pickles collected before fixing #113 just to make sure we remain backwards compatible. """ + orig = [ + pq.m, + 20*pq.m, + UncertainQuantity(20, 'm', 0.2), + constants.m_e + ] + data = [ + b'\x80\x02cquantities.unitquantity\nUnitLength\nq\x00(X\x05\x00\x00\x00meterq\x01NX\x01\x00\x00\x00mq\x02N]q\x03(X\x06\x00\x00\x00metersq\x04X\x05\x00\x00\x00metreq\x05X\x06\x00\x00\x00metresq\x06eNtq\x07Rq\x08K\x01K\x02K\x02\x86q\t\x86q\nb.', + b'\x80\x02cquantities.quantity\n_reconstruct_quantity\nq\x00(cquantities.quantity\nQuantity\nq\x01cnumpy\nndarray\nq\x02K\x00\x85q\x03X\x01\x00\x00\x00bq\x04tq\x05Rq\x06(K\x01)cnumpy\ndtype\nq\x07X\x02\x00\x00\x00f8q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00 Date: Tue, 6 Sep 2016 16:07:24 +0200 Subject: [PATCH 062/212] fixed regression test to work both in python 2 and 3, and changed copy test to actually test copy --- quantities/tests/test_persistence.py | 29 +++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/quantities/tests/test_persistence.py b/quantities/tests/test_persistence.py index 715ee80..9dd8699 100644 --- a/quantities/tests/test_persistence.py +++ b/quantities/tests/test_persistence.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import sys import pickle import copy @@ -54,14 +55,24 @@ def test_backward_compat(self): pq.m, 20*pq.m, UncertainQuantity(20, 'm', 0.2), - constants.m_e - ] - data = [ - b'\x80\x02cquantities.unitquantity\nUnitLength\nq\x00(X\x05\x00\x00\x00meterq\x01NX\x01\x00\x00\x00mq\x02N]q\x03(X\x06\x00\x00\x00metersq\x04X\x05\x00\x00\x00metreq\x05X\x06\x00\x00\x00metresq\x06eNtq\x07Rq\x08K\x01K\x02K\x02\x86q\t\x86q\nb.', - b'\x80\x02cquantities.quantity\n_reconstruct_quantity\nq\x00(cquantities.quantity\nQuantity\nq\x01cnumpy\nndarray\nq\x02K\x00\x85q\x03X\x01\x00\x00\x00bq\x04tq\x05Rq\x06(K\x01)cnumpy\ndtype\nq\x07X\x02\x00\x00\x00f8q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00 Date: Tue, 18 Oct 2016 21:28:22 +0200 Subject: [PATCH 063/212] test newer numpy versions --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index f1620ac..958682e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,14 @@ env: matrix: - NUMPY_VERSION="1.8.2" - NUMPY_VERSION="1.9.1" + - NUMPY_VERSION="1.10.4" + - NUMPY_VERSION="1.11.2" python: - 2.6 - 2.7 - 3.3 - 3.4 + - 3.5 before_install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; From a088179d98d2bb5fdf6c4fcb964a80871173d387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Mon, 7 Nov 2016 14:30:51 +0100 Subject: [PATCH 064/212] Add failing test for python3.5/numpy-1.11.2 --- quantities/tests/test_arithmetic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quantities/tests/test_arithmetic.py b/quantities/tests/test_arithmetic.py index 813b89b..5eb7da9 100644 --- a/quantities/tests/test_arithmetic.py +++ b/quantities/tests/test_arithmetic.py @@ -363,6 +363,7 @@ def test_in_place_subtraction(self): def test_powering(self): # test raising a quantity to a power self.assertQuantityEqual((5.5 * pq.cm)**5, (5.5**5) * (pq.cm**5)) + self.assertQuantityEqual((5.5 * pq.cm)**0, (5.5**0) * pq.dimensionless) # must also work with compound units self.assertQuantityEqual((5.5 * pq.J)**5, (5.5**5) * (pq.J**5)) From dc696d6372d8db7b3dabd5cbdb260022c7a7fb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Mon, 7 Nov 2016 14:33:00 +0100 Subject: [PATCH 065/212] use py3.5 on travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f1620ac..15d5fad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,12 @@ sudo: false env: matrix: - NUMPY_VERSION="1.8.2" - - NUMPY_VERSION="1.9.1" + - NUMPY_VERSION="1.11.2" python: - 2.6 - 2.7 - - 3.3 - 3.4 + - 3.5 before_install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; From da0bd6ca3d23a1ecfaabe46a22e36bff0ef53d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Mon, 7 Nov 2016 16:18:05 +0100 Subject: [PATCH 066/212] Fix power to zero with python3.5 and numpy 1.11.2 --- quantities/dimensionality.py | 1 + quantities/quantity.py | 2 +- quantities/unitquantity.py | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index 6bccf89..e5bf296 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -320,6 +320,7 @@ def _d_copy(q1, out=None): p_dict[np.conjugate] = _d_copy p_dict[np.negative] = _d_copy p_dict[np.ones_like] = _d_copy +p_dict[np.core.umath._ones_like] = _d_copy p_dict[np.rint] = _d_copy p_dict[np.floor] = _d_copy p_dict[np.fix] = _d_copy diff --git a/quantities/quantity.py b/quantities/quantity.py index 6c2997b..4d3fe85 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -318,7 +318,7 @@ def __rdiv__(self, other): @with_doc(np.ndarray.__pow__) @check_uniform def __pow__(self, other): - return super(Quantity, self).__pow__(other) + return np.power(self, other) @with_doc(np.ndarray.__ipow__) @check_uniform diff --git a/quantities/unitquantity.py b/quantities/unitquantity.py index 9ecc0eb..2233917 100644 --- a/quantities/unitquantity.py +++ b/quantities/unitquantity.py @@ -514,4 +514,3 @@ def set_default_units( UnitSubstance.set_default_unit(substance) UnitTemperature.set_default_unit(temperature) UnitTime.set_default_unit(time) - From bd02e9e702115559c97434c9827ed8bbaae19e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Mon, 7 Nov 2016 16:34:46 +0100 Subject: [PATCH 067/212] Do not rely on non-public numpy API. --- quantities/dimensionality.py | 1 - 1 file changed, 1 deletion(-) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index e5bf296..6bccf89 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -320,7 +320,6 @@ def _d_copy(q1, out=None): p_dict[np.conjugate] = _d_copy p_dict[np.negative] = _d_copy p_dict[np.ones_like] = _d_copy -p_dict[np.core.umath._ones_like] = _d_copy p_dict[np.rint] = _d_copy p_dict[np.floor] = _d_copy p_dict[np.fix] = _d_copy From 3d4f76ce36b29a9288ccf3477561469a9050ba00 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Sat, 17 Jun 2017 15:04:34 +0200 Subject: [PATCH 068/212] Run Travis CI with NumPy 1.12.1 and 1.13.0, and with Python 3.6 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6ef87e2..7ee995c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,14 @@ env: - NUMPY_VERSION="1.9.1" - NUMPY_VERSION="1.10.4" - NUMPY_VERSION="1.11.2" + - NUMPY_VERSION="1.12.1" + - NUMPY_VERSION="1.13.0" python: - 2.6 - 2.7 - 3.4 - 3.5 + - 3.6 before_install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; From 2335dcf87ad7989e5f9bdbf8d26d75731e81addd Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 7 Jul 2017 09:47:44 +0200 Subject: [PATCH 069/212] As of NumPy 1.13, NumPy is very strict about the sample spacing argument(s) in `np.gradient` being scalar, which Quantities of size 1 are not. --- quantities/tests/test_umath.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 7d776ee..5a37c61 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -38,6 +38,7 @@ def test_diff(self): def test_ediff1d(self): self.assertQuantityEqual(np.diff(self.q, 1), [1, 1, 1] * pq.J) + @unittest.expectedFailure def test_gradient(self): try: l = np.gradient([[1,1],[3,4]] * pq.J, 1 * pq.m) From 2a6e7a3592e0ad77480980586399480d22b33309 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 7 Jul 2017 09:52:29 +0200 Subject: [PATCH 070/212] Remove Python 2.6 from the Travis CI test matrix, since it is not supported by NumPy 1.12 or later --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7ee995c..ff19c03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,15 +9,11 @@ env: - NUMPY_VERSION="1.12.1" - NUMPY_VERSION="1.13.0" python: - - 2.6 - 2.7 - 3.4 - 3.5 - 3.6 before_install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then - pip install unittest2; - fi - pip install "numpy==$NUMPY_VERSION" install: - python setup.py install From cc845c7919313e674a214a9625dfd53cc539c3b0 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Mon, 17 Jul 2017 10:13:26 +0200 Subject: [PATCH 071/212] Updates to the documentation --- .gitignore | 2 ++ CHANGES.txt | 33 +++++++++++++++++++++++++++++++++ README.rst | 2 -- conda.recipe/run_test.py | 6 +----- doc/devel/devnotes.rst | 3 +-- doc/user/installation.rst | 12 ++++-------- quantities/tests/common.py | 5 +---- setup.py | 7 ++----- 8 files changed, 44 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 0e10680..051220d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ *.pyc quantities.egg-info build +_build dist .project .pydevproject .settings +.idea diff --git a/CHANGES.txt b/CHANGES.txt index 8d70bb4..83155e3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,39 @@ CHANGES ======= + +----- +0.12.0 +----- + +Removed support for Python 2.6, since NumPy removed support for it as of +version 1.12. Numpy-1.8.2 or later is now required. + +Added more ufuncs: equal, not_equal, less, less_equal, greater, greater_equal + + +Bugs fixed +********** + +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aissue%20is%3Aclosed%20updated%3A2015-12-06..2017-08-01 + + +----- +0.11.0 +----- + +Added many new unit definitions, including aliases for American/British spellings of liter/litre +The Quantity class can now be subclassed. +Supports `np.fabs` +The test suite is now run with Travis CI + + +Bugs fixed +********** + +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aissue%20is%3Aclosed%20updated%3A2011-09-27..2015-12-06 + + ----- 0.10.0 ----- diff --git a/README.rst b/README.rst index 7430066..ab2eca6 100644 --- a/README.rst +++ b/README.rst @@ -6,8 +6,6 @@ quantities .. |pypi version| image:: https://img.shields.io/pypi/v/quantities.png :target: https://pypi.python.org/pypi/quantities -.. |pypi download| image:: https://img.shields.io/pypi/dm/quantities.png - :target: https://pypi.python.org/pypi/quantities .. |Build status| image:: https://secure.travis-ci.org/python-quantities/python-quantities.png?branch=master :target: http://travis-ci.org/python-quantities/python-quantities diff --git a/conda.recipe/run_test.py b/conda.recipe/run_test.py index 60805e8..3939ce7 100644 --- a/conda.recipe/run_test.py +++ b/conda.recipe/run_test.py @@ -1,11 +1,7 @@ import os import sys - -if sys.version.startswith('2.6') or sys.version.startswith('3.1'): - import unittest2 as unittest -else: - import unittest +import unittest suite = unittest.TestLoader().discover('quantities') unittest.TextTestRunner(verbosity=1).run(suite) diff --git a/doc/devel/devnotes.rst b/doc/devel/devnotes.rst index 6d2daec..cacebbb 100644 --- a/doc/devel/devnotes.rst +++ b/doc/devel/devnotes.rst @@ -8,5 +8,4 @@ unittest package. Unit tests can be run with the following:: python setup.py test This works with the version of unittest provided by the python-2.7 and -python-3.2 standard library. If you are running python-2.6 or python-3.1, you -need to install unittest2 to run the test suite. +python-3.2+ standard library. diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 9896442..65c1f9e 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -8,18 +8,16 @@ Prerequisites Quantities has a few dependencies: -* Python_ (>=2.6) -* NumPy_ (>=1.4) - -If you want to run the test suite on python-2.6 or python-3.1, install the -unittest2_ or unittest2py3k_ package, respectively. +* Python_ (>=2.7) +* NumPy_ (>=1.8.2) Source Code Installation ======================== To install Quantities, download the Quantites sourcecode from PyPi_ -and run "python setup.py install" in the quantities source directory. +and run "python setup.py install" in the quantities source directory, +or run "pip install quantities". Development =========== @@ -35,6 +33,4 @@ website. .. _Python: http://www.python.org/ .. _NumPy: http://www.scipy.org .. _PyPi: http://pypi.python.org/pypi/quantities -.. _unittest2: http://pypi.python.org/pypi/unittest2 -.. _unittest2py3k: http://pypi.python.org/pypi/unittest2py3k .. _github: http://github.com/python-quantities/python-quantities diff --git a/quantities/tests/common.py b/quantities/tests/common.py index 0cdf8c6..68d6c9d 100644 --- a/quantities/tests/common.py +++ b/quantities/tests/common.py @@ -1,8 +1,5 @@ import sys -if sys.version.startswith('2.6') or sys.version.startswith('3.1'): - import unittest2 as unittest -else: - import unittest +import unittest import numpy as np diff --git a/setup.py b/setup.py index 1361a46..efd5323 100755 --- a/setup.py +++ b/setup.py @@ -85,10 +85,7 @@ def finalize_options(self): def run(self): import sys - if sys.version.startswith('2.6') or sys.version.startswith('3.1'): - import unittest2 as unittest - else: - import unittest + import unittest suite = unittest.TestLoader().discover('.') global TEST_RESULT TEST_RESULT = unittest.TextTestRunner(verbosity=self.verbosity+1).run(suite) @@ -139,7 +136,7 @@ def run(self): packages = packages, platforms = 'Any', requires = [ - 'python (>=2.6.0)', + 'python (>=2.7.0)', 'numpy (>=1.8.2)', ], url = 'http://packages.python.org/quantities', From aefee659da4f0649695ee9fedd1b656bdf5c7948 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Sat, 26 Aug 2017 00:12:10 +0200 Subject: [PATCH 072/212] units: Add kilosecond and megasecond --- quantities/units/time.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/quantities/units/time.py b/quantities/units/time.py index 1295615..f383161 100644 --- a/quantities/units/time.py +++ b/quantities/units/time.py @@ -11,6 +11,18 @@ symbol='s', aliases=['sec', 'seconds'] ) +ks = kilosecond = UnitTime( + 'millisecond', + s*1000, + 'ms', + aliases=['kiloseconds'] +) +Ms = megasecond = UnitTime( + 'megasecond', + ks*1000, + 'Ms', + aliases=['megaseconds'] +) ms = millisecond = UnitTime( 'millisecond', s/1000, From 55030fcbbf0137637d2d84409c049195f55ad8f4 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 30 Aug 2017 14:47:29 +0200 Subject: [PATCH 073/212] Update to reflect the new canonical location for the documentation, on readthedocs.io [skip ci] --- README.rst | 2 +- doc/devel/release.rst | 18 ++++++------------ setup.py | 4 ++-- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index ab2eca6..06720c6 100644 --- a/README.rst +++ b/README.rst @@ -28,7 +28,7 @@ Documentation and usage ----------------------- You can find the official documenation at: - http://packages.python.org/quantities +http://python-quantities.readthedocs.io/ Here is a simple example: diff --git a/doc/devel/release.rst b/doc/devel/release.rst index bf9a652..9eca148 100644 --- a/doc/devel/release.rst +++ b/doc/devel/release.rst @@ -42,9 +42,11 @@ This creates the executable windows installer in the `dist/` directory. Building Quantities documentation ================================= -When publishing a new release, the Quantities doumentation needs to be generated -and published as well. Sphinx_, LaTeX_ (preferably `TeX-Live`_), and dvipng_ are -required to build the documentation. Once these are installed, do:: +The Quantities documentation is automatically built on readthedocs.io. + +Should you need to build the documentation locally, +Sphinx_, LaTeX_ (preferably `TeX-Live`_), and dvipng_ are +required. Once these are installed, do:: cd doc make html @@ -56,15 +58,7 @@ which will produce the html output and save it in build/sphinx/html. Then run:: make all-pdf cp Quantities.pdf ../html -which will generate a pdf file in the latex directory. Finally, upload the html -content to the http://packages.python.org/quantities/ webserver. To do so:: - - cd build/html - zip -r quantities * - -and then visit `the Quantities project page -`_ at the Python Package Index to -upload the zip archive. +which will generate a pdf file in the latex directory. .. _Sphinx: http://sphinx.pocoo.org/ .. _LaTeX: http://www.latex-project.org/ diff --git a/setup.py b/setup.py index efd5323..847afd0 100755 --- a/setup.py +++ b/setup.py @@ -130,7 +130,7 @@ def run(self): and API are stable, test coverage is incomplete so the package is not suggested for mission-critical applications. - .. _tutorial: http://packages.python.org/quantities/user/tutorial.html + .. _tutorial: http://python-quantities.readthedocs.io/en/latest/user/tutorial.html """, name = 'quantities', packages = packages, @@ -139,7 +139,7 @@ def run(self): 'python (>=2.7.0)', 'numpy (>=1.8.2)', ], - url = 'http://packages.python.org/quantities', + url = 'http://python-quantities.readthedocs.io/', version = versioneer.get_version(), ) From 41c70c8ee2b9c7afd6b2a83b983c3be57fefe9c2 Mon Sep 17 00:00:00 2001 From: Michael von Papen Date: Wed, 30 Aug 2017 17:34:42 +0200 Subject: [PATCH 074/212] changed ms to ks corrected bug: kiloseconds was incorrectly refenced as ms instead of ks --- quantities/units/time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/units/time.py b/quantities/units/time.py index f383161..728578f 100644 --- a/quantities/units/time.py +++ b/quantities/units/time.py @@ -14,7 +14,7 @@ ks = kilosecond = UnitTime( 'millisecond', s*1000, - 'ms', + 'ks', aliases=['kiloseconds'] ) Ms = megasecond = UnitTime( From 1e025e90ebb3f66ffab72a05cebd6d21c1d70c93 Mon Sep 17 00:00:00 2001 From: Michael von Papen Date: Wed, 30 Aug 2017 17:55:45 +0200 Subject: [PATCH 075/212] millisecond -> kilosecond --- quantities/units/time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/units/time.py b/quantities/units/time.py index 728578f..f55436a 100644 --- a/quantities/units/time.py +++ b/quantities/units/time.py @@ -12,7 +12,7 @@ aliases=['sec', 'seconds'] ) ks = kilosecond = UnitTime( - 'millisecond', + 'kilosecond', s*1000, 'ks', aliases=['kiloseconds'] From 4a9d2680485164b055a5f455d8ec14f9ce223f2a Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 30 Aug 2017 23:10:49 +0200 Subject: [PATCH 076/212] Update versioneer, and fix a bug in which _version.py was not getting updated during "python setup.py sdist" (essentially, setup.py needs to get the sdist command from versioneer and not directly from distutils) --- README.rst | 2 +- quantities/_version.py | 204 ++++++++---- setup.py | 2 +- versioneer.py | 739 ++++++++++++++++++++++++----------------- 4 files changed, 565 insertions(+), 382 deletions(-) diff --git a/README.rst b/README.rst index 06720c6..8f91699 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ To get the git version do:: Documentation and usage ----------------------- -You can find the official documenation at: +You can find the official documentation at: http://python-quantities.readthedocs.io/ diff --git a/quantities/_version.py b/quantities/_version.py index 0ac34ba..028aab3 100644 --- a/quantities/_version.py +++ b/quantities/_version.py @@ -6,7 +6,9 @@ # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.15 (https://github.com/warner/python-versioneer) +# versioneer-0.18 (https://github.com/warner/python-versioneer) + +"""Git implementation of _version.py.""" import errno import os @@ -16,21 +18,24 @@ def get_keywords(): + """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "$Format:%d$" git_full = "$Format:%H$" - keywords = {"refnames": git_refnames, "full": git_full} + git_date = "$Format:%ci$" + keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: - pass + """Container for Versioneer configuration parameters.""" def get_config(): + """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() @@ -44,7 +49,7 @@ def get_config(): class NotThisMethod(Exception): - pass + """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} @@ -52,7 +57,9 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): + """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f @@ -60,14 +67,17 @@ def decorate(f): return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, + env=None): + """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + p = subprocess.Popen([c] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break @@ -78,37 +88,50 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): if verbose: print("unable to run %s" % dispcmd) print(e) - return None + return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) - return None + return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) - return None - return stdout + print("stdout was %s" % stdout) + return None, p.returncode + return stdout, p.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' doesn't start with " - "prefix '%s'" % (root, dirname, parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None} + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes both + the project name and a version string. We will also support searching up + two directory levels for an appropriately named parent directory + """ + rootdirs = [] + + for i in range(3): + dirname = os.path.basename(root) + if dirname.startswith(parentdir_prefix): + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None, "date": None} + else: + rootdirs.append(root) + root = os.path.dirname(root) # up a level + + if verbose: + print("Tried directories %s but none started with prefix %s" % + (str(rootdirs), parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from @@ -125,6 +148,10 @@ def git_get_keywords(versionfile_abs): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass @@ -133,8 +160,18 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") + date = keywords.get("date") + if date is not None: + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant + # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 + # -like" string, which we must then edit to make compliant), because + # it's been around since git-1.5.3, and it's too difficult to + # discover which version we're using, or to work around using an + # older one. + date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: @@ -155,7 +192,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) + print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): @@ -166,41 +203,46 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None - } + "dirty": False, "error": None, + "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags"} + "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. - - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %s" % root) - raise NotThisMethod("no .git directory") + """Get version from 'git describe' in the root of the source tree. + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - # if there is a tag, this yields TAG-NUM-gHEX[-dirty] - # if there are no tags, this yields HEX[-dirty] (no NUM) - describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long"], - cwd=root) + + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=True) + if rc != 0: + if verbose: + print("Directory %s not under git control" % root) + raise NotThisMethod("'git rev-parse --git-dir' returned error") + + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) + describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", "%s*" % tag_prefix], + cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() @@ -251,27 +293,34 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) pieces["distance"] = int(count_out) # total number of commits + # commit date: see ISO-8601 comment in git_versions_from_keywords() + date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], + cwd=root)[0].strip() + pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + return pieces def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): - # now build up version string, with post-release "local version - # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + """Build up version string, with post-release "local version identifier". - # exceptions: - # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -289,11 +338,11 @@ def render_pep440(pieces): def render_pep440_pre(pieces): - # TAG[.post.devDISTANCE] . No -dirty - - # exceptions: - # 1: no tags. 0.post.devDISTANCE + """TAG[.post.devDISTANCE] -- No -dirty. + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -305,14 +354,15 @@ def render_pep440_pre(pieces): def render_pep440_post(pieces): - # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that - # .dev0 sorts backwards (a dirty tree will appear "older" than the - # corresponding clean one), but you shouldn't be releasing software with - # -dirty anyways. + """TAG[.postDISTANCE[.dev0]+gHEX] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -331,11 +381,13 @@ def render_pep440_post(pieces): def render_pep440_old(pieces): - # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + """TAG[.postDISTANCE[.dev0]] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -351,12 +403,13 @@ def render_pep440_old(pieces): def render_git_describe(pieces): - # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty - # --always' + """TAG[-DISTANCE-gHEX][-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always'. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -370,12 +423,14 @@ def render_git_describe(pieces): def render_git_describe_long(pieces): - # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty - # --always -long'. The distance/hash is unconditional. + """TAG-DISTANCE-gHEX[-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) @@ -388,11 +443,13 @@ def render_git_describe_long(pieces): def render(pieces, style): + """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, - "error": pieces["error"]} + "error": pieces["error"], + "date": None} if not style or style == "default": style = "pep440" # the default @@ -413,10 +470,12 @@ def render(pieces, style): raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None} + "dirty": pieces["dirty"], "error": None, + "date": pieces.get("date")} def get_versions(): + """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which @@ -441,7 +500,8 @@ def get_versions(): except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, - "error": "unable to find root of source tree"} + "error": "unable to find root of source tree", + "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) @@ -457,4 +517,4 @@ def get_versions(): return {"version": "0+unknown", "full-revisionid": None, "dirty": None, - "error": "unable to compute version"} + "error": "unable to compute version", "date": None} diff --git a/setup.py b/setup.py index 847afd0..1e67772 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ from distutils.cmd import Command from distutils.core import setup -from distutils.command.sdist import sdist as _sdist from distutils.command.build import build as _build import os import sys @@ -10,6 +9,7 @@ TEST_RESULT = None cmdclass = versioneer.get_cmdclass() +_sdist = cmdclass['sdist'] class data(Command): diff --git a/versioneer.py b/versioneer.py index c010f63..64fea1c 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,7 +1,8 @@ -# Version: 0.15 +# Version: 0.18 + +"""The Versioneer - like a rocketeer, but for versions. -""" The Versioneer ============== @@ -9,7 +10,7 @@ * https://github.com/warner/python-versioneer * Brian Warner * License: Public Domain -* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, and pypy +* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy * [![Latest Version] (https://pypip.in/version/versioneer/badge.svg?style=flat) ](https://pypi.python.org/pypi/versioneer/) @@ -87,125 +88,7 @@ ## Installation -First, decide on values for the following configuration variables: - -* `VCS`: the version control system you use. Currently accepts "git". - -* `style`: the style of version string to be produced. See "Styles" below for - details. Defaults to "pep440", which looks like - `TAG[+DISTANCE.gSHORTHASH[.dirty]]`. - -* `versionfile_source`: - - A project-relative pathname into which the generated version strings should - be written. This is usually a `_version.py` next to your project's main - `__init__.py` file, so it can be imported at runtime. If your project uses - `src/myproject/__init__.py`, this should be `src/myproject/_version.py`. - This file should be checked in to your VCS as usual: the copy created below - by `setup.py setup_versioneer` will include code that parses expanded VCS - keywords in generated tarballs. The 'build' and 'sdist' commands will - replace it with a copy that has just the calculated version string. - - This must be set even if your project does not have any modules (and will - therefore never import `_version.py`), since "setup.py sdist" -based trees - still need somewhere to record the pre-calculated version strings. Anywhere - in the source tree should do. If there is a `__init__.py` next to your - `_version.py`, the `setup.py setup_versioneer` command (described below) - will append some `__version__`-setting assignments, if they aren't already - present. - -* `versionfile_build`: - - Like `versionfile_source`, but relative to the build directory instead of - the source directory. These will differ when your setup.py uses - 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, - then you will probably have `versionfile_build='myproject/_version.py'` and - `versionfile_source='src/myproject/_version.py'`. - - If this is set to None, then `setup.py build` will not attempt to rewrite - any `_version.py` in the built tree. If your project does not have any - libraries (e.g. if it only builds a script), then you should use - `versionfile_build = None` and override `distutils.command.build_scripts` - to explicitly insert a copy of `versioneer.get_version()` into your - generated script. - -* `tag_prefix`: - - a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. - If your tags look like 'myproject-1.2.0', then you should use - tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this - should be an empty string. - -* `parentdir_prefix`: - - a optional string, frequently the same as tag_prefix, which appears at the - start of all unpacked tarball filenames. If your tarball unpacks into - 'myproject-1.2.0', this should be 'myproject-'. To disable this feature, - just omit the field from your `setup.cfg`. - -This tool provides one script, named `versioneer`. That script has one mode, -"install", which writes a copy of `versioneer.py` into the current directory -and runs `versioneer.py setup` to finish the installation. - -To versioneer-enable your project: - -* 1: Modify your `setup.cfg`, adding a section named `[versioneer]` and - populating it with the configuration values you decided earlier (note that - the option names are not case-sensitive): - - ```` - [versioneer] - VCS = git - style = pep440 - versionfile_source = src/myproject/_version.py - versionfile_build = myproject/_version.py - tag_prefix = "" - parentdir_prefix = myproject- - ```` - -* 2: Run `versioneer install`. This will do the following: - - * copy `versioneer.py` into the top of your source tree - * create `_version.py` in the right place (`versionfile_source`) - * modify your `__init__.py` (if one exists next to `_version.py`) to define - `__version__` (by calling a function from `_version.py`) - * modify your `MANIFEST.in` to include both `versioneer.py` and the - generated `_version.py` in sdist tarballs - - `versioneer install` will complain about any problems it finds with your - `setup.py` or `setup.cfg`. Run it multiple times until you have fixed all - the problems. - -* 3: add a `import versioneer` to your setup.py, and add the following - arguments to the setup() call: - - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), - -* 4: commit these changes to your VCS. To make sure you won't forget, - `versioneer install` will mark everything it touched for addition using - `git add`. Don't forget to add `setup.py` and `setup.cfg` too. - -## Post-Installation Usage - -Once established, all uses of your tree from a VCS checkout should get the -current version string. All generated tarballs should include an embedded -version string (so users who unpack them will not need a VCS tool installed). - -If you distribute your project through PyPI, then the release process should -boil down to two steps: - -* 1: git tag 1.0 -* 2: python setup.py register sdist upload - -If you distribute it through github (i.e. users use github to generate -tarballs with `git archive`), the process is: - -* 1: git tag 1.0 -* 2: git push; git push --tags - -Versioneer will report "0+untagged.NUMCOMMITS.gHASH" until your tree has at -least one tag in its history. +See [INSTALL.md](./INSTALL.md) for detailed installation instructions. ## Version-String Flavors @@ -226,6 +109,10 @@ * `['full-revisionid']`: detailed revision identifier. For Git, this is the full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". +* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the + commit date in ISO 8601 format. This will be None if the date is not + available. + * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that this is only accurate if run in a VCS checkout, otherwise it is likely to be False or None @@ -264,8 +151,8 @@ software (exactly equal to a known tag), the identifier will only contain the stripped tag, e.g. "0.11". -Other styles are available. See details.md in the Versioneer source tree for -descriptions. +Other styles are available. See [details.md](details.md) in the Versioneer +source tree for descriptions. ## Debugging @@ -275,47 +162,95 @@ display the full contents of `get_versions()` (including the `error` string, which may help identify what went wrong). -## Updating Versioneer +## Known Limitations -To upgrade your project to a new release of Versioneer, do the following: +Some situations are known to cause problems for Versioneer. This details the +most significant ones. More can be found on Github +[issues page](https://github.com/warner/python-versioneer/issues). -* install the new Versioneer (`pip install -U versioneer` or equivalent) -* edit `setup.cfg`, if necessary, to include any new configuration settings - indicated by the release notes -* re-run `versioneer install` in your source tree, to replace - `SRC/_version.py` -* commit any changed files +### Subprojects + +Versioneer has limited support for source trees in which `setup.py` is not in +the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are +two common reasons why `setup.py` might not be in the root: + +* Source trees which contain multiple subprojects, such as + [Buildbot](https://github.com/buildbot/buildbot), which contains both + "master" and "slave" subprojects, each with their own `setup.py`, + `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI + distributions (and upload multiple independently-installable tarballs). +* Source trees whose main purpose is to contain a C library, but which also + provide bindings to Python (and perhaps other langauges) in subdirectories. + +Versioneer will look for `.git` in parent directories, and most operations +should get the right version string. However `pip` and `setuptools` have bugs +and implementation details which frequently cause `pip install .` from a +subproject directory to fail to find a correct version string (so it usually +defaults to `0+unknown`). + +`pip install --editable .` should work correctly. `setup.py install` might +work too. + +Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in +some later version. + +[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking +this issue. The discussion in +[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the +issue from the Versioneer side in more detail. +[pip PR#3176](https://github.com/pypa/pip/pull/3176) and +[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve +pip to let Versioneer work correctly. + +Versioneer-0.16 and earlier only looked for a `.git` directory next to the +`setup.cfg`, so subprojects were completely unsupported with those releases. -### Upgrading to 0.15 +### Editable installs with setuptools <= 18.5 -Starting with this version, Versioneer is configured with a `[versioneer]` -section in your `setup.cfg` file. Earlier versions required the `setup.py` to -set attributes on the `versioneer` module immediately after import. The new -version will refuse to run (raising an exception during import) until you -have provided the necessary `setup.cfg` section. +`setup.py develop` and `pip install --editable .` allow you to install a +project into a virtualenv once, then continue editing the source code (and +test) without re-installing after every change. -In addition, the Versioneer package provides an executable named -`versioneer`, and the installation process is driven by running `versioneer -install`. In 0.14 and earlier, the executable was named -`versioneer-installer` and was run without an argument. +"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a +convenient way to specify executable scripts that should be installed along +with the python package. -### Upgrading to 0.14 +These both work as expected when using modern setuptools. When using +setuptools-18.5 or earlier, however, certain operations will cause +`pkg_resources.DistributionNotFound` errors when running the entrypoint +script, which must be resolved by re-installing the package. This happens +when the install happens with one version, then the egg_info data is +regenerated while a different version is checked out. Many setup.py commands +cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into +a different virtualenv), so this can be surprising. -0.14 changes the format of the version string. 0.13 and earlier used -hyphen-separated strings like "0.11-2-g1076c97-dirty". 0.14 and beyond use a -plus-separated "local version" section strings, with dot-separated -components, like "0.11+2.g1076c97". PEP440-strict tools did not like the old -format, but should be ok with the new one. +[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes +this one, but upgrading to a newer version of setuptools should probably +resolve it. -### Upgrading from 0.11 to 0.12 +### Unicode version strings -Nothing special. +While Versioneer works (and is continually tested) with both Python 2 and +Python 3, it is not entirely consistent with bytes-vs-unicode distinctions. +Newer releases probably generate unicode version strings on py2. It's not +clear that this is wrong, but it may be surprising for applications when then +write these strings to a network connection or include them in bytes-oriented +APIs like cryptographic checksums. -### Upgrading from 0.10 to 0.11 +[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates +this question. -You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running -`setup.py setup_versioneer`. This will enable the use of additional -version-control systems (SVN, etc) in the future. + +## Updating Versioneer + +To upgrade your project to a new release of Versioneer, do the following: + +* install the new Versioneer (`pip install -U versioneer` or equivalent) +* edit `setup.cfg`, if necessary, to include any new configuration settings + indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. +* re-run `versioneer install` in your source tree, to replace + `SRC/_version.py` +* commit any changed files ## Future Directions @@ -333,9 +268,11 @@ ## License -To make Versioneer easier to embed, all its code is hereby released into the -public domain. The `_version.py` that it creates is also in the public -domain. +To make Versioneer easier to embed, all its code is dedicated to the public +domain. The `_version.py` that it creates is also in the public domain. +Specifically, both are released under the Creative Commons "Public Domain +Dedication" license (CC0-1.0), as described in +https://creativecommons.org/publicdomain/zero/1.0/ . """ @@ -353,12 +290,15 @@ class VersioneerConfig: - pass + """Container for Versioneer configuration parameters.""" def get_root(): - # we require that all commands are run from the project root, i.e. the - # directory that contains setup.py, setup.cfg, and versioneer.py . + """Get the project root directory. + + We require that all commands are run from the project root, i.e. the + directory that contains setup.py, setup.cfg, and versioneer.py . + """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") @@ -382,7 +322,9 @@ def get_root(): # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. me = os.path.realpath(os.path.abspath(__file__)) - if os.path.splitext(me)[0] != os.path.splitext(versioneer_py)[0]: + me_dir = os.path.normcase(os.path.splitext(me)[0]) + vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) + if me_dir != vsr_dir: print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(me), versioneer_py)) except NameError: @@ -391,6 +333,7 @@ def get_root(): def get_config_from_root(root): + """Read the project setup.cfg file to determine Versioneer config.""" # This might raise EnvironmentError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at @@ -411,13 +354,16 @@ def get(parser, name): cfg.versionfile_source = get(parser, "versionfile_source") cfg.versionfile_build = get(parser, "versionfile_build") cfg.tag_prefix = get(parser, "tag_prefix") + if cfg.tag_prefix in ("''", '""'): + cfg.tag_prefix = "" cfg.parentdir_prefix = get(parser, "parentdir_prefix") cfg.verbose = get(parser, "verbose") return cfg class NotThisMethod(Exception): - pass + """Exception raised if a method is not valid for the current scenario.""" + # these dictionaries contain VCS-specific tools LONG_VERSION_PY = {} @@ -425,7 +371,9 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): + """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f @@ -433,14 +381,17 @@ def decorate(f): return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, + env=None): + """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + p = subprocess.Popen([c] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break @@ -451,19 +402,22 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): if verbose: print("unable to run %s" % dispcmd) print(e) - return None + return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) - return None + return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) - return None - return stdout + print("stdout was %s" % stdout) + return None, p.returncode + return stdout, p.returncode + + LONG_VERSION_PY['git'] = ''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag @@ -472,7 +426,9 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.15 (https://github.com/warner/python-versioneer) +# versioneer-0.18 (https://github.com/warner/python-versioneer) + +"""Git implementation of _version.py.""" import errno import os @@ -482,21 +438,24 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): def get_keywords(): + """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" - keywords = {"refnames": git_refnames, "full": git_full} + git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" + keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: - pass + """Container for Versioneer configuration parameters.""" def get_config(): + """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() @@ -510,7 +469,7 @@ def get_config(): class NotThisMethod(Exception): - pass + """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} @@ -518,7 +477,9 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): + """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f @@ -526,14 +487,17 @@ def decorate(f): return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, + env=None): + """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + p = subprocess.Popen([c] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break @@ -544,37 +508,50 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): if verbose: print("unable to run %%s" %% dispcmd) print(e) - return None + return None, None else: if verbose: print("unable to find command, tried %%s" %% (commands,)) - return None + return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) - return None - return stdout + print("stdout was %%s" %% stdout) + return None, p.returncode + return stdout, p.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%%s', but '%%s' doesn't start with " - "prefix '%%s'" %% (root, dirname, parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None} + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes both + the project name and a version string. We will also support searching up + two directory levels for an appropriately named parent directory + """ + rootdirs = [] + + for i in range(3): + dirname = os.path.basename(root) + if dirname.startswith(parentdir_prefix): + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None, "date": None} + else: + rootdirs.append(root) + root = os.path.dirname(root) # up a level + + if verbose: + print("Tried directories %%s but none started with prefix %%s" %% + (str(rootdirs), parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from @@ -591,6 +568,10 @@ def git_get_keywords(versionfile_abs): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass @@ -599,8 +580,18 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") + date = keywords.get("date") + if date is not None: + # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant + # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 + # -like" string, which we must then edit to make compliant), because + # it's been around since git-1.5.3, and it's too difficult to + # discover which version we're using, or to work around using an + # older one. + date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: @@ -621,7 +612,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%%s', no digits" %% ",".join(refs-tags)) + print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: print("likely tags: %%s" %% ",".join(sorted(tags))) for ref in sorted(tags): @@ -632,41 +623,46 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): print("picking %%s" %% r) return {"version": r, "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None - } + "dirty": False, "error": None, + "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags"} + "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. - - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %%s" %% root) - raise NotThisMethod("no .git directory") + """Get version from 'git describe' in the root of the source tree. + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - # if there is a tag, this yields TAG-NUM-gHEX[-dirty] - # if there are no tags, this yields HEX[-dirty] (no NUM) - describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long"], - cwd=root) + + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=True) + if rc != 0: + if verbose: + print("Directory %%s not under git control" %% root) + raise NotThisMethod("'git rev-parse --git-dir' returned error") + + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) + describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", "%%s*" %% tag_prefix], + cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() @@ -717,27 +713,34 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) pieces["distance"] = int(count_out) # total number of commits + # commit date: see ISO-8601 comment in git_versions_from_keywords() + date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], + cwd=root)[0].strip() + pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + return pieces def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): - # now build up version string, with post-release "local version - # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + """Build up version string, with post-release "local version identifier". - # exceptions: - # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -755,11 +758,11 @@ def render_pep440(pieces): def render_pep440_pre(pieces): - # TAG[.post.devDISTANCE] . No -dirty - - # exceptions: - # 1: no tags. 0.post.devDISTANCE + """TAG[.post.devDISTANCE] -- No -dirty. + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -771,14 +774,15 @@ def render_pep440_pre(pieces): def render_pep440_post(pieces): - # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that - # .dev0 sorts backwards (a dirty tree will appear "older" than the - # corresponding clean one), but you shouldn't be releasing software with - # -dirty anyways. + """TAG[.postDISTANCE[.dev0]+gHEX] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -797,11 +801,13 @@ def render_pep440_post(pieces): def render_pep440_old(pieces): - # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + """TAG[.postDISTANCE[.dev0]] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -817,12 +823,13 @@ def render_pep440_old(pieces): def render_git_describe(pieces): - # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty - # --always' + """TAG[-DISTANCE-gHEX][-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always'. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -836,12 +843,14 @@ def render_git_describe(pieces): def render_git_describe_long(pieces): - # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty - # --always -long'. The distance/hash is unconditional. + """TAG-DISTANCE-gHEX[-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) @@ -854,11 +863,13 @@ def render_git_describe_long(pieces): def render(pieces, style): + """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, - "error": pieces["error"]} + "error": pieces["error"], + "date": None} if not style or style == "default": style = "pep440" # the default @@ -879,10 +890,12 @@ def render(pieces, style): raise ValueError("unknown style '%%s'" %% style) return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None} + "dirty": pieces["dirty"], "error": None, + "date": pieces.get("date")} def get_versions(): + """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which @@ -907,7 +920,8 @@ def get_versions(): except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, - "error": "unable to find root of source tree"} + "error": "unable to find root of source tree", + "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) @@ -923,12 +937,13 @@ def get_versions(): return {"version": "0+unknown", "full-revisionid": None, "dirty": None, - "error": "unable to compute version"} + "error": "unable to compute version", "date": None} ''' @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from @@ -945,6 +960,10 @@ def git_get_keywords(versionfile_abs): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass @@ -953,8 +972,18 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") + date = keywords.get("date") + if date is not None: + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant + # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 + # -like" string, which we must then edit to make compliant), because + # it's been around since git-1.5.3, and it's too difficult to + # discover which version we're using, or to work around using an + # older one. + date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: @@ -975,7 +1004,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) + print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): @@ -986,41 +1015,46 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None - } + "dirty": False, "error": None, + "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags"} + "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' keywords were *not* expanded, and - # _version.py hasn't already been rewritten with a short version string, - # meaning we're inside a checked out source tree. - - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %s" % root) - raise NotThisMethod("no .git directory") + """Get version from 'git describe' in the root of the source tree. + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - # if there is a tag, this yields TAG-NUM-gHEX[-dirty] - # if there are no tags, this yields HEX[-dirty] (no NUM) - describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long"], - cwd=root) + + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=True) + if rc != 0: + if verbose: + print("Directory %s not under git control" % root) + raise NotThisMethod("'git rev-parse --git-dir' returned error") + + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) + describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", "%s*" % tag_prefix], + cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() @@ -1071,14 +1105,24 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) pieces["distance"] = int(count_out) # total number of commits + # commit date: see ISO-8601 comment in git_versions_from_keywords() + date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], + cwd=root)[0].strip() + pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + return pieces def do_vcs_install(manifest_in, versionfile_source, ipy): + """Git-specific installation logic for Versioneer. + + For Git, this means creating/changing .gitattributes to mark _version.py + for export-subst keyword substitution. + """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] @@ -1112,26 +1156,37 @@ def do_vcs_install(manifest_in, versionfile_source, ipy): def versions_from_parentdir(parentdir_prefix, root, verbose): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' doesn't start with " - "prefix '%s'" % (root, dirname, parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None} + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes both + the project name and a version string. We will also support searching up + two directory levels for an appropriately named parent directory + """ + rootdirs = [] + + for i in range(3): + dirname = os.path.basename(root) + if dirname.startswith(parentdir_prefix): + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None, "date": None} + else: + rootdirs.append(root) + root = os.path.dirname(root) # up a level + + if verbose: + print("Tried directories %s but none started with prefix %s" % + (str(rootdirs), parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.15) from +# This file was generated by 'versioneer.py' (0.18) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. import json -import sys version_json = ''' %s @@ -1144,6 +1199,7 @@ def get_versions(): def versions_from_file(filename): + """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() @@ -1151,12 +1207,16 @@ def versions_from_file(filename): raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) + if not mo: + mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", + contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) def write_to_version_file(filename, versions): + """Write the given version number to the given _version.py file.""" os.unlink(filename) contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) @@ -1167,19 +1227,21 @@ def write_to_version_file(filename, versions): def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): - # now build up version string, with post-release "local version - # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + """Build up version string, with post-release "local version identifier". - # exceptions: - # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -1197,11 +1259,11 @@ def render_pep440(pieces): def render_pep440_pre(pieces): - # TAG[.post.devDISTANCE] . No -dirty - - # exceptions: - # 1: no tags. 0.post.devDISTANCE + """TAG[.post.devDISTANCE] -- No -dirty. + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -1213,14 +1275,15 @@ def render_pep440_pre(pieces): def render_pep440_post(pieces): - # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that - # .dev0 sorts backwards (a dirty tree will appear "older" than the - # corresponding clean one), but you shouldn't be releasing software with - # -dirty anyways. + """TAG[.postDISTANCE[.dev0]+gHEX] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -1239,11 +1302,13 @@ def render_pep440_post(pieces): def render_pep440_old(pieces): - # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. + """TAG[.postDISTANCE[.dev0]] . - # exceptions: - # 1: no tags. 0.postDISTANCE[.dev0] + The ".dev0" means dirty. + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: @@ -1259,12 +1324,13 @@ def render_pep440_old(pieces): def render_git_describe(pieces): - # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty - # --always' + """TAG[-DISTANCE-gHEX][-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always'. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: @@ -1278,12 +1344,14 @@ def render_git_describe(pieces): def render_git_describe_long(pieces): - # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty - # --always -long'. The distance/hash is unconditional. + """TAG-DISTANCE-gHEX[-dirty]. - # exceptions: - # 1: no tags. HEX[-dirty] (note: no 'g' prefix) + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) @@ -1296,11 +1364,13 @@ def render_git_describe_long(pieces): def render(pieces, style): + """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, - "error": pieces["error"]} + "error": pieces["error"], + "date": None} if not style or style == "default": style = "pep440" # the default @@ -1321,16 +1391,19 @@ def render(pieces, style): raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None} + "dirty": pieces["dirty"], "error": None, + "date": pieces.get("date")} class VersioneerBadRootError(Exception): - pass + """The project root directory is unknown or missing key files.""" def get_versions(verbose=False): - # returns dict with two keys: 'version' and 'full' + """Get the project version from whatever source is available. + Returns dict with two keys: 'version' and 'full'. + """ if "versioneer" in sys.modules: # see the discussion in cmdclass.py:get_cmdclass() del sys.modules["versioneer"] @@ -1398,14 +1471,17 @@ def get_versions(verbose=False): print("unable to compute version") return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, "error": "unable to compute version"} + "dirty": None, "error": "unable to compute version", + "date": None} def get_version(): + """Get the short version string for this project.""" return get_versions()["version"] def get_cmdclass(): + """Get the custom setuptools/distutils subclasses used by Versioneer.""" if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and @@ -1442,6 +1518,7 @@ def run(self): print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) print(" dirty: %s" % vers.get("dirty")) + print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) cmds["version"] = cmd_version @@ -1455,8 +1532,17 @@ def run(self): # setuptools/bdist_egg -> distutils/install_lib -> build_py # setuptools/install -> bdist_egg ->.. # setuptools/develop -> ? + # pip install: + # copies source tree to a tempdir before running egg_info/etc + # if .git isn't copied too, 'git describe' will fail + # then does setup.py bdist_wheel, or sometimes setup.py install + # setup.py egg_info -> ? - from distutils.command.build_py import build_py as _build_py + # we override different "build_py" commands for both environments + if "setuptools" in sys.modules: + from setuptools.command.build_py import build_py as _build_py + else: + from distutils.command.build_py import build_py as _build_py class cmd_build_py(_build_py): def run(self): @@ -1475,6 +1561,12 @@ def run(self): if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe + # nczeczulin reports that py2exe won't like the pep440-style string + # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. + # setup(console=[{ + # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION + # "product_version": versioneer.get_version(), + # ... class cmd_build_exe(_build_exe): def run(self): @@ -1499,6 +1591,34 @@ def run(self): cmds["build_exe"] = cmd_build_exe del cmds["build_py"] + if 'py2exe' in sys.modules: # py2exe enabled? + try: + from py2exe.distutils_buildexe import py2exe as _py2exe # py3 + except ImportError: + from py2exe.build_exe import py2exe as _py2exe # py2 + + class cmd_py2exe(_py2exe): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + target_versionfile = cfg.versionfile_source + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + + _py2exe.run(self) + os.unlink(target_versionfile) + with open(cfg.versionfile_source, "w") as f: + LONG = LONG_VERSION_PY[cfg.VCS] + f.write(LONG % + {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) + cmds["py2exe"] = cmd_py2exe + # we override different "sdist" commands for both environments if "setuptools" in sys.modules: from setuptools.command.sdist import sdist as _sdist @@ -1539,7 +1659,7 @@ def make_release_tree(self, base_dir, files): style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py - tag_prefix = "" + tag_prefix = parentdir_prefix = myproject- You will also need to edit your setup.py to use the results: @@ -1575,6 +1695,7 @@ def make_release_tree(self, base_dir, files): def do_setup(): + """Main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) @@ -1649,13 +1770,14 @@ def do_setup(): print(" versionfile_source already in MANIFEST.in") # Make VCS-specific changes. For git, this means creating/changing - # .gitattributes to mark _version.py for export-time keyword + # .gitattributes to mark _version.py for export-subst keyword # substitution. do_vcs_install(manifest_in, cfg.versionfile_source, ipy) return 0 def scan_setup_py(): + """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False errors = 0 @@ -1690,6 +1812,7 @@ def scan_setup_py(): errors += 1 return errors + if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": From 8451279a22bea48d8a2a1635c20d21d351121293 Mon Sep 17 00:00:00 2001 From: Olivier Sechet Date: Thu, 2 Nov 2017 23:08:37 +0100 Subject: [PATCH 077/212] Add additional information units --- quantities/tests/test_conversion.py | 38 +++++++++++ quantities/units/information.py | 98 +++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/quantities/tests/test_conversion.py b/quantities/tests/test_conversion.py index 3646e1b..7fb8502 100644 --- a/quantities/tests/test_conversion.py +++ b/quantities/tests/test_conversion.py @@ -55,3 +55,41 @@ def test_default_system(self): pq.set_default_units('cgs', length='mm') self.assertQuantityEqual(pq.kg.simplified, 1000*pq.g) self.assertQuantityEqual(pq.m.simplified, 1000*pq.mm) + +class TestUnitInformation(TestCase): + + def test_si(self): + pq.set_default_units(information='B') + self.assertQuantityEqual(pq.kB.simplified, pq.B*pq.kilo) + self.assertQuantityEqual(pq.MB.simplified, pq.B*pq.mega) + self.assertQuantityEqual(pq.GB.simplified, pq.B*pq.giga) + self.assertQuantityEqual(pq.TB.simplified, pq.B*pq.tera) + self.assertQuantityEqual(pq.PB.simplified, pq.B*pq.peta) + self.assertQuantityEqual(pq.EB.simplified, pq.B*pq.exa) + self.assertQuantityEqual(pq.ZB.simplified, pq.B*pq.zetta) + self.assertQuantityEqual(pq.YB.simplified, pq.B*pq.yotta) + + def test_si_aliases(self): + prefixes = ['kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zetta', 'yotta'] + for prefix in prefixes: + self.assertQuantityEqual(pq.B.rescale(prefix + 'byte'), pq.B.rescale(prefix + 'bytes')) + self.assertQuantityEqual(pq.B.rescale(prefix + 'byte'), pq.B.rescale(prefix + 'octet')) + self.assertQuantityEqual(pq.B.rescale(prefix + 'byte'), pq.B.rescale(prefix + 'octets')) + + def test_iec(self): + pq.set_default_units(information='B') + self.assertQuantityEqual(pq.KiB.simplified, pq.B*pq.kibi) + self.assertQuantityEqual(pq.MiB.simplified, pq.B*pq.mebi) + self.assertQuantityEqual(pq.GiB.simplified, pq.B*pq.gibi) + self.assertQuantityEqual(pq.TiB.simplified, pq.B*pq.tebi) + self.assertQuantityEqual(pq.PiB.simplified, pq.B*pq.pebi) + self.assertQuantityEqual(pq.EiB.simplified, pq.B*pq.exbi) + self.assertQuantityEqual(pq.ZiB.simplified, pq.B*pq.zebi) + self.assertQuantityEqual(pq.YiB.simplified, pq.B*pq.yobi) + + def test_iec_aliases(self): + prefixes = ['kibi', 'mebi', 'gibi', 'tebi', 'pebi', 'exbi', 'zebi', 'yobi'] + for prefix in prefixes: + self.assertQuantityEqual(pq.B.rescale(prefix + 'byte'), pq.B.rescale(prefix + 'bytes')) + self.assertQuantityEqual(pq.B.rescale(prefix + 'byte'), pq.B.rescale(prefix + 'octet')) + self.assertQuantityEqual(pq.B.rescale(prefix + 'byte'), pq.B.rescale(prefix + 'octets')) diff --git a/quantities/units/information.py b/quantities/units/information.py index b9c78df..0fd409a 100644 --- a/quantities/units/information.py +++ b/quantities/units/information.py @@ -15,10 +15,108 @@ symbol='B', aliases=['bytes', 'o', 'octet', 'octets'] ) +kB = kilobyte = ko = UnitInformation( + 'kilobyte', + 1000 * byte, + symbol='kB', + aliases=['kilobytes', 'kilooctet', 'kilooctets'] +) +MB = megabyte = Mo = UnitInformation( + 'megabyte', + 1000 * kilobyte, + symbol='MB', + aliases=['megabytes', 'megaoctet', 'megaoctets'] +) +GB = gigabyte = Go = UnitInformation( + 'gigabyte', + 1000 * megabyte, + symbol='GB', + aliases=['gigabytes', 'gigaoctet', 'gigaoctets'] +) +TB = terabyte = To = UnitInformation( + 'terabyte', + 1000 * gigabyte, + symbol='TB', + aliases=['terabytes', 'teraoctet', 'teraoctets'] +) +PB = petabyte = Po = UnitInformation( + 'petabyte', + 1000 * terabyte, + symbol='PB', + aliases=['petabytes', 'petaoctet', 'petaoctets'] +) +EB = exabyte = Eo = UnitInformation( + 'exabyte', + 1000 * petabyte, + symbol='EB', + aliases=['exabytes', 'exaoctet', 'exaoctets'] +) +ZB = zettabyte = Zo = UnitInformation( + 'zettabyte', + 1000 * exabyte, + symbol='ZB', + aliases=['zettabytes', 'zettaoctet', 'zettaoctets'] +) +YB = yottabyte = Yo = UnitInformation( + 'yottabyte', + 1000 * zettabyte, + symbol='YB', + aliases=['yottabytes', 'yottaoctet', 'yottaoctets'] +) Bd = baud = bps = UnitQuantity( 'baud', bit/s, symbol='Bd', ) +# IEC +KiB = kibibyte = Kio = UnitInformation( + 'kibibyte', + 1024 * byte, + symbol='KiB', + aliases=['kibibytes', 'kibioctet', 'kibioctets'] +) +MiB = mebibyte = Mio = UnitInformation( + 'mebibyte', + 1024 * kibibyte, + symbol='MiB', + aliases=['mebibytes', 'mebioctet', 'mebioctets'] +) +GiB = gibibyte = Gio = UnitInformation( + 'gibibyte', + 1024 * mebibyte, + symbol='GiB', + aliases=['gibibytes', 'gibioctet', 'gibioctets'] +) +TiB = tebibyte = Tio = UnitInformation( + 'tebibyte', + 1024 * gibibyte, + symbol='TiB', + aliases=['tebibytes', 'tebioctet', 'tebioctets'] +) +PiB = pebibyte = Pio = UnitInformation( + 'pebibyte', + 1024 * tebibyte, + symbol='PiB', + aliases=['pebibytes', 'pebioctet', 'pebioctets'] +) +EiB = exbibyte = Eio = UnitInformation( + 'exbibyte', + 1024 * pebibyte, + symbol='EiB', + aliases=['exbibytes', 'exbioctet', 'exbioctets'] +) +ZiB = zebibyte = Zio = UnitInformation( + 'zebibyte', + 1024 * exbibyte, + symbol='ZiB', + aliases=['zebibytes', 'zebioctet', 'zebioctets'] +) +YiB = yobibyte = Yio = UnitInformation( + 'yobibyte', + 1024 * zebibyte, + symbol='YiB', + aliases=['yobibytes', 'yobioctet', 'yobioctets'] +) + del UnitQuantity, s, dimensionless From 5d8eaf33d553c811cea81e0a1e844b7df54d8180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Wed, 8 Nov 2017 17:10:53 +0100 Subject: [PATCH 078/212] Add (failing) test for numpy.linspace --- quantities/tests/test_umath.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 5a37c61..5865cc7 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -38,6 +38,9 @@ def test_diff(self): def test_ediff1d(self): self.assertQuantityEqual(np.diff(self.q, 1), [1, 1, 1] * pq.J) + def test_linspace(self): + self.assertQuantityEqual(np.linspace(self.q[0], self.q[-1], 4), self.q) + @unittest.expectedFailure def test_gradient(self): try: From 1bd7e753cf9c9259809c61faabbc3c3de2cf2131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Wed, 8 Nov 2017 17:12:41 +0100 Subject: [PATCH 079/212] Propagate keyword arguments in astype --- quantities/quantity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index c388751..07b22ce 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -214,9 +214,9 @@ def rescale(self, units): return Quantity(cf*self.magnitude, to_u) @with_doc(np.ndarray.astype) - def astype(self, dtype=None): + def astype(self, dtype=None, **kwargs): '''Scalars are returned as scalar Quantity arrays.''' - ret = super(Quantity, self.view(Quantity)).astype(dtype) + ret = super(Quantity, self.view(Quantity)).astype(dtype, **kwargs) # scalar quantities get converted to plain numbers, so we fix it # might be related to numpy ticket # 826 if not isinstance(ret, type(self)): From 1ef120bb3dc0a91140fddeaf8126b399d4ec1e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Dahlgren?= Date: Mon, 2 Jul 2018 23:15:41 +0200 Subject: [PATCH 080/212] Avoid invalid esc seq in re.sub template (fixes gh-148) (#149) * Avoid esc seq in re.sub template (fixes gh-148) * Use 3.7-dev on Travis CI for now * Make Travis CI test matrix sparse (latest NumPy for latest Python) * Make example in README robust wrt. numpy version * Explicitly list our 29 elements in the build matrix * Fix typo in .travis.yml --- .gitignore | 1 + .travis.yml | 73 ++++++++++++++++++++++++++++++++++++-------- README.rst | 4 +-- quantities/markup.py | 2 +- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 051220d..5d19806 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist .pydevproject .settings .idea +.*cache/ diff --git a/.travis.yml b/.travis.yml index ff19c03..17e4c61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,65 @@ language: python sudo: false -env: - matrix: - - NUMPY_VERSION="1.8.2" - - NUMPY_VERSION="1.9.1" - - NUMPY_VERSION="1.10.4" - - NUMPY_VERSION="1.11.2" - - NUMPY_VERSION="1.12.1" - - NUMPY_VERSION="1.13.0" -python: - - 2.7 - - 3.4 - - 3.5 - - 3.6 +matrix: + include: + - python: "2.7" + env: NUMPY_VERSION="1.8.2" + - python: "2.7" + env: NUMPY_VERSION="1.9.3" + - python: "2.7" + env: NUMPY_VERSION="1.10.4" + - python: "2.7" + env: NUMPY_VERSION="1.11.3" + - python: "2.7" + env: NUMPY_VERSION="1.12.1" + - python: "2.7" + env: NUMPY_VERSION="1.13.3" + - python: "2.7" + env: NUMPY_VERSION="1.14.5" + - python: "3.4" + env: NUMPY_VERSION="1.8.2" + - python: "3.4" + env: NUMPY_VERSION="1.9.3" + - python: "3.4" + env: NUMPY_VERSION="1.10.4" + - python: "3.4" + env: NUMPY_VERSION="1.11.3" + - python: "3.4" + env: NUMPY_VERSION="1.12.1" + - python: "3.4" + env: NUMPY_VERSION="1.13.3" + - python: "3.4" + env: NUMPY_VERSION="1.14.5" + - python: "3.5" + env: NUMPY_VERSION="1.8.2" + - python: "3.5" + env: NUMPY_VERSION="1.9.3" + - python: "3.5" + env: NUMPY_VERSION="1.10.4" + - python: "3.5" + env: NUMPY_VERSION="1.11.3" + - python: "3.5" + env: NUMPY_VERSION="1.12.1" + - python: "3.5" + env: NUMPY_VERSION="1.13.3" + - python: "3.5" + env: NUMPY_VERSION="1.14.5" + - python: "3.6" + env: NUMPY_VERSION="1.8.2" + - python: "3.6" + env: NUMPY_VERSION="1.9.3" + - python: "3.6" + env: NUMPY_VERSION="1.10.4" + - python: "3.6" + env: NUMPY_VERSION="1.11.3" + - python: "3.6" + env: NUMPY_VERSION="1.12.1" + - python: "3.6" + env: NUMPY_VERSION="1.13.3" + - python: "3.6" + env: NUMPY_VERSION="1.14.5" + - python: "3.7-dev" + env: NUMPY_VERSION="1.14.5" before_install: - pip install "numpy==$NUMPY_VERSION" install: diff --git a/README.rst b/README.rst index 8f91699..1aa35fd 100644 --- a/README.rst +++ b/README.rst @@ -38,8 +38,8 @@ Here is a simple example: >>> distance = 42*pq.metre >>> time = 17*pq.second >>> velocity = distance / time - >>> velocity - array(2.4705882352941178) * m/s + >>> "%.3f %s" % (velocity.magnitude, velocity.dimensionality) + '2.471 m/s' >>> velocity + 3 Traceback (most recent call last): ... diff --git a/quantities/markup.py b/quantities/markup.py index 8714cca..a7b1a7b 100644 --- a/quantities/markup.py +++ b/quantities/markup.py @@ -86,7 +86,7 @@ def format_units_unicode(udict): return res -def format_units_latex(udict,font='mathrm',mult=r'\cdot',paren=False): +def format_units_latex(udict,font='mathrm',mult=r'\\cdot',paren=False): ''' Replace the units string provided with an equivalent latex string. From ebefa91a95561e60c987d87f75f00c6e8d9297f7 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Sun, 11 Nov 2018 23:11:42 +0100 Subject: [PATCH 081/212] Add microcoulomb and millicoulomb units (#150) --- quantities/units/electromagnetism.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/quantities/units/electromagnetism.py b/quantities/units/electromagnetism.py index 88dce95..1203085 100644 --- a/quantities/units/electromagnetism.py +++ b/quantities/units/electromagnetism.py @@ -76,6 +76,17 @@ A*s, symbol='C' ) +mC = millicoulomb = UnitQuantity( + 'millicoulomb', + 1e-3*C, + symbol='mC' +) +uC = microcoulomb = UnitQuantity( + 'microcoulomb', + 1e-6*C, + symbol='uC', + u_symbol='μC' +) V = volt = UnitQuantity( 'volt', J/C, From 89564fd6835f64f4a1a97357543bdcdc398db422 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Tue, 12 Feb 2019 20:31:09 +0100 Subject: [PATCH 082/212] Increase CI test space Test python-quantities with more numpy versions. --- .travis.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 17e4c61..4d3bab9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,7 @@ language: python +group: travis_latest +dist: xenial + sudo: false matrix: include: @@ -58,8 +61,17 @@ matrix: env: NUMPY_VERSION="1.13.3" - python: "3.6" env: NUMPY_VERSION="1.14.5" - - python: "3.7-dev" + - python: "3.6" + env: NUMPY_VERSION="1.15.4" + - python: "3.6" + env: NUMPY_VERSION="1.16.1" + - python: "3.7" env: NUMPY_VERSION="1.14.5" + - python: "3.7" + env: NUMPY_VERSION="1.15.4" + - python: "3.7" + env: NUMPY_VERSION="1.16.1" + before_install: - pip install "numpy==$NUMPY_VERSION" install: From 74e5ea5410b5a99dc6bc0c5eecdfd554072f3272 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 22 Feb 2019 09:53:05 +0100 Subject: [PATCH 083/212] Updated change log --- CHANGES.txt | 40 +++++++++++++++++++++++++++++++++++++--- doc/devel/release.rst | 8 +++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 83155e3..1795292 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,6 +3,40 @@ CHANGES ======= +------ +0.12.3 +------ + +Updates to support NumPy up to version 1.16.1, and Python 3.7. +Added microcoulomb and millicoulomb units. + +All changes +*********** + +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed%20updated%3A2018-07-03..2019-02-22 + + +------ +0.12.2 +------ + +Added SI multiples for the byte unit (kB, MB, ...) and the IEC units (KiB, MiB...). + +All changes +*********** + +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed%20updated%3A2017-09-01..2018-07-02 + + +------ +0.12.1 +------ + +Bugs fixed +********** + +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed%20updated%3A2017-08-02..2017-08-30 + ----- 0.12.0 ----- @@ -87,7 +121,7 @@ more robust. Bugs fixed ********** -* #1 use revolution/min instead of 1/min as definition of rpm +* #1 use revolution/min instead of 1/min as definition of rpm * #2 silently fail to import test runner if nose is not installed * #4 remove the "jiffy", as there are conflicting definitions depending on (or even within a) discipline. @@ -109,7 +143,7 @@ The log and exp functions have been removed. Quantities will work with numpy's version of these functions. Quantities development has migrated from bzr/launchpad to -git/github. Please report problems to +git/github. Please report problems to http://github.com/python-quantities/python-quantities or visit the mailing list at http://groups.google.com/group/python-quantities @@ -144,7 +178,7 @@ as well. Numpydoc, an external package developed to extend Sphinx for the numpy documentation project, is now required to build quantities' -documentation. +documentation. Bugs fixed ********** diff --git a/doc/devel/release.rst b/doc/devel/release.rst index 9eca148..62600de 100644 --- a/doc/devel/release.rst +++ b/doc/devel/release.rst @@ -5,12 +5,14 @@ Releases Creating Source Releases ======================== -Quantities is distributed as a source release for Linux and OS-X. To create a +Quantities is distributed as a source release for Linux and Mac OS. To create a source release, just do:: python setup.py register - python setup.py sdist --formats=zip,gztar upload --sign + python setup.py sdist --formats=zip,gztar + twine upload dist/quantities-.* +(replacing `x`, `y` and `z` appropriately). This will create the tgz source file and upload it to the Python Package Index. Uploading to PyPi requires a .pypirc file in your home directory, something like:: @@ -58,7 +60,7 @@ which will produce the html output and save it in build/sphinx/html. Then run:: make all-pdf cp Quantities.pdf ../html -which will generate a pdf file in the latex directory. +which will generate a pdf file in the latex directory. .. _Sphinx: http://sphinx.pocoo.org/ .. _LaTeX: http://www.latex-project.org/ From fd738db11d2b1564da21001f1a49b7e101f038a8 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Fri, 15 Mar 2019 20:14:59 +0100 Subject: [PATCH 084/212] Fix broken support for pq.Quanitty('mbar') Fix support for the unit mbar that was introduced in #72. The problem with mbar is that pq.Quanitty('mbar') raises a LookupError without this fix. --- quantities/units/pressure.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quantities/units/pressure.py b/quantities/units/pressure.py index da532a5..3cb061c 100644 --- a/quantities/units/pressure.py +++ b/quantities/units/pressure.py @@ -56,6 +56,7 @@ 'millibar', 0.001*bar, symbol='mb', + aliases=['mbar'] ) kbar = kilobar = UnitQuantity( 'kilobar', From b7d7b6723738483a4c9806f423e9c059662be976 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Sat, 17 Aug 2019 22:08:52 +0200 Subject: [PATCH 085/212] Add a __format__ implementation for Quantity Closes #156 This fix is a slightly adapated version of https://github.com/yt-project/unyt/commit/e57e1ecc1214deee36ba679f08ee46c6aa6aefbe If we want to keep support for older versions of numpy we have to add a numpy version check. For simplicity I dropped support for older versions of numpy. I hope that this is fine. --- quantities/quantity.py | 10 ++++++++++ quantities/tests/test_formatting.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 quantities/tests/test_formatting.py diff --git a/quantities/quantity.py b/quantities/quantity.py index 07b22ce..135c18d 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -359,6 +359,16 @@ def __str__(self): dims = self.dimensionality.string return '%s %s'%(str(self.magnitude), dims) + if tuple(map(int, np.__version__.split('.')[:2])) >= (1, 14): + # in numpy 1.14 the formatting of scalar values was changed + # see https://github.com/numpy/numpy/pull/9883 + + def __format__(self, format_spec): + ret = super(Quantity, self).__format__(format_spec) + if self.ndim: + return ret + return ret + ' {}'.format(self.dimensionality) + @with_doc(np.ndarray.__getitem__) def __getitem__(self, key): ret = super(Quantity, self).__getitem__(key) diff --git a/quantities/tests/test_formatting.py b/quantities/tests/test_formatting.py new file mode 100644 index 0000000..4697477 --- /dev/null +++ b/quantities/tests/test_formatting.py @@ -0,0 +1,17 @@ +from .. import units as pq +from .common import TestCase + + +class TestFormatting(TestCase): + + @staticmethod + def _check(quantity, formatted): + assert str(quantity) == formatted + assert '{}'.format(quantity) == formatted + assert '{!s}'.format(quantity) == formatted + + def test_str_format_scalar(self): + self._check(1*pq.J, '1.0 J') + + def test_str_format_non_scalar(self): + self._check([1, 2]*pq.J, '[1. 2.] J') From 799d6d883c892e57910f9907decc307f7c1397f0 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Fri, 6 Sep 2019 20:49:42 +0200 Subject: [PATCH 086/212] Fix np.arctan2 regression due to newer numpy version Due to a change in numpy 1.16.0 ndarray subclasses (=quantity) were treated differently by, e.g., np.angle if quantities were passed. This change ensures that np.arctan2 returns a dimensionaless quantity. --- quantities/dimensionality.py | 13 ++++++++++++- quantities/tests/test_umath.py | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index b2ab0e5..463772e 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -287,7 +287,6 @@ def _d_check_uniform(q1, q2, out=None): p_dict[np.fmod] = _d_check_uniform p_dict[np.remainder] = _d_check_uniform p_dict[np.floor_divide] = _d_check_uniform -p_dict[np.arctan2] = _d_check_uniform p_dict[np.hypot] = _d_check_uniform p_dict[np.equal] = _d_check_uniform p_dict[np.not_equal] = _d_check_uniform @@ -296,6 +295,18 @@ def _d_check_uniform(q1, q2, out=None): p_dict[np.greater] = _d_check_uniform p_dict[np.greater_equal] = _d_check_uniform +def _d_arctan2(q1, q2, out=None): + try: + assert q1._dimensionality == q2._dimensionality + return Dimensionality() + except AssertionError: + raise ValueError( + 'quantities must have identical units, got "%s" and "%s"' % + (q1.units, q2.units) + ) + +p_dict[np.arctan2] = _d_arctan2 + def _d_power(q1, q2, out=None): if getattr(q2, 'dimensionality', None): raise ValueError("exponent must be dimensionless") diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 5865cc7..f5aeb78 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -222,6 +222,10 @@ def test_arctan2(self): np.arctan2(0*pq.dimensionless, 0*pq.dimensionless), 0 ) + self.assertQuantityEqual( + np.arctan2(3*pq.V, 3*pq.V), + np.radians(45)*pq.dimensionless + ) self.assertRaises(ValueError, np.arctan2, (1*pq.m, 1*pq.m)) def test_hypot(self): From e53971d6892f47e761c5781ba04eb326230cc10b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kemetm=C3=BCller?= Date: Tue, 19 Nov 2019 22:32:28 +0100 Subject: [PATCH 087/212] Fix " not supported error This adds support for the clip ufunc introduced in Numpy 1.17. Fixes: python-quantities#158 --- quantities/dimensionality.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index b2ab0e5..691857b 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -329,6 +329,13 @@ def _d_copy(q1, out=None): p_dict[np.fix] = _d_copy p_dict[np.ceil] = _d_copy +def _d_clip(a1, a2, a3, q): + return q.dimensionality +try: + p_dict[np.core.umath.clip] = _d_clip +except AttributeError: + pass # For compatibility with Numpy < 1.17 when clip wasn't a ufunc yet + def _d_sqrt(q1, out=None): return q1._dimensionality**0.5 p_dict[np.sqrt] = _d_sqrt From d8a165053aa98136e1e47f73ecb88c55ae9b748d Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Thu, 28 Nov 2019 20:33:26 +0100 Subject: [PATCH 088/212] Extend the range of versions tested by Travis CI --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4d3bab9..b01b05b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python group: travis_latest dist: xenial - + sudo: false matrix: include: @@ -71,6 +71,10 @@ matrix: env: NUMPY_VERSION="1.15.4" - python: "3.7" env: NUMPY_VERSION="1.16.1" + - python: "3.7" + env: NUMPY_VERSION="1.17.2" + - python: "3.8" + env: NUMPY_VERSION="1.17.2" before_install: - pip install "numpy==$NUMPY_VERSION" From 1a0b45052158df92fbe60b73470fafa609ee138c Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 8 Jan 2020 17:22:26 +0100 Subject: [PATCH 089/212] Updated change log --- CHANGES.txt | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 1795292..88220cb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,20 @@ CHANGES ======= +------ +0.12.4 +------ + +- Fix broken support for `pq.Quanitty('mbar')` +- Add a `__format__` implementation for Quantity +- Fix `np.arctan2` regression due to newer numpy version +- Fix " not supported" error +- Test against Python 3.8 and NumPy 1.17 + +All changes +*********** + +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed+closed%3A2019-02-23..2020-01-08+ ------ 0.12.3 @@ -13,7 +27,7 @@ Added microcoulomb and millicoulomb units. All changes *********** -https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed%20updated%3A2018-07-03..2019-02-22 +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed%20closed%3A2018-07-03..2019-02-22 ------ @@ -25,7 +39,7 @@ Added SI multiples for the byte unit (kB, MB, ...) and the IEC units (KiB, MiB.. All changes *********** -https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed%20updated%3A2017-09-01..2018-07-02 +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed%20closed%3A2017-09-01..2018-07-02 ------ @@ -35,7 +49,7 @@ https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Acl Bugs fixed ********** -https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed%20updated%3A2017-08-02..2017-08-30 +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed%20closed%3A2017-08-02..2017-08-30 ----- 0.12.0 @@ -50,7 +64,7 @@ Added more ufuncs: equal, not_equal, less, less_equal, greater, greater_equal Bugs fixed ********** -https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aissue%20is%3Aclosed%20updated%3A2015-12-06..2017-08-01 +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aissue%20is%3Aclosed%20closed%3A2015-12-06..2017-08-01 ----- @@ -66,7 +80,7 @@ The test suite is now run with Travis CI Bugs fixed ********** -https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aissue%20is%3Aclosed%20updated%3A2011-09-27..2015-12-06 +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aissue%20is%3Aclosed%20closed%3A2011-09-27..2015-12-06 ----- From fa0b684d357c5c8ae0c8406fdb42a3bccb245d16 Mon Sep 17 00:00:00 2001 From: Rick Gerkin Date: Tue, 28 Jan 2020 21:45:38 +0000 Subject: [PATCH 090/212] Added preferred units support for .rescale --- quantities/quantity.py | 21 +++++++++++++-- quantities/tests/test_conversion.py | 40 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 135c18d..4fb3e82 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -13,6 +13,10 @@ from .registry import unit_registry from .decorators import with_doc +PREFERRED = [] # List of preferred quanitities for each symbol, + # e.g. PREFERRED = [pq.mV, pq.pA, pq.UnitQuantity('femtocoulomb', 1e-15*pq.C, 'fC')] + # Intended to be overwritten in down-stream packages + if sys.version.startswith('3'): unicode = str @@ -195,10 +199,15 @@ def units(self, units): mag *= cf self._dimensionality = to_u.dimensionality - def rescale(self, units): + def rescale(self, units=None): """ Return a copy of the quantity converted to the specified units """ + if units is None: + try: + return self.rescale_preferred() + except Exception as e: + raise Exception('No argument passed to `.rescale` and %s' % e) to_dims = validate_dimensionality(units) if self.dimensionality == to_dims: return self.astype(self.dtype) @@ -212,7 +221,15 @@ def rescale(self, units): %(from_u._dimensionality, to_u._dimensionality) ) return Quantity(cf*self.magnitude, to_u) - + + def rescale_preferred(self): + units_str = str(self.simplified.dimensionality) + for preferred in PREFERRED: + if units_str == str(preferred.simplified.dimensionality): + return self.rescale(preferred) + raise Exception(("Preferred units for '%s' (or equivalent) not specified in " + "quantites.quantity.PREFERRED." % self.dimensionality)) + @with_doc(np.ndarray.astype) def astype(self, dtype=None, **kwargs): '''Scalars are returned as scalar Quantity arrays.''' diff --git a/quantities/tests/test_conversion.py b/quantities/tests/test_conversion.py index 7fb8502..fb042a3 100644 --- a/quantities/tests/test_conversion.py +++ b/quantities/tests/test_conversion.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- +import unittest from .. import units as pq +from .. import quantity from .common import TestCase @@ -15,6 +17,44 @@ def test_inplace_conversion(self): def test_rescale(self): for u in ('ft', 'feet', pq.ft): self.assertQuantityEqual((10*pq.m).rescale(u), 32.80839895 * pq.ft) + + def test_rescale_preferred(self): + quantity.PREFERRED = [pq.mV, pq.pA] + q = 10*pq.V + self.assertQuantityEqual(q.rescale_preferred(), q.rescale(pq.mV)) + q = 5*pq.A + self.assertQuantityEqual(q.rescale_preferred(), q.rescale(pq.pA)) + quantity.PREFERRED = [] + + def test_rescale_preferred_failure(self): + quantity.PREFERRED = [pq.pA] + q = 10*pq.V + try: + self.assertQuantityEqual(q.rescale_preferred(), q.rescale(pq.mV)) + except: + self.assertTrue(True) + else: + self.assertTrue(False) + quantity.PREFERRED = [] + + def test_rescale_noargs(self): + quantity.PREFERRED = [pq.mV, pq.pA] + q = 10*pq.V + self.assertQuantityEqual(q.rescale(), q.rescale(pq.mV)) + q = 5*pq.A + self.assertQuantityEqual(q.rescale(), q.rescale(pq.pA)) + quantity.PREFERRED = [] + + def test_rescale_noargs_failure(self): + quantity.PREFERRED = [pq.pA] + q = 10*pq.V + try: + self.assertQuantityEqual(q.rescale_preferred(), q.rescale(pq.mV)) + except: + self.assertTrue(True) + else: + self.assertTrue(False) + quantity.PREFERRED = [] def test_compound_reduction(self): pc_per_cc = pq.CompoundUnit("pc/cm**3") From 1a0dc157c5c2cd91a4df26752a2811f6da32c4af Mon Sep 17 00:00:00 2001 From: Richard C Gerkin Date: Thu, 13 Feb 2020 09:48:50 -0700 Subject: [PATCH 091/212] Docstring changes for rescale and rescale_preferred --- quantities/quantity.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 4fb3e82..ec78385 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -201,7 +201,9 @@ def units(self, units): def rescale(self, units=None): """ - Return a copy of the quantity converted to the specified units + Return a copy of the quantity converted to the specified units. + If `units` is `None`, an attempt will be made to rescale the quantity + to preferred units (see `rescale_preferred`). """ if units is None: try: @@ -223,6 +225,18 @@ def rescale(self, units=None): return Quantity(cf*self.magnitude, to_u) def rescale_preferred(self): + """ + Return a copy of the quantity converted to the preferred units and scale. + These will be identified from among the compatible units specified in the + list PREFERRED in this module. For example, a voltage quantity might be + converted to `mV`: + ``` + import quantities as pq + pq.quantity.PREFERRED = [pq.mV, pq.pA] + old = 3.1415 * pq.V + new = old.rescale_preferred() # `new` will be 3141.5 mV. + ``` + """ units_str = str(self.simplified.dimensionality) for preferred in PREFERRED: if units_str == str(preferred.simplified.dimensionality): From a15e9fc71d54ace1827759f18a3b1901088983fd Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 6 May 2020 09:27:43 +0200 Subject: [PATCH 092/212] Add a more recent NumPy to the test matrix for Python 2.7 (to help with #175) --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index b01b05b..4dd742a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,8 @@ matrix: env: NUMPY_VERSION="1.13.3" - python: "2.7" env: NUMPY_VERSION="1.14.5" + - python: "2.7" + env: NUMPY_VERSION="1.15.1" - python: "3.4" env: NUMPY_VERSION="1.8.2" - python: "3.4" From bccfed25aa7b82824689f71fe498e70083cb6ac0 Mon Sep 17 00:00:00 2001 From: jadoro Date: Wed, 8 Jul 2020 13:20:06 +0200 Subject: [PATCH 093/212] Add dimensionless unit 'lsb' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dimensionless qualifier 'lsb' (least significant bit) is commonly used when describing the behavior of electronic instruments, especially in the context of A/D and D/A converters. It denotes a given change of the digital value in units of the smallest possible change – one LSB. --- quantities/units/dimensionless.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/quantities/units/dimensionless.py b/quantities/units/dimensionless.py index bf6a73b..8800530 100644 --- a/quantities/units/dimensionless.py +++ b/quantities/units/dimensionless.py @@ -17,4 +17,11 @@ aliases=['cts', 'counts'] ) +lsb = UnitQuantity( + 'least_significant_bit', + 1*dimensionless, + symbol='lsb', + aliases=['lsbs'] +) + del UnitQuantity From 78dd381808d6d0b9e60530bb03d696825d1acb4c Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Wed, 17 Mar 2021 21:03:24 +0100 Subject: [PATCH 094/212] Add SI multiples for Kelvin See table in https://en.wikipedia.org/wiki/Orders_of_magnitude_(temperature)#SI_multiples --- quantities/units/temperature.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/quantities/units/temperature.py b/quantities/units/temperature.py index 7dd3828..df06f48 100644 --- a/quantities/units/temperature.py +++ b/quantities/units/temperature.py @@ -11,6 +11,35 @@ symbol='K', aliases=['degK', 'kelvin'] ) +for prefix, symbolprefix, magnitude in ( + ('yotta', 'Y', 1e24), + ('zetta', 'Z', 1e21), + ('exa', 'E', 1e18), + ('peta', 'P', 1e15), + ('tera', 'T', 1e12), + ('giga', 'G', 1e9), + ('mega', 'M', 1e6), + ('kilo', 'k', 1e3), + ('hecto', 'h', 1e2), + ('deka', 'da', 1e1), + ('deci', 'd', 1e-1), + ('centi', 'c', 1e-2), + ('milli', 'm', 1e-3), + ('micro', 'u', 1e-6), + ('nano', 'n', 1e-9), + ('pico', 'p', 1e-12), + ('femto', 'f', 1e-15), + ('atto', 'a', 1e-18), + ('zepto', 'z', 1e-21), + ('yocto', 'y', 1e-24), +): + symbol = symbolprefix +'K' + globals()[symbol] = UnitTemperature( + prefix + 'kelvin', + K*magnitude, + symbol=symbol + ) + degR = rankine = Rankine = UnitTemperature( 'Rankine', K/1.8, From fcc68303a54b1cfac63d557294ae1ff835f311d6 Mon Sep 17 00:00:00 2001 From: Richard Gerkin Date: Thu, 27 May 2021 22:34:02 -0700 Subject: [PATCH 095/212] Fixed non-list tolist() bug --- quantities/quantity.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index ec78385..39b8582 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -461,7 +461,10 @@ def tolist(self): #first get a dummy array from the ndarray method work_list = self.magnitude.tolist() #now go through and replace all numbers with the appropriate Quantity - self._tolist(work_list) + if isinstance(work_list, list): + self._tolist(work_list) + else: + work_list = Quantity(work_list, self.dimensionality) return work_list def _tolist(self, work_list): From b387d63c148ebc1890ade3a61297d36aa211fe30 Mon Sep 17 00:00:00 2001 From: Richard Gerkin Date: Sun, 30 May 2021 11:25:26 -0700 Subject: [PATCH 096/212] Updated unit test for .tolist() on singleton np arrays --- quantities/tests/test_methods.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index 039fb6d..75538f4 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -11,6 +11,8 @@ def setUp(self): def test_tolist(self): self.assertEqual(self.q.tolist(), [[1*pq.m, 2*pq.m], [3*pq.m, 4*pq.m]]) + q_singleton = 1 * pq.m + self.assertEqual(q_singleton.tolist(), q_singleton) def test_sum(self): self.assertQuantityEqual(self.q.sum(), 10*pq.m) From 8954c942b4efdd8d73e8ccdac2b31a8854c8553b Mon Sep 17 00:00:00 2001 From: Richard Gerkin Date: Sun, 30 May 2021 11:26:18 -0700 Subject: [PATCH 097/212] Expected test failure now succeeds; updated test --- quantities/tests/test_umath.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index f5aeb78..e1bbb75 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -154,17 +154,13 @@ def test_ceil(self): [-1., -1., -0., 1., 2., 2., 2.] * pq.m ) - @unittest.expectedFailure def test_fix(self): - try: - self.assertQuantityEqual(np.fix(3.14 * pq.degF), 3.0 * pq.degF) - self.assertQuantityEqual(np.fix(3.0 * pq.degF), 3.0 * pq.degF) - self.assertQuantityEqual( - np.fix([2.1, 2.9, -2.1, -2.9] * pq.degF), - [2., 2., -2., -2.] * pq.degF - ) - except ValueError as e: - raise self.failureException(e) + self.assertQuantityEqual(np.fix(3.14 * pq.degF), 3.0 * pq.degF) + self.assertQuantityEqual(np.fix(3.0 * pq.degF), 3.0 * pq.degF) + self.assertQuantityEqual( + np.fix([2.1, 2.9, -2.1, -2.9] * pq.degF), + [2., 2., -2., -2.] * pq.degF + ) def test_exp(self): self.assertQuantityEqual(np.exp(1*pq.dimensionless), np.e) From c61a844ecbb8fd6813dafd7c5b9994cf84395e92 Mon Sep 17 00:00:00 2001 From: Daniel McCloy Date: Wed, 11 Aug 2021 16:55:28 -0500 Subject: [PATCH 098/212] add backslash --- quantities/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/__init__.py b/quantities/__init__.py index 5d43e20..47f1069 100644 --- a/quantities/__init__.py +++ b/quantities/__init__.py @@ -220,7 +220,7 @@ >>> mass = 50 * pq.kg >>> weight = mass*g >>> print weight.dimensionality.latex - $\mathrm{\\frac{kg{\\cdot}m}{s^{2}}}$ + $\\mathrm{\\frac{kg{\\cdot}m}{s^{2}}}$ >>> weight.units = pq.N >>> print weight.dimensionality.latex $\\mathrm{N}$ From 91277c81cc8a6dfc92a54664cb80ac41002b6414 Mon Sep 17 00:00:00 2001 From: Daniel McCloy Date: Wed, 11 Aug 2021 17:00:37 -0500 Subject: [PATCH 099/212] fix invalid escape in format_units_latex docstring --- quantities/markup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/markup.py b/quantities/markup.py index a7b1a7b..0943291 100644 --- a/quantities/markup.py +++ b/quantities/markup.py @@ -99,7 +99,7 @@ def format_units_latex(udict,font='mathrm',mult=r'\\cdot',paren=False): 'mathit', 'mathsf', and 'mathtt'. Multiplication (*) are replaced with the symbol specified by the mult argument. - By default this is the latex \cdot symbol. Other useful + By default this is the latex \\cdot symbol. Other useful options may be '' or '*'. If paren=True, encapsulate the string in '\left(' and '\right)' From 4992796c8209e8358432ef97c0de0c9e7d5cae81 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Mon, 16 Aug 2021 22:02:54 +0100 Subject: [PATCH 100/212] Updated change log --- CHANGES.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 88220cb..f2e5cea 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,20 @@ CHANGES ======= +------ +0.12.5 +------ + +- Added preferred units support for .rescale +- Added dimensionless unit 'lsb' (least significant bit) +- Added SI multiples for Kelvin +- Fixed invalid escape sequence + +All changes +*********** + +https://github.com/python-quantities/python-quantities/issues?utf8=✓&q=is%3Aclosed+closed%3A2020-01-08..2021-08-16 + ------ 0.12.4 ------ From c6699f60bbd172504822e12e54c662c381c926b8 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 28 Aug 2021 19:43:26 +0300 Subject: [PATCH 101/212] Replace Travis CI with GitHub Actions --- .github/workflows/test.yml | 93 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 89 ------------------------------------ 2 files changed, 93 insertions(+), 89 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f316890 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,93 @@ +name: Test + +on: [push, pull_request] + +env: + FORCE_COLOR: 1 + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + python-version: [ "2.7", "3.5", "3.6", "3.7", "3.8" ] + numpy-version: [ + "1.8.2", + "1.9.3", + "1.10.4", + "1.11.3", + "1.12.1", + "1.13.3", + "1.14.5", + "1.15.1", + "1.15.4", + "1.16.1", + "1.17.2", + ] + exclude: + - { python-version: "2.7", numpy-version: "1.16.1" } + - { python-version: "2.7", numpy-version: "1.17.2" } + + - { python-version: "3.5", numpy-version: "1.15.1" } + - { python-version: "3.5", numpy-version: "1.15.4" } + - { python-version: "3.5", numpy-version: "1.16.1" } + - { python-version: "3.5", numpy-version: "1.17.2" } + + - { python-version: "3.6", numpy-version: "1.17.2" } + + - { python-version: "3.7", numpy-version: "1.8.2" } + - { python-version: "3.7", numpy-version: "1.9.3" } + - { python-version: "3.7", numpy-version: "1.10.4" } + - { python-version: "3.7", numpy-version: "1.11.3" } + - { python-version: "3.7", numpy-version: "1.12.1" } + - { python-version: "3.7", numpy-version: "1.13.3" } + + - { python-version: "3.8", numpy-version: "1.8.2" } + - { python-version: "3.8", numpy-version: "1.9.3" } + - { python-version: "3.8", numpy-version: "1.10.4" } + - { python-version: "3.8", numpy-version: "1.11.3" } + - { python-version: "3.8", numpy-version: "1.12.1" } + - { python-version: "3.8", numpy-version: "1.13.3" } + - { python-version: "3.8", numpy-version: "1.14.5" } + - { python-version: "3.8", numpy-version: "1.15.1" } + - { python-version: "3.8", numpy-version: "1.15.4" } + - { python-version: "3.8", numpy-version: "1.16.1" } + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.numpy-version }}-v1-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.numpy-version }}-v1- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U wheel + python -m pip install "numpy==${{ matrix.numpy-version }}" + + - name: Install + run: | + python setup.py install + + - name: Test + run: | + python setup.py test + python -m doctest README.rst diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4dd742a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,89 +0,0 @@ -language: python -group: travis_latest -dist: xenial - -sudo: false -matrix: - include: - - python: "2.7" - env: NUMPY_VERSION="1.8.2" - - python: "2.7" - env: NUMPY_VERSION="1.9.3" - - python: "2.7" - env: NUMPY_VERSION="1.10.4" - - python: "2.7" - env: NUMPY_VERSION="1.11.3" - - python: "2.7" - env: NUMPY_VERSION="1.12.1" - - python: "2.7" - env: NUMPY_VERSION="1.13.3" - - python: "2.7" - env: NUMPY_VERSION="1.14.5" - - python: "2.7" - env: NUMPY_VERSION="1.15.1" - - python: "3.4" - env: NUMPY_VERSION="1.8.2" - - python: "3.4" - env: NUMPY_VERSION="1.9.3" - - python: "3.4" - env: NUMPY_VERSION="1.10.4" - - python: "3.4" - env: NUMPY_VERSION="1.11.3" - - python: "3.4" - env: NUMPY_VERSION="1.12.1" - - python: "3.4" - env: NUMPY_VERSION="1.13.3" - - python: "3.4" - env: NUMPY_VERSION="1.14.5" - - python: "3.5" - env: NUMPY_VERSION="1.8.2" - - python: "3.5" - env: NUMPY_VERSION="1.9.3" - - python: "3.5" - env: NUMPY_VERSION="1.10.4" - - python: "3.5" - env: NUMPY_VERSION="1.11.3" - - python: "3.5" - env: NUMPY_VERSION="1.12.1" - - python: "3.5" - env: NUMPY_VERSION="1.13.3" - - python: "3.5" - env: NUMPY_VERSION="1.14.5" - - python: "3.6" - env: NUMPY_VERSION="1.8.2" - - python: "3.6" - env: NUMPY_VERSION="1.9.3" - - python: "3.6" - env: NUMPY_VERSION="1.10.4" - - python: "3.6" - env: NUMPY_VERSION="1.11.3" - - python: "3.6" - env: NUMPY_VERSION="1.12.1" - - python: "3.6" - env: NUMPY_VERSION="1.13.3" - - python: "3.6" - env: NUMPY_VERSION="1.14.5" - - python: "3.6" - env: NUMPY_VERSION="1.15.4" - - python: "3.6" - env: NUMPY_VERSION="1.16.1" - - python: "3.7" - env: NUMPY_VERSION="1.14.5" - - python: "3.7" - env: NUMPY_VERSION="1.15.4" - - python: "3.7" - env: NUMPY_VERSION="1.16.1" - - python: "3.7" - env: NUMPY_VERSION="1.17.2" - - python: "3.8" - env: NUMPY_VERSION="1.17.2" - -before_install: - - pip install "numpy==$NUMPY_VERSION" -install: - - python setup.py install -script: - - python setup.py test - - python -m doctest README.rst -notifications: - email: false From 7afdff275a1c0548921b7ca4020f109a4044eb29 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 28 Aug 2021 20:14:24 +0300 Subject: [PATCH 102/212] Replace deprecated 'python setup.py test' with modern pytest --- .github/workflows/test.yml | 3 ++- README.rst | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f316890..3618783 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,6 +81,7 @@ jobs: run: | python -m pip install -U pip python -m pip install -U wheel + python -m pip install -U pytest python -m pip install "numpy==${{ matrix.numpy-version }}" - name: Install @@ -89,5 +90,5 @@ jobs: - name: Test run: | - python setup.py test + pytest python -m doctest README.rst diff --git a/README.rst b/README.rst index 1aa35fd..a7e26fe 100644 --- a/README.rst +++ b/README.rst @@ -62,12 +62,16 @@ If you install it system-wide, you may need to prefix the previous command with Tests ----- -To execute all tests, run:: +To execute all tests, install pytest:: - $ python setup.py test + $ python -m pip install pytest + +And run:: + + $ pytest in the current directory. The master branch is automatically tested by -Travis CI. +GitHub Actions. Author ------ From 7c243aa4caa09c27aa94e713f650a3cee22f8252 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 28 Aug 2021 20:29:41 +0300 Subject: [PATCH 103/212] Follow NEP 29: Python 3.7+ and NumPy 1.18+ --- .github/workflows/test.yml | 46 +++----------------------------------- README.rst | 4 ++-- doc/user/installation.rst | 12 +++++----- setup.py | 4 ++-- 4 files changed, 13 insertions(+), 53 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3618783..ca712a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,48 +12,8 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "2.7", "3.5", "3.6", "3.7", "3.8" ] - numpy-version: [ - "1.8.2", - "1.9.3", - "1.10.4", - "1.11.3", - "1.12.1", - "1.13.3", - "1.14.5", - "1.15.1", - "1.15.4", - "1.16.1", - "1.17.2", - ] - exclude: - - { python-version: "2.7", numpy-version: "1.16.1" } - - { python-version: "2.7", numpy-version: "1.17.2" } - - - { python-version: "3.5", numpy-version: "1.15.1" } - - { python-version: "3.5", numpy-version: "1.15.4" } - - { python-version: "3.5", numpy-version: "1.16.1" } - - { python-version: "3.5", numpy-version: "1.17.2" } - - - { python-version: "3.6", numpy-version: "1.17.2" } - - - { python-version: "3.7", numpy-version: "1.8.2" } - - { python-version: "3.7", numpy-version: "1.9.3" } - - { python-version: "3.7", numpy-version: "1.10.4" } - - { python-version: "3.7", numpy-version: "1.11.3" } - - { python-version: "3.7", numpy-version: "1.12.1" } - - { python-version: "3.7", numpy-version: "1.13.3" } - - - { python-version: "3.8", numpy-version: "1.8.2" } - - { python-version: "3.8", numpy-version: "1.9.3" } - - { python-version: "3.8", numpy-version: "1.10.4" } - - { python-version: "3.8", numpy-version: "1.11.3" } - - { python-version: "3.8", numpy-version: "1.12.1" } - - { python-version: "3.8", numpy-version: "1.13.3" } - - { python-version: "3.8", numpy-version: "1.14.5" } - - { python-version: "3.8", numpy-version: "1.15.1" } - - { python-version: "3.8", numpy-version: "1.15.4" } - - { python-version: "3.8", numpy-version: "1.16.1" } + python-version: [ "3.7", "3.8", "3.9" ] + numpy-version: [ "1.18", "1.19", "1.20", "1.21" ] steps: - uses: actions/checkout@v2 @@ -90,5 +50,5 @@ jobs: - name: Test run: | - pytest + PY_IGNORE_IMPORTMISMATCH=1 pytest python -m doctest README.rst diff --git a/README.rst b/README.rst index a7e26fe..6e50252 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ quantities :target: http://travis-ci.org/python-quantities/python-quantities A Python package for handling physical quantities. The source code and issue -tracker are hosted on github: +tracker are hosted on GitHub: https://www.github.com/python-quantities/python-quantities @@ -19,7 +19,7 @@ Download Get the latest version of quantities from https://pypi.python.org/pypi/quantities/ -To get the git version do:: +To get the Git version do:: $ git clone git://github.com/python-quantities/python-quantities.git diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 65c1f9e..0c891bf 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -8,14 +8,14 @@ Prerequisites Quantities has a few dependencies: -* Python_ (>=2.7) -* NumPy_ (>=1.8.2) +* Python_ (>=3.7) +* NumPy_ (>=1.18) Source Code Installation ======================== -To install Quantities, download the Quantites sourcecode from PyPi_ +To install Quantities, download the Quantites sourcecode from PyPI_ and run "python setup.py install" in the quantities source directory, or run "pip install quantities". @@ -26,11 +26,11 @@ You can follow and contribute to Quantities' development using git:: git clone git@github.com:python-quantities/python-quantities.git -Bugs, feature requests, and questions can be directed to the github_ +Bugs, feature requests, and questions can be directed to the GitHub_ website. .. _Python: http://www.python.org/ .. _NumPy: http://www.scipy.org -.. _PyPi: http://pypi.python.org/pypi/quantities -.. _github: http://github.com/python-quantities/python-quantities +.. _PyPI: http://pypi.python.org/pypi/quantities +.. _GitHub: http://github.com/python-quantities/python-quantities diff --git a/setup.py b/setup.py index 1e67772..88501f6 100755 --- a/setup.py +++ b/setup.py @@ -136,8 +136,8 @@ def run(self): packages = packages, platforms = 'Any', requires = [ - 'python (>=2.7.0)', - 'numpy (>=1.8.2)', + 'python (>=3.7)', + 'numpy (>=1.18)', ], url = 'http://python-quantities.readthedocs.io/', version = versioneer.get_version(), From 4809140af0341df6326fd3346197ed2a6a2a2d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 17 Aug 2021 08:24:07 +0200 Subject: [PATCH 104/212] fix expected np.arctan2() exception for numpy 1.21 NumPy 1.21 has changed the exception type for incorrect np.arctan2() arguments from ValueError to TypeError. Adjust the test appropriately. Fixes #190 --- quantities/tests/test_umath.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index f5aeb78..8d60194 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -226,7 +226,9 @@ def test_arctan2(self): np.arctan2(3*pq.V, 3*pq.V), np.radians(45)*pq.dimensionless ) - self.assertRaises(ValueError, np.arctan2, (1*pq.m, 1*pq.m)) + # NumPy <1.21 raises ValueError + # NumPy >=1.21 raises TypeError + self.assertRaises((TypeError, ValueError), np.arctan2, (1*pq.m, 1*pq.m)) def test_hypot(self): self.assertQuantityEqual(np.hypot(3 * pq.m, 4 * pq.m), 5 * pq.m) From 847ffa91caae76d5244ba81697b0335de34820e5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 28 Aug 2021 20:45:27 +0300 Subject: [PATCH 105/212] test_fix no longer fails --- quantities/tests/test_umath.py | 1 - 1 file changed, 1 deletion(-) diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 8d60194..63a3334 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -154,7 +154,6 @@ def test_ceil(self): [-1., -1., -0., 1., 2., 2., 2.] * pq.m ) - @unittest.expectedFailure def test_fix(self): try: self.assertQuantityEqual(np.fix(3.14 * pq.degF), 3.0 * pq.degF) From 5f2386408d173dd5ef15abc60cc39e645e84441c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 28 Aug 2021 20:49:43 +0300 Subject: [PATCH 106/212] Upgrade Python syntax with pyupgrade --py36-plus --- doc/conf.py | 9 ++-- doc/sphinxext/ipython_console_highlighting.py | 8 ++-- quantities/__init__.py | 1 - quantities/constants/__init__.py | 2 - quantities/constants/_utils.py | 2 - quantities/constants/alpha.py | 2 - quantities/constants/astronomy.py | 2 - quantities/constants/atomicunits.py | 2 - quantities/constants/deuteron.py | 2 - quantities/constants/electromagnetism.py | 2 - quantities/constants/electron.py | 2 - quantities/constants/helion.py | 2 - quantities/constants/mathematical.py | 2 - quantities/constants/muon.py | 2 - quantities/constants/naturalunits.py | 2 - quantities/constants/neutron.py | 2 - quantities/constants/proton.py | 2 - quantities/constants/quantum.py | 2 - quantities/constants/relationships.py | 2 - quantities/constants/statisticalmechanics.py | 2 - quantities/constants/tau.py | 2 - quantities/constants/triton.py | 2 - quantities/constants/weak.py | 2 - quantities/constants/xray.py | 2 - quantities/decorators.py | 11 ++--- quantities/dimensionality.py | 4 +- quantities/markup.py | 10 ++--- quantities/quantity.py | 41 +++++++++---------- quantities/registry.py | 1 - quantities/tests/common.py | 4 +- quantities/tests/test_arithmetic.py | 6 +-- quantities/tests/test_comparison.py | 2 - quantities/tests/test_constants.py | 2 - quantities/tests/test_conversion.py | 2 - quantities/tests/test_dimensionality.py | 2 - quantities/tests/test_formatting.py | 4 +- quantities/tests/test_methods.py | 2 - quantities/tests/test_persistence.py | 2 - quantities/tests/test_umath.py | 3 -- quantities/tests/test_uncertainty.py | 2 - quantities/tests/test_units.py | 2 - quantities/umath.py | 10 ++--- quantities/uncertainquantity.py | 20 ++++----- quantities/unitquantity.py | 3 +- quantities/units/__init__.py | 1 - quantities/units/acceleration.py | 2 - quantities/units/angle.py | 2 - quantities/units/area.py | 1 - quantities/units/compound.py | 1 - quantities/units/concentration.py | 2 - quantities/units/dimensionless.py | 1 - quantities/units/electromagnetism.py | 2 - quantities/units/energy.py | 1 - quantities/units/force.py | 1 - quantities/units/frequency.py | 1 - quantities/units/heat.py | 1 - quantities/units/information.py | 1 - quantities/units/length.py | 2 - quantities/units/mass.py | 2 - quantities/units/power.py | 1 - quantities/units/prefixes.py | 1 - quantities/units/pressure.py | 1 - quantities/units/radiation.py | 1 - quantities/units/substance.py | 2 - quantities/units/temperature.py | 2 - quantities/units/time.py | 2 - quantities/units/velocity.py | 1 - quantities/units/viscosity.py | 1 - quantities/units/volume.py | 1 - 69 files changed, 57 insertions(+), 170 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 0589cbd..d0cd127 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # quantities documentation build configuration file, created by # sphinx-quickstart on Sun Oct 25 09:49:05 2009. @@ -41,8 +40,8 @@ master_doc = 'index' # General information about the project. -project = u'quantities' -copyright = u'2009, Darren Dale' +project = 'quantities' +copyright = '2009, Darren Dale' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -178,8 +177,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'quantities.tex', u'quantities Documentation', - u'Darren Dale', 'manual'), + ('index', 'quantities.tex', 'quantities Documentation', + 'Darren Dale', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of diff --git a/doc/sphinxext/ipython_console_highlighting.py b/doc/sphinxext/ipython_console_highlighting.py index 217b779..e6e34f5 100644 --- a/doc/sphinxext/ipython_console_highlighting.py +++ b/doc/sphinxext/ipython_console_highlighting.py @@ -52,10 +52,10 @@ class IPythonConsoleLexer(Lexer): name = 'IPython console session' aliases = ['ipython'] mimetypes = ['text/x-ipython-console'] - input_prompt = re.compile("(In \[[0-9]+\]: )|( \.\.\.+:)") - output_prompt = re.compile("(Out\[[0-9]+\]: )|( \.\.\.+:)") - continue_prompt = re.compile(" \.\.\.+:") - tb_start = re.compile("\-+") + input_prompt = re.compile(r"(In \[[0-9]+\]: )|( \.\.\.+:)") + output_prompt = re.compile(r"(Out\[[0-9]+\]: )|( \.\.\.+:)") + continue_prompt = re.compile(r" \.\.\.+:") + tb_start = re.compile(r"\-+") def get_tokens_unprocessed(self, text): pylexer = PythonLexer(**self.options) diff --git a/quantities/__init__.py b/quantities/__init__.py index 47f1069..7a69610 100644 --- a/quantities/__init__.py +++ b/quantities/__init__.py @@ -265,7 +265,6 @@ """ -from __future__ import absolute_import from ._version import get_versions __version__ = get_versions()['version'] diff --git a/quantities/constants/__init__.py b/quantities/constants/__init__.py index b79ac17..9aaa9b3 100644 --- a/quantities/constants/__init__.py +++ b/quantities/constants/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from .alpha import * from .astronomy import * from .atomicunits import * diff --git a/quantities/constants/_utils.py b/quantities/constants/_utils.py index 7fe4da0..e14f4cb 100644 --- a/quantities/constants/_utils.py +++ b/quantities/constants/_utils.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from ._codata import physical_constants from quantities.quantity import Quantity from quantities.uncertainquantity import UncertainQuantity diff --git a/quantities/constants/alpha.py b/quantities/constants/alpha.py index 6840f0f..e3a1991 100644 --- a/quantities/constants/alpha.py +++ b/quantities/constants/alpha.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/astronomy.py b/quantities/constants/astronomy.py index de350e2..ba64a97 100644 --- a/quantities/constants/astronomy.py +++ b/quantities/constants/astronomy.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..uncertainquantity import UncertainQuantity diff --git a/quantities/constants/atomicunits.py b/quantities/constants/atomicunits.py index 857ecac..8e53e93 100644 --- a/quantities/constants/atomicunits.py +++ b/quantities/constants/atomicunits.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/deuteron.py b/quantities/constants/deuteron.py index 925f8e2..a6846a0 100644 --- a/quantities/constants/deuteron.py +++ b/quantities/constants/deuteron.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/electromagnetism.py b/quantities/constants/electromagnetism.py index f2b3a09..9aca2fd 100644 --- a/quantities/constants/electromagnetism.py +++ b/quantities/constants/electromagnetism.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/electron.py b/quantities/constants/electron.py index 4fbc888..865862d 100644 --- a/quantities/constants/electron.py +++ b/quantities/constants/electron.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/helion.py b/quantities/constants/helion.py index bb28c8b..dcc23a0 100644 --- a/quantities/constants/helion.py +++ b/quantities/constants/helion.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/mathematical.py b/quantities/constants/mathematical.py index 5aae3fa..abaff18 100644 --- a/quantities/constants/mathematical.py +++ b/quantities/constants/mathematical.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import math as _math from ..unitquantity import UnitConstant diff --git a/quantities/constants/muon.py b/quantities/constants/muon.py index 2dc97c8..cf81fae 100644 --- a/quantities/constants/muon.py +++ b/quantities/constants/muon.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/naturalunits.py b/quantities/constants/naturalunits.py index 2b48012..4147391 100644 --- a/quantities/constants/naturalunits.py +++ b/quantities/constants/naturalunits.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/neutron.py b/quantities/constants/neutron.py index dee1f54..84d5565 100644 --- a/quantities/constants/neutron.py +++ b/quantities/constants/neutron.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/proton.py b/quantities/constants/proton.py index 4b9bed7..1700c30 100644 --- a/quantities/constants/proton.py +++ b/quantities/constants/proton.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/quantum.py b/quantities/constants/quantum.py index 8f9650c..b98c1f7 100644 --- a/quantities/constants/quantum.py +++ b/quantities/constants/quantum.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/relationships.py b/quantities/constants/relationships.py index 9a7916e..7d90227 100644 --- a/quantities/constants/relationships.py +++ b/quantities/constants/relationships.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/statisticalmechanics.py b/quantities/constants/statisticalmechanics.py index 2d545cd..b3cbf05 100644 --- a/quantities/constants/statisticalmechanics.py +++ b/quantities/constants/statisticalmechanics.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/tau.py b/quantities/constants/tau.py index 35b8479..fdbe59a 100644 --- a/quantities/constants/tau.py +++ b/quantities/constants/tau.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/triton.py b/quantities/constants/triton.py index e2a4f0d..ba50eb5 100644 --- a/quantities/constants/triton.py +++ b/quantities/constants/triton.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/weak.py b/quantities/constants/weak.py index ac9424c..225ab9b 100644 --- a/quantities/constants/weak.py +++ b/quantities/constants/weak.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/constants/xray.py b/quantities/constants/xray.py index 5a5a866..e2f3bbe 100644 --- a/quantities/constants/xray.py +++ b/quantities/constants/xray.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ._utils import _cd from ..unitquantity import UnitConstant diff --git a/quantities/decorators.py b/quantities/decorators.py index 5c701f0..dcac5f9 100644 --- a/quantities/decorators.py +++ b/quantities/decorators.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import inspect import os import re @@ -46,10 +43,10 @@ def __call__(self, new_method): if original_doc and new_doc: new_method.__doc__ = """ - %s - %s - %s - """ % (original_doc, header, new_doc) + {} + {} + {} + """.format(original_doc, header, new_doc) elif original_doc: new_method.__doc__ = original_doc diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index 9dc7652..119ed9d 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import import sys import operator @@ -17,7 +15,7 @@ def assert_isinstance(obj, types): assert isinstance(obj, types) except AssertionError: raise TypeError( - "arg %r must be of type %r, got %r" % (obj, types, type(obj)) + f"arg {obj!r} must be of type {types!r}, got {type(obj)!r}" ) diff --git a/quantities/markup.py b/quantities/markup.py index 0943291..d22671c 100644 --- a/quantities/markup.py +++ b/quantities/markup.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import, with_statement import copy import operator @@ -9,7 +7,7 @@ import threading -class _Config(object): +class _Config: @property def lock(self): @@ -51,7 +49,7 @@ def format_units(udict): den = [] keys = [k for k, o in sorted( - [(k, k.format_order) for k in udict], + ((k, k.format_order) for k in udict), key=operator.itemgetter(1) ) ] @@ -102,7 +100,7 @@ def format_units_latex(udict,font='mathrm',mult=r'\\cdot',paren=False): By default this is the latex \\cdot symbol. Other useful options may be '' or '*'. - If paren=True, encapsulate the string in '\left(' and '\right)' + If paren=True, encapsulate the string in '\\left(' and '\right)' The result of format_units_latex is encapsulated in $. This allows the result to be used directly in Latex in normal text mode, or in Matplotlib text via the @@ -126,7 +124,7 @@ def format_units_latex(udict,font='mathrm',mult=r'\\cdot',paren=False): res = re.sub(r'\*','{'+mult+'}',res) if paren and not compound: res = r'\left(%s\right)' % res - res = r'$\%s{%s}$' % (font,res) + res = fr'$\{font}{{{res}}}$' return res diff --git a/quantities/quantity.py b/quantities/quantity.py index ec78385..6f93313 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import import copy from functools import wraps @@ -241,8 +240,8 @@ def rescale_preferred(self): for preferred in PREFERRED: if units_str == str(preferred.simplified.dimensionality): return self.rescale(preferred) - raise Exception(("Preferred units for '%s' (or equivalent) not specified in " - "quantites.quantity.PREFERRED." % self.dimensionality)) + raise Exception("Preferred units for '%s' (or equivalent) not specified in " + "quantites.quantity.PREFERRED." % self.dimensionality) @with_doc(np.ndarray.astype) def astype(self, dtype=None, **kwargs): @@ -293,70 +292,70 @@ def __array_wrap__(self, obj, context=None): @with_doc(np.ndarray.__add__) @scale_other_units def __add__(self, other): - return super(Quantity, self).__add__(other) + return super().__add__(other) @with_doc(np.ndarray.__radd__) @scale_other_units def __radd__(self, other): return np.add(other, self) - return super(Quantity, self).__radd__(other) + return super().__radd__(other) @with_doc(np.ndarray.__iadd__) @scale_other_units def __iadd__(self, other): - return super(Quantity, self).__iadd__(other) + return super().__iadd__(other) @with_doc(np.ndarray.__sub__) @scale_other_units def __sub__(self, other): - return super(Quantity, self).__sub__(other) + return super().__sub__(other) @with_doc(np.ndarray.__rsub__) @scale_other_units def __rsub__(self, other): return np.subtract(other, self) - return super(Quantity, self).__rsub__(other) + return super().__rsub__(other) @with_doc(np.ndarray.__isub__) @scale_other_units def __isub__(self, other): - return super(Quantity, self).__isub__(other) + return super().__isub__(other) @with_doc(np.ndarray.__mod__) @scale_other_units def __mod__(self, other): - return super(Quantity, self).__mod__(other) + return super().__mod__(other) @with_doc(np.ndarray.__imod__) @scale_other_units def __imod__(self, other): - return super(Quantity, self).__imod__(other) + return super().__imod__(other) @with_doc(np.ndarray.__imul__) @protected_multiplication def __imul__(self, other): - return super(Quantity, self).__imul__(other) + return super().__imul__(other) @with_doc(np.ndarray.__rmul__) def __rmul__(self, other): return np.multiply(other, self) - return super(Quantity, self).__rmul__(other) + return super().__rmul__(other) @with_doc(np.ndarray.__itruediv__) @protected_multiplication def __itruediv__(self, other): - return super(Quantity, self).__itruediv__(other) + return super().__itruediv__(other) @with_doc(np.ndarray.__rtruediv__) def __rtruediv__(self, other): return np.true_divide(other, self) - return super(Quantity, self).__rtruediv__(other) + return super().__rtruediv__(other) if sys.version_info[0] < 3: @with_doc(np.ndarray.__idiv__) @protected_multiplication def __idiv__(self, other): - return super(Quantity, self).__itruediv__(other) + return super().__itruediv__(other) @with_doc(np.ndarray.__rdiv__) def __rdiv__(self, other): @@ -371,7 +370,7 @@ def __pow__(self, other): @check_uniform @protected_power def __ipow__(self, other): - return super(Quantity, self).__ipow__(other) + return super().__ipow__(other) def __round__(self, decimals=0): return np.around(self, decimals) @@ -395,14 +394,14 @@ def __str__(self): # see https://github.com/numpy/numpy/pull/9883 def __format__(self, format_spec): - ret = super(Quantity, self).__format__(format_spec) + ret = super().__format__(format_spec) if self.ndim: return ret - return ret + ' {}'.format(self.dimensionality) + return ret + f' {self.dimensionality}' @with_doc(np.ndarray.__getitem__) def __getitem__(self, key): - ret = super(Quantity, self).__getitem__(key) + ret = super().__getitem__(key) if isinstance(ret, Quantity): return ret else: @@ -764,7 +763,7 @@ def __reduce__(self): """ Return a tuple for pickling a Quantity. """ - reconstruct,reconstruct_args,state = super(Quantity,self).__reduce__() + reconstruct,reconstruct_args,state = super().__reduce__() state = state + (self._dimensionality,) return (_reconstruct_quantity, (self.__class__, np.ndarray, (0, ), 'b', ), diff --git a/quantities/registry.py b/quantities/registry.py index 76eadd4..ec520e8 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ """ diff --git a/quantities/tests/common.py b/quantities/tests/common.py index 68d6c9d..fb5d974 100644 --- a/quantities/tests/common.py +++ b/quantities/tests/common.py @@ -26,7 +26,7 @@ def assertQuantityEqual(self, q1, q2, msg=None, delta=None): q2 = Quantity(q2) if q1.shape != q2.shape: raise self.failureException( - "Shape mismatch (%s vs %s)%s" % (q1.shape, q2.shape, msg) + f"Shape mismatch ({q1.shape} vs {q2.shape}){msg}" ) if not np.all(np.abs(q1.magnitude - q2.magnitude) < delta): raise self.failureException( @@ -38,5 +38,5 @@ def assertQuantityEqual(self, q1, q2, msg=None, delta=None): d2 = getattr(q2, '_dimensionality', None) if (d1 or d2) and not (d1 == d2): raise self.failureException( - "Dimensionalities are not equal (%s vs %s)%s" % (d1, d2, msg) + f"Dimensionalities are not equal ({d1} vs {d2}){msg}" ) diff --git a/quantities/tests/test_arithmetic.py b/quantities/tests/test_arithmetic.py index 5eb7da9..fe5670e 100644 --- a/quantities/tests/test_arithmetic.py +++ b/quantities/tests/test_arithmetic.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import operator as op from functools import partial import sys @@ -50,7 +48,7 @@ def check(f, *args, **kwargs): return (new, ) -class iter_dtypes(object): +class iter_dtypes: def __init__(self): self._i = 1 @@ -78,7 +76,7 @@ def get_dtypes(): return list(iter_dtypes()) -class iter_types(object): +class iter_types: def __init__(self, dtype): self._index = -1 diff --git a/quantities/tests/test_comparison.py b/quantities/tests/test_comparison.py index 8485aac..523dc3b 100644 --- a/quantities/tests/test_comparison.py +++ b/quantities/tests/test_comparison.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import operator as op from .. import units as pq diff --git a/quantities/tests/test_constants.py b/quantities/tests/test_constants.py index 1fa6ef7..363e5fb 100644 --- a/quantities/tests/test_constants.py +++ b/quantities/tests/test_constants.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from .. import units as pq from .. import constants as pc from .common import TestCase diff --git a/quantities/tests/test_conversion.py b/quantities/tests/test_conversion.py index fb042a3..ca82a34 100644 --- a/quantities/tests/test_conversion.py +++ b/quantities/tests/test_conversion.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import unittest from .. import units as pq from .. import quantity diff --git a/quantities/tests/test_dimensionality.py b/quantities/tests/test_dimensionality.py index 6b1597e..6d20ca2 100644 --- a/quantities/tests/test_dimensionality.py +++ b/quantities/tests/test_dimensionality.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import operator as op from .. import units as pq diff --git a/quantities/tests/test_formatting.py b/quantities/tests/test_formatting.py index 4697477..7f0e465 100644 --- a/quantities/tests/test_formatting.py +++ b/quantities/tests/test_formatting.py @@ -7,8 +7,8 @@ class TestFormatting(TestCase): @staticmethod def _check(quantity, formatted): assert str(quantity) == formatted - assert '{}'.format(quantity) == formatted - assert '{!s}'.format(quantity) == formatted + assert f'{quantity}' == formatted + assert f'{quantity!s}' == formatted def test_str_format_scalar(self): self._check(1*pq.J, '1.0 J') diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index 039fb6d..6468d61 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from .. import units as pq from .common import TestCase import numpy as np diff --git a/quantities/tests/test_persistence.py b/quantities/tests/test_persistence.py index 9dd8699..d59a8dc 100644 --- a/quantities/tests/test_persistence.py +++ b/quantities/tests/test_persistence.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import sys import pickle import copy diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 63a3334..dea2731 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import numpy as np from .. import units as pq diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index bcac52d..380ad01 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from .. import units as pq from ..uncertainquantity import UncertainQuantity from .common import TestCase diff --git a/quantities/tests/test_units.py b/quantities/tests/test_units.py index 67ebdec..2bbbf93 100644 --- a/quantities/tests/test_units.py +++ b/quantities/tests/test_units.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from .. import units as pq from .common import TestCase diff --git a/quantities/umath.py b/quantities/umath.py index 5c8019e..59445a9 100644 --- a/quantities/umath.py +++ b/quantities/umath.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import numpy as np from .quantity import Quantity @@ -67,18 +65,18 @@ def gradient(f, *varargs): # varargs = (Quantity(1),) varargsQuantities = [Quantity(i, copy=False) for i in varargs] - varargsMag = tuple([i.magnitude for i in varargsQuantities]) + varargsMag = tuple(i.magnitude for i in varargsQuantities) ret = np.gradient(f.magnitude, *varargsMag) if len(varargs) == 1: # if there was only one sample distance provided, # apply the units in all directions - return tuple([ Quantity(i, f.units/varargs[0].units) for i in ret]) + return tuple( Quantity(i, f.units/varargs[0].units) for i in ret) else: #give each output array the units of the input array #divided by the units of the spacing quantity given - return tuple([ Quantity(i, f.units/j.units) - for i,j in zip( ret, varargsQuantities)]) + return tuple( Quantity(i, f.units/j.units) + for i,j in zip( ret, varargsQuantities)) @with_doc(np.cross) def cross (a, b , axisa=-1, axisb=-1, axisc=-1, axis=None): diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index 0189300..9b3ab7e 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import import sys @@ -33,18 +31,18 @@ def __new__(cls, data, units='', uncertainty=None, dtype='d', copy=True): @Quantity.units.setter def units(self, units): - super(UncertainQuantity, self)._set_units(units) + super()._set_units(units) self.uncertainty.units = self._dimensionality @property def _reference(self): - ret = super(UncertainQuantity, self)._reference.view(UncertainQuantity) + ret = super()._reference.view(UncertainQuantity) ret.uncertainty = self.uncertainty._reference return ret @property def simplified(self): - ret = super(UncertainQuantity, self).simplified.view(UncertainQuantity) + ret = super().simplified.view(UncertainQuantity) ret.uncertainty = self.uncertainty.simplified return ret @@ -89,7 +87,7 @@ def __array_finalize__(self, obj): @with_doc(Quantity.__add__, use_header=False) @scale_other_units def __add__(self, other): - res = super(UncertainQuantity, self).__add__(other) + res = super().__add__(other) u = (self.uncertainty**2+other.uncertainty**2)**0.5 return UncertainQuantity(res, uncertainty=u, copy=False) @@ -101,7 +99,7 @@ def __radd__(self, other): @with_doc(Quantity.__sub__, use_header=False) @scale_other_units def __sub__(self, other): - res = super(UncertainQuantity, self).__sub__(other) + res = super().__sub__(other) u = (self.uncertainty**2+other.uncertainty**2)**0.5 return UncertainQuantity(res, uncertainty=u, copy=False) @@ -115,7 +113,7 @@ def __rsub__(self, other): @with_doc(Quantity.__mul__, use_header=False) def __mul__(self, other): - res = super(UncertainQuantity, self).__mul__(other) + res = super().__mul__(other) try: sru = self.relative_uncertainty oru = other.relative_uncertainty @@ -137,7 +135,7 @@ def __neg__(self): @with_doc(Quantity.__truediv__, use_header=False) def __truediv__(self, other): - res = super(UncertainQuantity, self).__truediv__(other) + res = super().__truediv__(other) try: sru = self.relative_uncertainty oru = other.relative_uncertainty @@ -164,7 +162,7 @@ def __rtruediv__(self, other): @with_doc(Quantity.__pow__, use_header=False) def __pow__(self, other): - res = super(UncertainQuantity, self).__pow__(other) + res = super().__pow__(other) res.uncertainty = res.view(Quantity) * other * self.relative_uncertainty return res @@ -287,7 +285,7 @@ def __reduce__(self): """ Return a tuple for pickling a Quantity. """ - reconstruct, reconstruct_args, state = super(UncertainQuantity, self).__reduce__() + reconstruct, reconstruct_args, state = super().__reduce__() state = state + (self._uncertainty,) return reconstruct, reconstruct_args, state diff --git a/quantities/unitquantity.py b/quantities/unitquantity.py index 2233917..c7dbd28 100644 --- a/quantities/unitquantity.py +++ b/quantities/unitquantity.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import import sys import weakref @@ -311,7 +310,7 @@ def __init__( self, name, definition=None, symbol=None, u_symbol=None, aliases=[], doc=None ): - super(IrreducibleUnit, self).__init__( + super().__init__( name, definition, symbol, u_symbol, aliases, doc ) cls = type(self) diff --git a/quantities/units/__init__.py b/quantities/units/__init__.py index 39fa644..4933a55 100644 --- a/quantities/units/__init__.py +++ b/quantities/units/__init__.py @@ -1,7 +1,6 @@ """ """ -from __future__ import absolute_import from . import prefixes from .prefixes import * diff --git a/quantities/units/acceleration.py b/quantities/units/acceleration.py index d476857..2641dc2 100644 --- a/quantities/units/acceleration.py +++ b/quantities/units/acceleration.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .time import s diff --git a/quantities/units/angle.py b/quantities/units/angle.py index ec6feb8..fbe812b 100644 --- a/quantities/units/angle.py +++ b/quantities/units/angle.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from math import pi diff --git a/quantities/units/area.py b/quantities/units/area.py index 92376d0..17c66fe 100644 --- a/quantities/units/area.py +++ b/quantities/units/area.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .length import m, rod diff --git a/quantities/units/compound.py b/quantities/units/compound.py index 5b5406a..dcc4d0b 100644 --- a/quantities/units/compound.py +++ b/quantities/units/compound.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import CompoundUnit diff --git a/quantities/units/concentration.py b/quantities/units/concentration.py index 26b220f..013ebb3 100644 --- a/quantities/units/concentration.py +++ b/quantities/units/concentration.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .substance import mol diff --git a/quantities/units/dimensionless.py b/quantities/units/dimensionless.py index 8800530..5efdd9a 100644 --- a/quantities/units/dimensionless.py +++ b/quantities/units/dimensionless.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import dimensionless, UnitQuantity diff --git a/quantities/units/electromagnetism.py b/quantities/units/electromagnetism.py index 1203085..8bde6bc 100644 --- a/quantities/units/electromagnetism.py +++ b/quantities/units/electromagnetism.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ..unitquantity import UnitCurrent, UnitLuminousIntensity, UnitQuantity from .time import s diff --git a/quantities/units/energy.py b/quantities/units/energy.py index 19a4f7f..a40f495 100644 --- a/quantities/units/energy.py +++ b/quantities/units/energy.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .force import dyne, N diff --git a/quantities/units/force.py b/quantities/units/force.py index 8a50164..115080f 100644 --- a/quantities/units/force.py +++ b/quantities/units/force.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .mass import gram, kg, ounce, lb diff --git a/quantities/units/frequency.py b/quantities/units/frequency.py index 0c28b1a..24c20c7 100644 --- a/quantities/units/frequency.py +++ b/quantities/units/frequency.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .angle import revolution diff --git a/quantities/units/heat.py b/quantities/units/heat.py index 5069411..4ca1509 100644 --- a/quantities/units/heat.py +++ b/quantities/units/heat.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .temperature import K, degF diff --git a/quantities/units/information.py b/quantities/units/information.py index 0fd409a..d138577 100644 --- a/quantities/units/information.py +++ b/quantities/units/information.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity, UnitInformation, dimensionless from .time import s diff --git a/quantities/units/length.py b/quantities/units/length.py index 0a7e822..422e467 100644 --- a/quantities/units/length.py +++ b/quantities/units/length.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ..unitquantity import UnitLength, UnitQuantity diff --git a/quantities/units/mass.py b/quantities/units/mass.py index c4f27c8..02c67d8 100644 --- a/quantities/units/mass.py +++ b/quantities/units/mass.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity, UnitMass from .length import m diff --git a/quantities/units/power.py b/quantities/units/power.py index 06774eb..4bf99ca 100644 --- a/quantities/units/power.py +++ b/quantities/units/power.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .energy import Btu, J diff --git a/quantities/units/prefixes.py b/quantities/units/prefixes.py index 5b4a280..fbef430 100644 --- a/quantities/units/prefixes.py +++ b/quantities/units/prefixes.py @@ -1,4 +1,3 @@ - #SI prefixes yotta = 1e24 zetta = 1e21 diff --git a/quantities/units/pressure.py b/quantities/units/pressure.py index 3cb061c..fcacfac 100644 --- a/quantities/units/pressure.py +++ b/quantities/units/pressure.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .acceleration import gravity diff --git a/quantities/units/radiation.py b/quantities/units/radiation.py index 99771c1..0830820 100644 --- a/quantities/units/radiation.py +++ b/quantities/units/radiation.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .time import s diff --git a/quantities/units/substance.py b/quantities/units/substance.py index 0dad28b..2754aa3 100644 --- a/quantities/units/substance.py +++ b/quantities/units/substance.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ..unitquantity import UnitSubstance diff --git a/quantities/units/temperature.py b/quantities/units/temperature.py index df06f48..db7c70e 100644 --- a/quantities/units/temperature.py +++ b/quantities/units/temperature.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ..unitquantity import UnitTemperature diff --git a/quantities/units/time.py b/quantities/units/time.py index f55436a..f331545 100644 --- a/quantities/units/time.py +++ b/quantities/units/time.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity, UnitTime diff --git a/quantities/units/velocity.py b/quantities/units/velocity.py index cf0d529..e23cb88 100644 --- a/quantities/units/velocity.py +++ b/quantities/units/velocity.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .length import m, nmi diff --git a/quantities/units/viscosity.py b/quantities/units/viscosity.py index b23195c..4b7c128 100644 --- a/quantities/units/viscosity.py +++ b/quantities/units/viscosity.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .time import s diff --git a/quantities/units/volume.py b/quantities/units/volume.py index ea6eeb4..56ec30c 100644 --- a/quantities/units/volume.py +++ b/quantities/units/volume.py @@ -1,6 +1,5 @@ """ """ -from __future__ import absolute_import from ..unitquantity import UnitQuantity from .length import cm, m, foot, inch From 80290b87cf5ed7304bcd28117239c9bbb7b1792e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 28 Aug 2021 20:54:05 +0300 Subject: [PATCH 107/212] Drop support for EOL Python 2.7 --- quantities/dimensionality.py | 11 ----------- quantities/quantity.py | 16 +--------------- quantities/tests/test_persistence.py | 24 +++++++----------------- quantities/uncertainquantity.py | 6 ------ quantities/unitquantity.py | 15 --------------- 5 files changed, 8 insertions(+), 64 deletions(-) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index 119ed9d..cea68ca 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -1,7 +1,6 @@ """ """ -import sys import operator import numpy as np @@ -147,11 +146,6 @@ def __truediv__(self, other): new[unit] = -power return new - if sys.version_info[0] < 3: - def __div__(self, other): - assert_isinstance(other, Dimensionality) - return self.__truediv__(other) - def __itruediv__(self, other): assert_isinstance(other, Dimensionality) for unit, power in other.items(): @@ -163,11 +157,6 @@ def __itruediv__(self, other): self[unit] = -power return self - if sys.version_info[0] < 3: - def __idiv__(self, other): - assert_isinstance(other, Dimensionality) - return self.__itruediv__(other) - def __pow__(self, other): try: assert np.isscalar(other) diff --git a/quantities/quantity.py b/quantities/quantity.py index 6f93313..8d59939 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -3,7 +3,6 @@ import copy from functools import wraps -import sys import numpy as np @@ -16,9 +15,6 @@ # e.g. PREFERRED = [pq.mV, pq.pA, pq.UnitQuantity('femtocoulomb', 1e-15*pq.C, 'fC')] # Intended to be overwritten in down-stream packages -if sys.version.startswith('3'): - unicode = str - def validate_unit_quantity(value): try: assert isinstance(value, Quantity) @@ -32,7 +28,7 @@ def validate_unit_quantity(value): return value def validate_dimensionality(value): - if isinstance(value, (str, unicode)): + if isinstance(value, str): try: return unit_registry[value].dimensionality except (KeyError, UnicodeDecodeError): @@ -351,16 +347,6 @@ def __rtruediv__(self, other): return np.true_divide(other, self) return super().__rtruediv__(other) - if sys.version_info[0] < 3: - @with_doc(np.ndarray.__idiv__) - @protected_multiplication - def __idiv__(self, other): - return super().__itruediv__(other) - - @with_doc(np.ndarray.__rdiv__) - def __rdiv__(self, other): - return np.divide(other, self) - @with_doc(np.ndarray.__pow__) @check_uniform def __pow__(self, other): diff --git a/quantities/tests/test_persistence.py b/quantities/tests/test_persistence.py index d59a8dc..da26924 100644 --- a/quantities/tests/test_persistence.py +++ b/quantities/tests/test_persistence.py @@ -1,4 +1,3 @@ -import sys import pickle import copy @@ -55,22 +54,13 @@ def test_backward_compat(self): UncertainQuantity(20, 'm', 0.2), constants.m_e, ] - if sys.version_info[0] < 3: - data = [ - # generated in python 2 with protocol=-1 (i.e. newest == 2) - b'\x80\x02cquantities.unitquantity\nUnitLength\nq\x00(U\x05meterq\x01NU\x01mq\x02N]q\x03(U\x06metersq\x04U\x05metreq\x05U\x06metresq\x06eNtq\x07Rq\x08K\x01K\x02K\x02\x86q\t\x86q\nb.', - b'\x80\x02cquantities.quantity\n_reconstruct_quantity\nq\x00(cquantities.quantity\nQuantity\nq\x01cnumpy\nndarray\nq\x02K\x00\x85q\x03U\x01bq\x04tq\x05Rq\x06(K\x01)cnumpy\ndtype\nq\x07U\x02f8q\x08K\x00K\x01\x87q\tRq\n(K\x03U\x01 Date: Mon, 18 Oct 2021 09:30:58 +0200 Subject: [PATCH 108/212] Support older versions of NumPy; add Python 3.10 (this corresponds to following NEP + 1 year) --- .github/workflows/test.yml | 4 ++-- doc/user/installation.rst | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ca712a4..cf48aea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,8 +12,8 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "3.7", "3.8", "3.9" ] - numpy-version: [ "1.18", "1.19", "1.20", "1.21" ] + python-version: [ "3.7", "3.8", "3.9", "3.10" ] + numpy-version: [ "1.16", "1.17", "1.18", "1.19", "1.20", "1.21" ] steps: - uses: actions/checkout@v2 diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 0c891bf..7d0cd41 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -9,7 +9,7 @@ Prerequisites Quantities has a few dependencies: * Python_ (>=3.7) -* NumPy_ (>=1.18) +* NumPy_ (>=1.16) Source Code Installation diff --git a/setup.py b/setup.py index 88501f6..038df8a 100755 --- a/setup.py +++ b/setup.py @@ -137,7 +137,7 @@ def run(self): platforms = 'Any', requires = [ 'python (>=3.7)', - 'numpy (>=1.18)', + 'numpy (>=1.16)', ], url = 'http://python-quantities.readthedocs.io/', version = versioneer.get_version(), From 687decde7ff0b08469b2dc87820b58eba7f1725a Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Mon, 18 Oct 2021 09:53:25 +0200 Subject: [PATCH 109/212] exclude combinations of NumPy and Python versions that don't build on Github actions --- .github/workflows/test.yml | 17 +++++++++++++++-- doc/user/installation.rst | 2 ++ quantities/markup.py | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cf48aea..626bde6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,9 +12,22 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "3.7", "3.8", "3.9", "3.10" ] + python-version: [ "3.7", "3.8", "3.9" ] numpy-version: [ "1.16", "1.17", "1.18", "1.19", "1.20", "1.21" ] - + exclude: + - python-version: "3.9" + numpy-version: "1.16" + os: ubuntu-latest + - python-version: "3.9" + numpy-version: "1.17" + os: ubuntu-latest + - python-version: "3.8" + numpy-version: "1.17" + os: ubuntu-latest + include: + - python-version: "3.10" + numpy-version: "1.21" + os: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 7d0cd41..9e1bdb5 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -11,6 +11,8 @@ Quantities has a few dependencies: * Python_ (>=3.7) * NumPy_ (>=1.16) +(bearing in mind that not all combinations of Python and NumPy versions necessarily work). + Source Code Installation ======================== diff --git a/quantities/markup.py b/quantities/markup.py index d22671c..d8d4284 100644 --- a/quantities/markup.py +++ b/quantities/markup.py @@ -100,7 +100,7 @@ def format_units_latex(udict,font='mathrm',mult=r'\\cdot',paren=False): By default this is the latex \\cdot symbol. Other useful options may be '' or '*'. - If paren=True, encapsulate the string in '\\left(' and '\right)' + If paren=True, encapsulate the string in '\\left(' and '\\right)' The result of format_units_latex is encapsulated in $. This allows the result to be used directly in Latex in normal text mode, or in Matplotlib text via the From f754d202ce5306abb1db17b8c3cc9362f2731bbf Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Thu, 11 Nov 2021 06:28:18 +1100 Subject: [PATCH 110/212] docs: Fix a few typos There are small typos in: - doc/devel/documenting.rst - quantities/decorators.py - quantities/quantity.py - quantities/umath.py Fixes: - Should read `updating` rather than `updateing`. - Should read `replace` rather than `repalce`. - Should read `quantities` rather than `quanitities`. - Should read `necessary` rather than `necesssary`. - Should read `mimics` rather than `mimicks`. --- doc/devel/documenting.rst | 4 ++-- quantities/decorators.py | 2 +- quantities/quantity.py | 2 +- quantities/umath.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/devel/documenting.rst b/doc/devel/documenting.rst index d7992a1..64ffd69 100644 --- a/doc/devel/documenting.rst +++ b/doc/devel/documenting.rst @@ -257,7 +257,7 @@ and refer to it using the standard reference syntax:: Keep in mind that we may want to reorganize the contents later, so let's avoid top level names in references like ``user`` or ``devel`` -or ``faq`` unless necesssary, because for example the FAQ "what is a +or ``faq`` unless necessary, because for example the FAQ "what is a backend?" could later become part of the users guide, so the label:: .. _what-is-a-backend @@ -286,7 +286,7 @@ Emacs helpers There is an emacs mode `rst.el `_ which -automates many important ReST tasks like building and updateing +automates many important ReST tasks like building and updating table-of-contents, and promoting or demoting section headings. Here is the basic ``.emacs`` configuration:: diff --git a/quantities/decorators.py b/quantities/decorators.py index dcac5f9..bd4d0e2 100644 --- a/quantities/decorators.py +++ b/quantities/decorators.py @@ -104,7 +104,7 @@ def wrapped_function(*args , **kwargs): #convert the list back to a tuple so it can be used as an output args = tuple (args) - #repalce all the quantities in the keyword argument + #replace all the quantities in the keyword argument #dictionary with ndarrays for i in kwargs: #test if the argument is a quantity diff --git a/quantities/quantity.py b/quantities/quantity.py index 6b896b9..2da1ac8 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -11,7 +11,7 @@ from .registry import unit_registry from .decorators import with_doc -PREFERRED = [] # List of preferred quanitities for each symbol, +PREFERRED = [] # List of preferred quantities for each symbol, # e.g. PREFERRED = [pq.mV, pq.pA, pq.UnitQuantity('femtocoulomb', 1e-15*pq.C, 'fC')] # Intended to be overwritten in down-stream packages diff --git a/quantities/umath.py b/quantities/umath.py index 59445a9..45f06a3 100644 --- a/quantities/umath.py +++ b/quantities/umath.py @@ -57,7 +57,7 @@ def ediff1d(ary, to_end=None, to_begin=None): @with_doc(np.gradient) def gradient(f, *varargs): # if no sample distances are specified, use dimensionless 1 - # this mimicks the behavior of np.gradient, but perhaps we should + # this mimics the behavior of np.gradient, but perhaps we should # remove this default behavior # removed for now:: # From f874fe96728dcb9b7c1f8cf5d4ba323821ec4dbf Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 12 Jan 2022 14:59:03 +0200 Subject: [PATCH 111/212] Update bundled versioneer for Python 3.11 compatibility --- quantities/__init__.py | 5 +- quantities/_version.py | 250 +++++++++++---- versioneer.py | 713 +++++++++++++++++++++++++++++------------ 3 files changed, 689 insertions(+), 279 deletions(-) diff --git a/quantities/__init__.py b/quantities/__init__.py index 7a69610..1a20c9e 100644 --- a/quantities/__init__.py +++ b/quantities/__init__.py @@ -266,9 +266,8 @@ """ -from ._version import get_versions -__version__ = get_versions()['version'] -del get_versions +from . import _version +__version__ = _version.get_versions()['version'] from .registry import unit_registry diff --git a/quantities/_version.py b/quantities/_version.py index 028aab3..19b5da1 100644 --- a/quantities/_version.py +++ b/quantities/_version.py @@ -6,7 +6,7 @@ # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.18 (https://github.com/warner/python-versioneer) +# versioneer-0.21 (https://github.com/python-versioneer/python-versioneer) """Git implementation of _version.py.""" @@ -15,6 +15,7 @@ import re import subprocess import sys +from typing import Callable, Dict def get_keywords(): @@ -52,12 +53,12 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY = {} -HANDLERS = {} +LONG_VERSION_PY: Dict[str, str] = {} +HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" + """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: @@ -71,17 +72,17 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) - p = None - for c in commands: + process = None + for command in commands: try: - dispcmd = str([c] + args) + dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) break - except EnvironmentError: + except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue @@ -93,15 +94,13 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, if verbose: print("unable to find command, tried %s" % (commands,)) return None, None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: + stdout = process.communicate()[0].strip().decode() + if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) - return None, p.returncode - return stdout, p.returncode + return None, process.returncode + return stdout, process.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): @@ -113,15 +112,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): """ rootdirs = [] - for i in range(3): + for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} - else: - rootdirs.append(root) - root = os.path.dirname(root) # up a level + rootdirs.append(root) + root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % @@ -138,22 +136,21 @@ def git_get_keywords(versionfile_abs): # _version.py. keywords = {} try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - f.close() - except EnvironmentError: + with open(versionfile_abs, "r") as fobj: + for line in fobj: + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + except OSError: pass return keywords @@ -161,10 +158,14 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because @@ -177,11 +178,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -190,7 +191,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -199,6 +200,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] + # Filter out refs that exactly match prefix or that don't start + # with a number once the prefix is stripped (mostly a concern + # when prefix is '') + if not re.match(r'\d', r): + continue if verbose: print("picking %s" % r) return {"version": r, @@ -214,7 +220,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): +def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -222,11 +228,13 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): version string, meaning we're inside a checked out source tree. """ GITS = ["git"] + TAG_PREFIX_REGEX = "*" if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] + TAG_PREFIX_REGEX = r"\*" - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -234,15 +242,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", + "%s%s" % (tag_prefix, TAG_PREFIX_REGEX)], + cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() @@ -252,6 +261,39 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) + # --abbrev-ref was added in git-1.6.3 + if rc != 0 or branch_name is None: + raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") + branch_name = branch_name.strip() + + if branch_name == "HEAD": + # If we aren't exactly on a branch, pick a branch which represents + # the current commit. If all else fails, we are on a branchless + # commit. + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + # --contains was added in git-1.5.4 + if rc != 0 or branches is None: + raise NotThisMethod("'git branch --contains' returned error") + branches = branches.split("\n") + + # Remove the first line if we're running detached + if "(" in branches[0]: + branches.pop(0) + + # Strip off the leading "* " from the list of branches. + branches = [branch[2:] for branch in branches] + if "master" in branches: + branch_name = "master" + elif not branches: + branch_name = None + else: + # Pick the first branch that is returned. Good or bad. + branch_name = branches[0] + + pieces["branch"] = branch_name + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out @@ -268,7 +310,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: - # unparseable. Maybe git-describe is misbehaving? + # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces @@ -293,13 +335,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], - cwd=root)[0].strip() + date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces @@ -337,19 +380,67 @@ def render_pep440(pieces): return rendered -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. +def render_pep440_branch(pieces): + """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . + + The ".dev0" means not master branch. Note that .dev0 sorts backwards + (a feature branch will appear "older" than the master branch). Exceptions: - 1: no tags. 0.post.devDISTANCE + 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def pep440_split_post(ver): + """Split pep440 version string at the post-release segment. + + Returns the release segments before the post-release and the + post-release version number (or -1 if no post-release segment is present). + """ + vc = str.split(ver, ".post") + return vc[0], int(vc[1] or 0) if len(vc) == 2 else None + + +def render_pep440_pre(pieces): + """TAG[.postN.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post0.devDISTANCE + """ + if pieces["closest-tag"]: if pieces["distance"]: - rendered += ".post.dev%d" % pieces["distance"] + # update the post release segment + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + rendered = tag_version + if post_version is not None: + rendered += ".post%d.dev%d" % (post_version+1, pieces["distance"]) + else: + rendered += ".post0.dev%d" % (pieces["distance"]) + else: + # no commits, use the tag as the version + rendered = pieces["closest-tag"] else: # exception #1 - rendered = "0.post.dev%d" % pieces["distance"] + rendered = "0.post0.dev%d" % pieces["distance"] return rendered @@ -380,12 +471,41 @@ def render_pep440_post(pieces): return rendered +def render_pep440_post_branch(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . + + The ".dev0" means not master branch. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. - Eexceptions: + Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: @@ -456,10 +576,14 @@ def render(pieces, style): if style == "pep440": rendered = render_pep440(pieces) + elif style == "pep440-branch": + rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) + elif style == "pep440-post-branch": + rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": @@ -495,7 +619,7 @@ def get_versions(): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): + for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, diff --git a/versioneer.py b/versioneer.py index 64fea1c..b4cd1d6 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,5 +1,5 @@ -# Version: 0.18 +# Version: 0.21 """The Versioneer - like a rocketeer, but for versions. @@ -7,16 +7,12 @@ ============== * like a rocketeer, but for versions! -* https://github.com/warner/python-versioneer +* https://github.com/python-versioneer/python-versioneer * Brian Warner * License: Public Domain -* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy -* [![Latest Version] -(https://pypip.in/version/versioneer/badge.svg?style=flat) -](https://pypi.python.org/pypi/versioneer/) -* [![Build Status] -(https://travis-ci.org/warner/python-versioneer.png?branch=master) -](https://travis-ci.org/warner/python-versioneer) +* Compatible with: Python 3.6, 3.7, 3.8, 3.9 and pypy3 +* [![Latest Version][pypi-image]][pypi-url] +* [![Build Status][travis-image]][travis-url] This is a tool for managing a recorded version number in distutils-based python projects. The goal is to remove the tedious and error-prone "update @@ -27,9 +23,10 @@ ## Quick Install -* `pip install versioneer` to somewhere to your $PATH -* add a `[versioneer]` section to your setup.cfg (see below) +* `pip install versioneer` to somewhere in your $PATH +* add a `[versioneer]` section to your setup.cfg (see [Install](INSTALL.md)) * run `versioneer install` in your source tree, commit the results +* Verify version information with `python setup.py version` ## Version Identifiers @@ -61,7 +58,7 @@ for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has -uncommitted changes. +uncommitted changes). The version identifier is used for multiple purposes: @@ -166,7 +163,7 @@ Some situations are known to cause problems for Versioneer. This details the most significant ones. More can be found on Github -[issues page](https://github.com/warner/python-versioneer/issues). +[issues page](https://github.com/python-versioneer/python-versioneer/issues). ### Subprojects @@ -180,7 +177,7 @@ `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI distributions (and upload multiple independently-installable tarballs). * Source trees whose main purpose is to contain a C library, but which also - provide bindings to Python (and perhaps other langauges) in subdirectories. + provide bindings to Python (and perhaps other languages) in subdirectories. Versioneer will look for `.git` in parent directories, and most operations should get the right version string. However `pip` and `setuptools` have bugs @@ -194,9 +191,9 @@ Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in some later version. -[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking +[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking this issue. The discussion in -[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the +[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the issue from the Versioneer side in more detail. [pip PR#3176](https://github.com/pypa/pip/pull/3176) and [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve @@ -224,22 +221,10 @@ cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into a different virtualenv), so this can be surprising. -[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes +[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes this one, but upgrading to a newer version of setuptools should probably resolve it. -### Unicode version strings - -While Versioneer works (and is continually tested) with both Python 2 and -Python 3, it is not entirely consistent with bytes-vs-unicode distinctions. -Newer releases probably generate unicode version strings on py2. It's not -clear that this is wrong, but it may be surprising for applications when then -write these strings to a network connection or include them in bytes-oriented -APIs like cryptographic checksums. - -[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates -this question. - ## Updating Versioneer @@ -265,6 +250,14 @@ direction and include code from all supported VCS systems, reducing the number of intermediate scripts. +## Similar projects + +* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time + dependency +* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of + versioneer +* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools + plugin ## License @@ -274,19 +267,27 @@ Dedication" license (CC0-1.0), as described in https://creativecommons.org/publicdomain/zero/1.0/ . +[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg +[pypi-url]: https://pypi.python.org/pypi/versioneer/ +[travis-image]: +https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg +[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer + """ +# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring +# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements +# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error +# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with +# pylint:disable=attribute-defined-outside-init,too-many-arguments -from __future__ import print_function -try: - import configparser -except ImportError: - import ConfigParser as configparser +import configparser import errno import json import os import re import subprocess import sys +from typing import Callable, Dict class VersioneerConfig: @@ -321,12 +322,12 @@ def get_root(): # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. - me = os.path.realpath(os.path.abspath(__file__)) - me_dir = os.path.normcase(os.path.splitext(me)[0]) + my_path = os.path.realpath(os.path.abspath(__file__)) + me_dir = os.path.normcase(os.path.splitext(my_path)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) if me_dir != vsr_dir: print("Warning: build in %s is using versioneer.py from %s" - % (os.path.dirname(me), versioneer_py)) + % (os.path.dirname(my_path), versioneer_py)) except NameError: pass return root @@ -334,30 +335,29 @@ def get_root(): def get_config_from_root(root): """Read the project setup.cfg file to determine Versioneer config.""" - # This might raise EnvironmentError (if setup.cfg is missing), or + # This might raise OSError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.SafeConfigParser() - with open(setup_cfg, "r") as f: - parser.readfp(f) + parser = configparser.ConfigParser() + with open(setup_cfg, "r") as cfg_file: + parser.read_file(cfg_file) VCS = parser.get("versioneer", "VCS") # mandatory - def get(parser, name): - if parser.has_option("versioneer", name): - return parser.get("versioneer", name) - return None + # Dict-like interface for non-mandatory entries + section = parser["versioneer"] + cfg = VersioneerConfig() cfg.VCS = VCS - cfg.style = get(parser, "style") or "" - cfg.versionfile_source = get(parser, "versionfile_source") - cfg.versionfile_build = get(parser, "versionfile_build") - cfg.tag_prefix = get(parser, "tag_prefix") + cfg.style = section.get("style", "") + cfg.versionfile_source = section.get("versionfile_source") + cfg.versionfile_build = section.get("versionfile_build") + cfg.tag_prefix = section.get("tag_prefix") if cfg.tag_prefix in ("''", '""'): cfg.tag_prefix = "" - cfg.parentdir_prefix = get(parser, "parentdir_prefix") - cfg.verbose = get(parser, "verbose") + cfg.parentdir_prefix = section.get("parentdir_prefix") + cfg.verbose = section.get("verbose") return cfg @@ -366,17 +366,15 @@ class NotThisMethod(Exception): # these dictionaries contain VCS-specific tools -LONG_VERSION_PY = {} -HANDLERS = {} +LONG_VERSION_PY: Dict[str, str] = {} +HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" + """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f + HANDLERS.setdefault(vcs, {})[method] = f return f return decorate @@ -385,17 +383,17 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) - p = None - for c in commands: + process = None + for command in commands: try: - dispcmd = str([c] + args) + dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) break - except EnvironmentError: + except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue @@ -407,18 +405,16 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, if verbose: print("unable to find command, tried %s" % (commands,)) return None, None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: + stdout = process.communicate()[0].strip().decode() + if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) - return None, p.returncode - return stdout, p.returncode + return None, process.returncode + return stdout, process.returncode -LONG_VERSION_PY['git'] = ''' +LONG_VERSION_PY['git'] = r''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build @@ -426,7 +422,7 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.18 (https://github.com/warner/python-versioneer) +# versioneer-0.21 (https://github.com/python-versioneer/python-versioneer) """Git implementation of _version.py.""" @@ -435,6 +431,7 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, import re import subprocess import sys +from typing import Callable, Dict def get_keywords(): @@ -472,12 +469,12 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY = {} -HANDLERS = {} +LONG_VERSION_PY: Dict[str, str] = {} +HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" + """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: @@ -491,17 +488,17 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) - p = None - for c in commands: + process = None + for command in commands: try: - dispcmd = str([c] + args) + dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) break - except EnvironmentError: + except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue @@ -513,15 +510,13 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, if verbose: print("unable to find command, tried %%s" %% (commands,)) return None, None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: + stdout = process.communicate()[0].strip().decode() + if process.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) print("stdout was %%s" %% stdout) - return None, p.returncode - return stdout, p.returncode + return None, process.returncode + return stdout, process.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): @@ -533,15 +528,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): """ rootdirs = [] - for i in range(3): + for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} - else: - rootdirs.append(root) - root = os.path.dirname(root) # up a level + rootdirs.append(root) + root = os.path.dirname(root) # up a level if verbose: print("Tried directories %%s but none started with prefix %%s" %% @@ -558,22 +552,21 @@ def git_get_keywords(versionfile_abs): # _version.py. keywords = {} try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - f.close() - except EnvironmentError: + with open(versionfile_abs, "r") as fobj: + for line in fobj: + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + except OSError: pass return keywords @@ -581,10 +574,14 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] + # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because @@ -597,11 +594,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %%d @@ -610,7 +607,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: @@ -619,6 +616,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] + # Filter out refs that exactly match prefix or that don't start + # with a number once the prefix is stripped (mostly a concern + # when prefix is '') + if not re.match(r'\d', r): + continue if verbose: print("picking %%s" %% r) return {"version": r, @@ -634,7 +636,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): +def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -642,11 +644,13 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): version string, meaning we're inside a checked out source tree. """ GITS = ["git"] + TAG_PREFIX_REGEX = "*" if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] + TAG_PREFIX_REGEX = r"\*" - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=True) if rc != 0: if verbose: print("Directory %%s not under git control" %% root) @@ -654,15 +658,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%%s*" %% tag_prefix], - cwd=root) + describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", + "%%s%%s" %% (tag_prefix, TAG_PREFIX_REGEX)], + cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() @@ -672,6 +677,39 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) + # --abbrev-ref was added in git-1.6.3 + if rc != 0 or branch_name is None: + raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") + branch_name = branch_name.strip() + + if branch_name == "HEAD": + # If we aren't exactly on a branch, pick a branch which represents + # the current commit. If all else fails, we are on a branchless + # commit. + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + # --contains was added in git-1.5.4 + if rc != 0 or branches is None: + raise NotThisMethod("'git branch --contains' returned error") + branches = branches.split("\n") + + # Remove the first line if we're running detached + if "(" in branches[0]: + branches.pop(0) + + # Strip off the leading "* " from the list of branches. + branches = [branch[2:] for branch in branches] + if "master" in branches: + branch_name = "master" + elif not branches: + branch_name = None + else: + # Pick the first branch that is returned. Good or bad. + branch_name = branches[0] + + pieces["branch"] = branch_name + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out @@ -688,7 +726,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: - # unparseable. Maybe git-describe is misbehaving? + # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces @@ -713,13 +751,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], - cwd=root)[0].strip() + date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces @@ -757,19 +796,67 @@ def render_pep440(pieces): return rendered -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. +def render_pep440_branch(pieces): + """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . + + The ".dev0" means not master branch. Note that .dev0 sorts backwards + (a feature branch will appear "older" than the master branch). Exceptions: - 1: no tags. 0.post.devDISTANCE + 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%%d.g%%s" %% (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def pep440_split_post(ver): + """Split pep440 version string at the post-release segment. + + Returns the release segments before the post-release and the + post-release version number (or -1 if no post-release segment is present). + """ + vc = str.split(ver, ".post") + return vc[0], int(vc[1] or 0) if len(vc) == 2 else None + + +def render_pep440_pre(pieces): + """TAG[.postN.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post0.devDISTANCE + """ + if pieces["closest-tag"]: if pieces["distance"]: - rendered += ".post.dev%%d" %% pieces["distance"] + # update the post release segment + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + rendered = tag_version + if post_version is not None: + rendered += ".post%%d.dev%%d" %% (post_version+1, pieces["distance"]) + else: + rendered += ".post0.dev%%d" %% (pieces["distance"]) + else: + # no commits, use the tag as the version + rendered = pieces["closest-tag"] else: # exception #1 - rendered = "0.post.dev%%d" %% pieces["distance"] + rendered = "0.post0.dev%%d" %% pieces["distance"] return rendered @@ -800,12 +887,41 @@ def render_pep440_post(pieces): return rendered +def render_pep440_post_branch(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . + + The ".dev0" means not master branch. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%%d" %% pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%%s" %% pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0.post%%d" %% pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+g%%s" %% pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. - Eexceptions: + Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: @@ -876,10 +992,14 @@ def render(pieces, style): if style == "pep440": rendered = render_pep440(pieces) + elif style == "pep440-branch": + rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) + elif style == "pep440-post-branch": + rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": @@ -915,7 +1035,7 @@ def get_versions(): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): + for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, @@ -950,22 +1070,21 @@ def git_get_keywords(versionfile_abs): # _version.py. keywords = {} try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - f.close() - except EnvironmentError: + with open(versionfile_abs, "r") as fobj: + for line in fobj: + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + except OSError: pass return keywords @@ -973,10 +1092,14 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because @@ -989,11 +1112,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -1002,7 +1125,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -1011,6 +1134,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] + # Filter out refs that exactly match prefix or that don't start + # with a number once the prefix is stripped (mostly a concern + # when prefix is '') + if not re.match(r'\d', r): + continue if verbose: print("picking %s" % r) return {"version": r, @@ -1026,7 +1154,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): +def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -1034,11 +1162,13 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): version string, meaning we're inside a checked out source tree. """ GITS = ["git"] + TAG_PREFIX_REGEX = "*" if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] + TAG_PREFIX_REGEX = r"\*" - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -1046,15 +1176,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", + "%s%s" % (tag_prefix, TAG_PREFIX_REGEX)], + cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() @@ -1064,6 +1195,39 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) + # --abbrev-ref was added in git-1.6.3 + if rc != 0 or branch_name is None: + raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") + branch_name = branch_name.strip() + + if branch_name == "HEAD": + # If we aren't exactly on a branch, pick a branch which represents + # the current commit. If all else fails, we are on a branchless + # commit. + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + # --contains was added in git-1.5.4 + if rc != 0 or branches is None: + raise NotThisMethod("'git branch --contains' returned error") + branches = branches.split("\n") + + # Remove the first line if we're running detached + if "(" in branches[0]: + branches.pop(0) + + # Strip off the leading "* " from the list of branches. + branches = [branch[2:] for branch in branches] + if "master" in branches: + branch_name = "master" + elif not branches: + branch_name = None + else: + # Pick the first branch that is returned. Good or bad. + branch_name = branches[0] + + pieces["branch"] = branch_name + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out @@ -1080,7 +1244,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: - # unparseable. Maybe git-describe is misbehaving? + # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces @@ -1105,13 +1269,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], - cwd=root)[0].strip() + date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces @@ -1130,27 +1295,26 @@ def do_vcs_install(manifest_in, versionfile_source, ipy): if ipy: files.append(ipy) try: - me = __file__ - if me.endswith(".pyc") or me.endswith(".pyo"): - me = os.path.splitext(me)[0] + ".py" - versioneer_file = os.path.relpath(me) + my_path = __file__ + if my_path.endswith(".pyc") or my_path.endswith(".pyo"): + my_path = os.path.splitext(my_path)[0] + ".py" + versioneer_file = os.path.relpath(my_path) except NameError: versioneer_file = "versioneer.py" files.append(versioneer_file) present = False try: - f = open(".gitattributes", "r") - for line in f.readlines(): - if line.strip().startswith(versionfile_source): - if "export-subst" in line.strip().split()[1:]: - present = True - f.close() - except EnvironmentError: + with open(".gitattributes", "r") as fobj: + for line in fobj: + if line.strip().startswith(versionfile_source): + if "export-subst" in line.strip().split()[1:]: + present = True + break + except OSError: pass if not present: - f = open(".gitattributes", "a+") - f.write("%s export-subst\n" % versionfile_source) - f.close() + with open(".gitattributes", "a+") as fobj: + fobj.write(f"{versionfile_source} export-subst\n") files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) @@ -1164,15 +1328,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): """ rootdirs = [] - for i in range(3): + for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} - else: - rootdirs.append(root) - root = os.path.dirname(root) # up a level + rootdirs.append(root) + root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % @@ -1181,7 +1344,7 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.18) from +# This file was generated by 'versioneer.py' (0.21) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. @@ -1203,7 +1366,7 @@ def versions_from_file(filename): try: with open(filename) as f: contents = f.read() - except EnvironmentError: + except OSError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) @@ -1258,19 +1421,67 @@ def render_pep440(pieces): return rendered -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. +def render_pep440_branch(pieces): + """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . + + The ".dev0" means not master branch. Note that .dev0 sorts backwards + (a feature branch will appear "older" than the master branch). Exceptions: - 1: no tags. 0.post.devDISTANCE + 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def pep440_split_post(ver): + """Split pep440 version string at the post-release segment. + + Returns the release segments before the post-release and the + post-release version number (or -1 if no post-release segment is present). + """ + vc = str.split(ver, ".post") + return vc[0], int(vc[1] or 0) if len(vc) == 2 else None + + +def render_pep440_pre(pieces): + """TAG[.postN.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post0.devDISTANCE + """ + if pieces["closest-tag"]: if pieces["distance"]: - rendered += ".post.dev%d" % pieces["distance"] + # update the post release segment + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + rendered = tag_version + if post_version is not None: + rendered += ".post%d.dev%d" % (post_version+1, pieces["distance"]) + else: + rendered += ".post0.dev%d" % (pieces["distance"]) + else: + # no commits, use the tag as the version + rendered = pieces["closest-tag"] else: # exception #1 - rendered = "0.post.dev%d" % pieces["distance"] + rendered = "0.post0.dev%d" % pieces["distance"] return rendered @@ -1301,12 +1512,41 @@ def render_pep440_post(pieces): return rendered +def render_pep440_post_branch(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . + + The ".dev0" means not master branch. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. - Eexceptions: + Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: @@ -1377,10 +1617,14 @@ def render(pieces, style): if style == "pep440": rendered = render_pep440(pieces) + elif style == "pep440-branch": + rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) + elif style == "pep440-post-branch": + rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": @@ -1480,8 +1724,12 @@ def get_version(): return get_versions()["version"] -def get_cmdclass(): - """Get the custom setuptools/distutils subclasses used by Versioneer.""" +def get_cmdclass(cmdclass=None): + """Get the custom setuptools/distutils subclasses used by Versioneer. + + If the package uses a different cmdclass (e.g. one from numpy), it + should be provide as an argument. + """ if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and @@ -1495,9 +1743,9 @@ def get_cmdclass(): # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. - # Also see https://github.com/warner/python-versioneer/issues/52 + # Also see https://github.com/python-versioneer/python-versioneer/issues/52 - cmds = {} + cmds = {} if cmdclass is None else cmdclass.copy() # we add "version" to both distutils and setuptools from distutils.core import Command @@ -1539,7 +1787,9 @@ def run(self): # setup.py egg_info -> ? # we override different "build_py" commands for both environments - if "setuptools" in sys.modules: + if 'build_py' in cmds: + _build_py = cmds['build_py'] + elif "setuptools" in sys.modules: from setuptools.command.build_py import build_py as _build_py else: from distutils.command.build_py import build_py as _build_py @@ -1559,6 +1809,33 @@ def run(self): write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py + if 'build_ext' in cmds: + _build_ext = cmds['build_ext'] + elif "setuptools" in sys.modules: + from setuptools.command.build_ext import build_ext as _build_ext + else: + from distutils.command.build_ext import build_ext as _build_ext + + class cmd_build_ext(_build_ext): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + _build_ext.run(self) + if self.inplace: + # build_ext --inplace will only build extensions in + # build/lib<..> dir with no _version.py to write to. + # As in place builds will already have a _version.py + # in the module dir, we do not need to write one. + return + # now locate _version.py in the new build/ directory and replace + # it with an updated value + target_versionfile = os.path.join(self.build_lib, + cfg.versionfile_build) + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + cmds["build_ext"] = cmd_build_ext + if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe # nczeczulin reports that py2exe won't like the pep440-style string @@ -1592,10 +1869,7 @@ def run(self): del cmds["build_py"] if 'py2exe' in sys.modules: # py2exe enabled? - try: - from py2exe.distutils_buildexe import py2exe as _py2exe # py3 - except ImportError: - from py2exe.build_exe import py2exe as _py2exe # py2 + from py2exe.distutils_buildexe import py2exe as _py2exe class cmd_py2exe(_py2exe): def run(self): @@ -1620,7 +1894,9 @@ def run(self): cmds["py2exe"] = cmd_py2exe # we override different "sdist" commands for both environments - if "setuptools" in sys.modules: + if 'sdist' in cmds: + _sdist = cmds['sdist'] + elif "setuptools" in sys.modules: from setuptools.command.sdist import sdist as _sdist else: from distutils.command.sdist import sdist as _sdist @@ -1687,21 +1963,26 @@ def make_release_tree(self, base_dir, files): """ -INIT_PY_SNIPPET = """ +OLD_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ +INIT_PY_SNIPPET = """ +from . import {0} +__version__ = {0}.get_versions()['version'] +""" + def do_setup(): - """Main VCS-independent setup function for installing Versioneer.""" + """Do main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) - except (EnvironmentError, configparser.NoSectionError, + except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e: - if isinstance(e, (EnvironmentError, configparser.NoSectionError)): + if isinstance(e, (OSError, configparser.NoSectionError)): print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: @@ -1725,12 +2006,18 @@ def do_setup(): try: with open(ipy, "r") as f: old = f.read() - except EnvironmentError: + except OSError: old = "" - if INIT_PY_SNIPPET not in old: + module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] + snippet = INIT_PY_SNIPPET.format(module) + if OLD_SNIPPET in old: + print(" replacing boilerplate in %s" % ipy) + with open(ipy, "w") as f: + f.write(old.replace(OLD_SNIPPET, snippet)) + elif snippet not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: - f.write(INIT_PY_SNIPPET) + f.write(snippet) else: print(" %s unmodified" % ipy) else: @@ -1749,7 +2036,7 @@ def do_setup(): if line.startswith("include "): for include in line.split()[1:]: simple_includes.add(include) - except EnvironmentError: + except OSError: pass # That doesn't cover everything MANIFEST.in can do # (http://docs.python.org/2/distutils/sourcedist.html#commands), so From a3e4d0d4e1fdfdad36b9b5d334fea71b55cef7e0 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Thu, 13 Jan 2022 14:20:14 +0000 Subject: [PATCH 112/212] Replace Travis CI badge in README with Actions badge. Closes #198 --- README.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 6e50252..6831ada 100644 --- a/README.rst +++ b/README.rst @@ -2,14 +2,14 @@ quantities ========== -|pypi version| |pypi download| |Build status| +|pypi version|_ |Build status|_ .. |pypi version| image:: https://img.shields.io/pypi/v/quantities.png - :target: https://pypi.python.org/pypi/quantities -.. |Build status| image:: https://secure.travis-ci.org/python-quantities/python-quantities.png?branch=master - :target: http://travis-ci.org/python-quantities/python-quantities +.. _`pypi version`: https://pypi.python.org/pypi/quantities +.. |Build status| image:: https://github.com/python-quantities/python-quantities/actions/workflows/test.yml/badge.svg?branch=master +.. _`Build status`: https://github.com/python-quantities/python-quantities/actions/workflows/test.yml -A Python package for handling physical quantities. The source code and issue +A Python package for handling physical quantities. The source code and issue tracker are hosted on GitHub: https://www.github.com/python-quantities/python-quantities @@ -75,7 +75,9 @@ GitHub Actions. Author ------ -quantities is written by Darren Dale +quantities was originally written by Darren Dale, and has received contributions from `many people`_. + +.. _`many people`: https://github.com/python-quantities/python-quantities/graphs/contributors License ------- From ffd231e4859fb9e05aea5bf537311404183f51ad Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Thu, 13 Jan 2022 14:30:55 +0000 Subject: [PATCH 113/212] Updated changelog --- CHANGES.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index f2e5cea..337b3c3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,15 @@ CHANGES ======= +------ +0.13.0 +------ + +- Dropped support for Python versions older than 3.7, in particular, for Python 2.7. +- Dropped support for NumPy versions older than 1.16 +- Switched test runner to pytest, and CI to Github Actions + + ------ 0.12.5 ------ From ae3d065457fdc963299f811972daf0d034ead362 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Sat, 19 Mar 2022 13:05:37 +0100 Subject: [PATCH 114/212] Add decimeter to list of supported units --- quantities/units/length.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/quantities/units/length.py b/quantities/units/length.py index 422e467..5634f75 100644 --- a/quantities/units/length.py +++ b/quantities/units/length.py @@ -14,6 +14,12 @@ symbol='km', aliases=['kilometers', 'kilometre', 'kilometres'] ) +dm = decimeter = decimetre = UnitLength( + 'decimeter', + m/10, + 'dm', + aliases=['decimeters', 'decimetre', 'decimetres'] +) cm = centimeter = centimetre = UnitLength( 'centimeter', m/100, From 3d0afe0e90ca5bc0919376f07f68316928f7f877 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Wed, 20 Apr 2022 00:03:57 +0200 Subject: [PATCH 115/212] Remove deprecated mb as symbol for millibar unit In order to be consistent with the other pressure units that are derived from the unit bar, we should use mbar as the symbol for millibar. The symbol mb, which is a deprecated symbol, because the b in mb conflicts with the metric unit barn, was removed. Since the other bar units also define an alias with a written out SI prefix, this change also adds the unit "millibars" as an alias for mbar. --- quantities/units/pressure.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quantities/units/pressure.py b/quantities/units/pressure.py index fcacfac..fd6daf8 100644 --- a/quantities/units/pressure.py +++ b/quantities/units/pressure.py @@ -51,11 +51,11 @@ 100000*pascal, aliases=['bars'] ) -mb = mbar = millibar = UnitQuantity( +mbar = millibar = UnitQuantity( 'millibar', 0.001*bar, - symbol='mb', - aliases=['mbar'] + symbol='mbar', + aliases=['millibars'] ) kbar = kilobar = UnitQuantity( 'kilobar', From f08a50ad5d928fcf6837f612c0430d8a2c5cfbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Sun, 29 May 2022 12:14:51 +0200 Subject: [PATCH 116/212] Preserve dtype in rescale --- quantities/quantity.py | 8 ++++---- quantities/tests/test_uncertainty.py | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 2da1ac8..1cb6d96 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -198,7 +198,7 @@ def rescale(self, units=None): """ Return a copy of the quantity converted to the specified units. If `units` is `None`, an attempt will be made to rescale the quantity - to preferred units (see `rescale_preferred`). + to preferred units (see `rescale_preferred`). """ if units is None: try: @@ -217,8 +217,8 @@ def rescale(self, units=None): 'Unable to convert between units of "%s" and "%s"' %(from_u._dimensionality, to_u._dimensionality) ) - return Quantity(cf*self.magnitude, to_u) - + return Quantity(cf*self.magnitude, to_u, dtype=self.dtype) + def rescale_preferred(self): """ Return a copy of the quantity converted to the preferred units and scale. @@ -238,7 +238,7 @@ def rescale_preferred(self): return self.rescale(preferred) raise Exception("Preferred units for '%s' (or equivalent) not specified in " "quantites.quantity.PREFERRED." % self.dimensionality) - + @with_doc(np.ndarray.astype) def astype(self, dtype=None, **kwargs): '''Scalars are returned as scalar Quantity arrays.''' diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index 380ad01..62d1e9a 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -1,4 +1,5 @@ from .. import units as pq +from ..quantity import Quantity from ..uncertainquantity import UncertainQuantity from .common import TestCase import numpy as np @@ -31,6 +32,13 @@ def test_rescale(self): [0.32808399, 0.32808399, 0.32808399]*pq.ft ) + seventy_km = Quantity(70, pq.km, dtype=np.float32) + seven_km = Quantity(7, pq.km, dtype=np.float32) + seventyish_km = UncertainQuantity(seventy_km, pq.km, seven_km, dtype=np.float32) + self.assertTrue(seventyish_km.dtype == np.float32) + in_meters = seventyish_km.rescale(pq.m) + self.assertTrue(in_meters.dtype == seventyish_km.dtype) + def test_set_uncertainty(self): a = UncertainQuantity([1, 2], 'm', [.1, .2]) a.uncertainty = [1., 2.]*pq.m From 2f3d79e2ded78c8f3188d3d582185c218c681d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Sun, 29 May 2022 12:23:54 +0200 Subject: [PATCH 117/212] Add test exercising dtype preservation in astype --- quantities/quantity.py | 4 ++-- quantities/tests/test_uncertainty.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 1cb6d96..115b296 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -247,9 +247,9 @@ def astype(self, dtype=None, **kwargs): # might be related to numpy ticket # 826 if not isinstance(ret, type(self)): if self.__array_priority__ >= Quantity.__array_priority__: - ret = type(self)(ret, self._dimensionality) + ret = type(self)(ret, self._dimensionality, dtype=self.dtype) else: - ret = Quantity(ret, self._dimensionality) + ret = Quantity(ret, self._dimensionality, dtype=self.dtype) return ret diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index 62d1e9a..3e1bf00 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -38,6 +38,8 @@ def test_rescale(self): self.assertTrue(seventyish_km.dtype == np.float32) in_meters = seventyish_km.rescale(pq.m) self.assertTrue(in_meters.dtype == seventyish_km.dtype) + seventyish_km_rescaled_idempotent = seventyish_km.rescale(pq.km) + self.assertTrue(seventyish_km_rescaled_idempotent.dtype == np.float32) def test_set_uncertainty(self): a = UncertainQuantity([1, 2], 'm', [.1, .2]) From 412cdf9e4c26350904790218286273b2c2090516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Wed, 1 Jun 2022 09:40:51 +0200 Subject: [PATCH 118/212] Use dtype='d' as default in __setitem__ and put for backwards compat --- quantities/quantity.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 115b296..7bae868 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -194,7 +194,7 @@ def units(self, units): mag *= cf self._dimensionality = to_u.dimensionality - def rescale(self, units=None): + def rescale(self, units=None, dtype=None): """ Return a copy of the quantity converted to the specified units. If `units` is `None`, an attempt will be made to rescale the quantity @@ -206,8 +206,10 @@ def rescale(self, units=None): except Exception as e: raise Exception('No argument passed to `.rescale` and %s' % e) to_dims = validate_dimensionality(units) + if dtype is None: + dtype = self.dtype if self.dimensionality == to_dims: - return self.astype(self.dtype) + return self.astype(dtype) to_u = Quantity(1.0, to_dims) from_u = Quantity(1.0, self.dimensionality) try: @@ -217,7 +219,7 @@ def rescale(self, units=None): 'Unable to convert between units of "%s" and "%s"' %(from_u._dimensionality, to_u._dimensionality) ) - return Quantity(cf*self.magnitude, to_u, dtype=self.dtype) + return Quantity(cf*self.magnitude, to_u, dtype=dtype) def rescale_preferred(self): """ @@ -398,7 +400,9 @@ def __setitem__(self, key, value): if not isinstance(value, Quantity): value = Quantity(value) if self._dimensionality != value._dimensionality: - value = value.rescale(self._dimensionality) + # Setting `dtype` to 'd' is done to ensure backwards + # compatibility, arguably it's questionable design. + value = value.rescale(self._dimensionality, dtype='d') self.magnitude[key] = value @with_doc(np.ndarray.__lt__) @@ -494,15 +498,17 @@ def fill(self, value): pass @with_doc(np.ndarray.put) - def put(self, indicies, values, mode='raise'): + def put(self, indicies, values, mode='raise', dtype='d'): """ performs the equivalent of ndarray.put() but enforces units values - must be an Quantity with the same units as self """ + # The default of `dtype` is set to 'd' to ensure backwards + # compatibility, arguably it's questionable design. if not isinstance(values, Quantity): values = Quantity(values) if values._dimensionality != self._dimensionality: - values = values.rescale(self.units) + values = values.rescale(self.units, dtype=dtype) self.magnitude.put(indicies, values, mode) # choose does not function correctly, and it is not clear From cb09f6dcb3c27438ff854a27fca88aa426f53cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Wed, 1 Jun 2022 13:56:45 +0200 Subject: [PATCH 119/212] determine dtype in scale_other_units --- quantities/quantity.py | 2 +- quantities/tests/test_arithmetic.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 7bae868..4ff645b 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -59,7 +59,7 @@ def g(self, other, *args): if not isinstance(other, Quantity): other = other.view(type=Quantity) if other._dimensionality != self._dimensionality: - other = other.rescale(self.units) + other = other.rescale(self.units, dtype=np.result_type(self.dtype, other.dtype)) return f(self, other, *args) return g diff --git a/quantities/tests/test_arithmetic.py b/quantities/tests/test_arithmetic.py index fe5670e..bf02987 100644 --- a/quantities/tests/test_arithmetic.py +++ b/quantities/tests/test_arithmetic.py @@ -358,6 +358,12 @@ def test_in_place_subtraction(self): self.assertRaises(ValueError, op.isub, [1, 2, 3]*pq.m, pq.J) self.assertRaises(ValueError, op.isub, [1, 2, 3]*pq.m, 5*pq.J) + def test_division(self): + molar = pq.UnitQuantity('M', 1e3 * pq.mole/pq.m**3, u_symbol='M') + for subtr in [1, 1.0]: + q = 1*molar/(1000*pq.mole/pq.m**3) + self.assertQuantityEqual((q - subtr).simplified, 0) + def test_powering(self): # test raising a quantity to a power self.assertQuantityEqual((5.5 * pq.cm)**5, (5.5**5) * (pq.cm**5)) From b711cebcfcaa827f430c22c6383f6035f6a5cad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Wed, 1 Jun 2022 14:12:36 +0200 Subject: [PATCH 120/212] Add kwarg dtype to UncertainQuantity.refine too --- quantities/tests/test_arithmetic.py | 2 +- quantities/tests/test_uncertainty.py | 2 ++ quantities/uncertainquantity.py | 7 +++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/quantities/tests/test_arithmetic.py b/quantities/tests/test_arithmetic.py index bf02987..e9c55ad 100644 --- a/quantities/tests/test_arithmetic.py +++ b/quantities/tests/test_arithmetic.py @@ -359,7 +359,7 @@ def test_in_place_subtraction(self): self.assertRaises(ValueError, op.isub, [1, 2, 3]*pq.m, 5*pq.J) def test_division(self): - molar = pq.UnitQuantity('M', 1e3 * pq.mole/pq.m**3, u_symbol='M') + molar = pq.UnitQuantity('M', 1000 * pq.mole/pq.m**3, u_symbol='M') for subtr in [1, 1.0]: q = 1*molar/(1000*pq.mole/pq.m**3) self.assertQuantityEqual((q - subtr).simplified, 0) diff --git a/quantities/tests/test_uncertainty.py b/quantities/tests/test_uncertainty.py index 3e1bf00..85296d2 100644 --- a/quantities/tests/test_uncertainty.py +++ b/quantities/tests/test_uncertainty.py @@ -40,6 +40,8 @@ def test_rescale(self): self.assertTrue(in_meters.dtype == seventyish_km.dtype) seventyish_km_rescaled_idempotent = seventyish_km.rescale(pq.km) self.assertTrue(seventyish_km_rescaled_idempotent.dtype == np.float32) + self.assertQuantityEqual(seventyish_km + in_meters, 2*seventy_km) + def test_set_uncertainty(self): a = UncertainQuantity([1, 2], 'm', [.1, .2]) diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index 7023675..b7658a6 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -64,10 +64,10 @@ def relative_uncertainty(self): return self.uncertainty.magnitude/self.magnitude @with_doc(Quantity.rescale, use_header=False) - def rescale(self, units): + def rescale(self, units, dtype=None): cls = UncertainQuantity - ret = super(cls, self).rescale(units).view(cls) - ret.uncertainty = self.uncertainty.rescale(units) + ret = super(cls, self).rescale(units, dtype=dtype).view(cls) + ret.uncertainty = self.uncertainty.rescale(units, dtype=dtype) return ret def __array_finalize__(self, obj): @@ -282,4 +282,3 @@ def __reduce__(self): reconstruct, reconstruct_args, state = super().__reduce__() state = state + (self._uncertainty,) return reconstruct, reconstruct_args, state - From 5569c5630198fec72afa9059f5a351921636c811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Wed, 1 Jun 2022 15:04:15 +0200 Subject: [PATCH 121/212] Add exp2 as a supported ufunc --- quantities/dimensionality.py | 1 + quantities/tests/test_umath.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index cea68ca..caa4fba 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -367,6 +367,7 @@ def _d_dimensionless(q1, out=None): p_dict[np.log2] = _d_dimensionless p_dict[np.log1p] = _d_dimensionless p_dict[np.exp] = _d_dimensionless +p_dict[np.exp2] = _d_dimensionless p_dict[np.expm1] = _d_dimensionless p_dict[np.logaddexp] = _d_dimensionless p_dict[np.logaddexp2] = _d_dimensionless diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 5e3c616..964af98 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -163,6 +163,10 @@ def test_exp(self): self.assertQuantityEqual(np.exp(1*pq.dimensionless), np.e) self.assertRaises(ValueError, np.exp, 1*pq.m) + def test_exp2(self): + self.assertQuantityEqual(np.exp2(1*pq.dimensionless), 2.0) + self.assertRaises(ValueError, np.exp2, 1*pq.m) + def test_log(self): self.assertQuantityEqual(np.log(1*pq.dimensionless), 0) self.assertRaises(ValueError, np.log, 1*pq.m) From ff46a703f903668b26c178a2b2bed59d7fbb8f8a Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:55:34 +0200 Subject: [PATCH 122/212] Shared quantity types --- quantities/typing/__init__.py | 0 quantities/typing/quantities.pyi | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 quantities/typing/__init__.py create mode 100644 quantities/typing/quantities.pyi diff --git a/quantities/typing/__init__.py b/quantities/typing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quantities/typing/quantities.pyi b/quantities/typing/quantities.pyi new file mode 100644 index 0000000..1a82a8d --- /dev/null +++ b/quantities/typing/quantities.pyi @@ -0,0 +1,8 @@ +from typing import Union, Iterable + +from quantities import Quantity +from quantities.dimensionality import Dimensionality +import numpy.typing as npt + +DimensionalityDescriptor = Union[str, Quantity, Dimensionality] +QuantityData = Union[Quantity, npt.NDArray[Union[float,int]], Iterable[Union[float,int]], float, int] From d03b503c8446527de61ef15d41153dedf552dba6 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:55:57 +0200 Subject: [PATCH 123/212] Dimensionality type stub --- quantities/dimensionality.pyi | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 quantities/dimensionality.pyi diff --git a/quantities/dimensionality.pyi b/quantities/dimensionality.pyi new file mode 100644 index 0000000..6d3f459 --- /dev/null +++ b/quantities/dimensionality.pyi @@ -0,0 +1,84 @@ +class Dimensionality(dict): + @property + def ndims(self) -> int: + ... + + @property + def simplified(self) -> Dimensionality: + ... + + @property + def string(self) -> str: + ... + + @property + def unicode(self) -> str: + ... + + @property + def latex(self) -> str: + ... + + @property + def html(self) -> str: + ... + + def __hash__(self) -> int: + ... + + def __add__(self, other: Dimensionality) -> Dimensionality: + ... + + def __iadd__(self, other: Dimensionality) -> Dimensionality: + ... + + def __sub__(self, other: Dimensionality) -> Dimensionality: + ... + + def __isub__(self, other: Dimensionality) -> Dimensionality: + ... + + def __mul__(self, other: Dimensionality) -> Dimensionality: + ... + + def __imul__(self, other: Dimensionality) -> Dimensionality: + ... + + def __truediv__(self, other: Dimensionality) -> Dimensionality: + ... + + def __itruediv__(self, other: Dimensionality) -> Dimensionality: + ... + + def __pow__(self, other : Dimensionality) -> Dimensionality: + ... + + def __ipow__(self, other: Dimensionality) -> Dimensionality: + ... + + def __repr__(self) -> str: + ... + + def __str__(self) -> str: + ... + + def __eq__(self, other: object) -> bool: + ... + + def __ne__(self, other: object) -> bool: + ... + + def __gt__(self, other: Dimensionality) -> bool: + ... + + def __ge__(self, other: Dimensionality) -> bool: + ... + + def __lt__(self, other: Dimensionality) -> bool: + ... + + def __le__(self, other: Dimensionality)-> bool: + ... + + def copy(self) -> Dimensionality: + ... \ No newline at end of file From b89656b22844e841629a83c7b68eafe02b263fba Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:56:10 +0200 Subject: [PATCH 124/212] Quantity type stub --- quantities/quantity.pyi | 109 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 quantities/quantity.pyi diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi new file mode 100644 index 0000000..0ef6962 --- /dev/null +++ b/quantities/quantity.pyi @@ -0,0 +1,109 @@ +from typing import Optional + +from quantities.dimensionality import Dimensionality +from quantities.typing.quantities import DimensionalityDescriptor, QuantityData +import numpy.typing as npt + +def validate_unit_quantity(value: Quantity) -> Quantity: + ... + +def validate_dimensionality(value: DimensionalityDescriptor) -> Dimensionality: + ... + +def get_conversion_factor(from_u: Quantity, to_u: Quantity) -> float: + ... + + +class Quantity(npt.NDArray): + + def __new__(cls, data: QuantityData, units: DimensionalityDescriptor = '', + dtype: Optional[object] = None, copy: bool = True) -> Quantity: + ... + + @property + def dimensionality(self) -> Dimensionality: + ... + + @property + def _reference(self) : + ... + + @property + def magnitude(self) -> npt.NDArray: + ... + + @property + def real(self) -> Quantity: + ... + + @property + def imag(self) -> Quantity: + ... + + @property + def units(self) -> Quantity: + ... + + def rescale(self, units: Optional[DimensionalityDescriptor] = None) -> Quantity: + ... + + def rescale_preferred(self) -> Quantity: + ... + + def __add__(self, other) -> Quantity: + ... + + def __radd__(self, other) -> Quantity: + ... + + def __iadd__(self, other) -> Quantity: + ... + + def __sub__(self, other) -> Quantity: + ... + + def __rsub__(self, other) -> Quantity: + ... + + def __isub__(self, other) -> Quantity: + ... + + def __mod__(self, other) -> Quantity: + ... + + def __imod__(self, other) -> Quantity: + ... + + def __imul__(self, other) -> Quantity: + ... + + def __rmul__(self, other) -> Quantity: + ... + + def __itruediv__(self, other) -> Quantity: + ... + + def __rtruediv__(self, other) -> Quantity: + ... + + def __pow__(self, power) -> Quantity: + ... + + def __ipow__(self, other) -> Quantity: + ... + + def __round__(self, decimals: int = 0) -> Quantity: + ... + + def __repr__(self) -> str: + ... + + def __str__(self) -> str: + ... + + def __getitem__(self, item: int) -> Quantity: + ... + + def __setitem__(self, key: int, value: QuantityData) -> None: + ... + From 819535a631ca47bc71911be892b3954b392e532b Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:56:32 +0200 Subject: [PATCH 125/212] UnitQuantity type stub --- quantities/unitquantity.pyi | 96 +++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 quantities/unitquantity.pyi diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi new file mode 100644 index 0000000..5f25159 --- /dev/null +++ b/quantities/unitquantity.pyi @@ -0,0 +1,96 @@ +from typing import Optional, Union, List + +from quantities import Quantity +from quantities.dimensionality import Dimensionality + + +class UnitQuantity(Quantity): + + def __new__( + cls, name: str, definition: Optional[Union[Quantity, float, int]] = None, symbol: Optional[str] = None, + u_symbol: Optional[str] = None, + aliases: List[str] = [], doc=None + ) -> UnitQuantity: + ... + + def __init__( + self, name: str, definition: Optional[Union[Quantity, float, int]] = None, symbol: Optional[str] = None, + u_symbol: Optional[str] = None, + aliases: List[str] = [], doc=None + ) -> None: + ... + + def __hash__(self) -> int: + ... + + @property + def _reference(self) -> UnitQuantity: + ... + + @property + def _dimensionality(self) -> Dimensionality: + ... + + @property + def name(self) -> str: + ... + + @property + def symbol(self) -> str: + ... + + @property + def u_symbol(self) -> str: + ... + + @property + def units(self) -> UnitQuantity: + ... + + def __repr__(self) -> str: + ... + + def __str__(self) -> str: + ... + + def __add__(self, other) -> Quantity: + ... + + def __radd__(self, other) -> Quantity: + ... + + def __sub__(self, other) -> Quantity: + ... + + def __rsub__(self, other) -> Quantity: + ... + + def __mod__(self, other) -> Quantity: + ... + + def __rmod__(self, other) -> Quantity: + ... + + def __mul__(self, other) -> Quantity: + ... + + def __rmul__(self, other) -> Quantity: + ... + + def __truediv__(self, other) -> Quantity: + ... + + def __rtruediv__(self, other) -> Quantity: + ... + + def __pow__(self, other) -> Quantity: + ... + + def __rpow__(self, other) -> Quantity: + ... + + class Dimensionless(UnitQuantity): + + @property + def _dimensionality(self) -> Dimensionality: + ... \ No newline at end of file From b5c2847abd770148c6e79842a02e34fc2717fb87 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:56:44 +0200 Subject: [PATCH 126/212] Registry type stub --- quantities/registry.pyi | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 quantities/registry.pyi diff --git a/quantities/registry.pyi b/quantities/registry.pyi new file mode 100644 index 0000000..ad149cc --- /dev/null +++ b/quantities/registry.pyi @@ -0,0 +1,7 @@ +from quantities import UnitQuantity + + +class UnitRegistry: + + def __getitem__(self, item: str) -> UnitQuantity: + ... \ No newline at end of file From b9d5adad07ca85573000ed525587d0b018f88b73 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 12:01:07 +0200 Subject: [PATCH 127/212] EOF new lines --- quantities/dimensionality.pyi | 2 +- quantities/quantity.pyi | 1 - quantities/registry.pyi | 2 +- quantities/unitquantity.pyi | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/quantities/dimensionality.pyi b/quantities/dimensionality.pyi index 6d3f459..216b8cb 100644 --- a/quantities/dimensionality.pyi +++ b/quantities/dimensionality.pyi @@ -81,4 +81,4 @@ class Dimensionality(dict): ... def copy(self) -> Dimensionality: - ... \ No newline at end of file + ... diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 0ef6962..8bae3f6 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -106,4 +106,3 @@ class Quantity(npt.NDArray): def __setitem__(self, key: int, value: QuantityData) -> None: ... - diff --git a/quantities/registry.pyi b/quantities/registry.pyi index ad149cc..dd0d980 100644 --- a/quantities/registry.pyi +++ b/quantities/registry.pyi @@ -4,4 +4,4 @@ from quantities import UnitQuantity class UnitRegistry: def __getitem__(self, item: str) -> UnitQuantity: - ... \ No newline at end of file + ... diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index 5f25159..beb43dd 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -93,4 +93,4 @@ class UnitQuantity(Quantity): @property def _dimensionality(self) -> Dimensionality: - ... \ No newline at end of file + ... From 99448bded2bd6c5b7da53dff4eba0626da625106 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Thu, 7 Jul 2022 10:03:32 +0200 Subject: [PATCH 128/212] fix failure to handle units with floordiv fixes #205 --- quantities/dimensionality.py | 2 +- quantities/tests/test_arithmetic.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index cea68ca..d54f96f 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -238,6 +238,7 @@ def _d_divide(q1, q2, out=None): return q2.dimensionality**-1 p_dict[np.divide] = _d_divide p_dict[np.true_divide] = _d_divide +p_dict[np.floor_divide] = _d_divide def _d_check_uniform(q1, q2, out=None): try: @@ -273,7 +274,6 @@ def _d_check_uniform(q1, q2, out=None): p_dict[np.mod] = _d_check_uniform p_dict[np.fmod] = _d_check_uniform p_dict[np.remainder] = _d_check_uniform -p_dict[np.floor_divide] = _d_check_uniform p_dict[np.hypot] = _d_check_uniform p_dict[np.equal] = _d_check_uniform p_dict[np.not_equal] = _d_check_uniform diff --git a/quantities/tests/test_arithmetic.py b/quantities/tests/test_arithmetic.py index fe5670e..534702c 100644 --- a/quantities/tests/test_arithmetic.py +++ b/quantities/tests/test_arithmetic.py @@ -52,7 +52,7 @@ class iter_dtypes: def __init__(self): self._i = 1 - self._typeDict = np.typeDict.copy() + self._typeDict = np.sctypeDict.copy() self._typeDict[17] = int self._typeDict[18] = long self._typeDict[19] = float @@ -132,6 +132,20 @@ def test_mul(self): self.check_rmul(x, y) dtypes.pop(0) + def test_truediv(self): + q = Quantity([44, 40, 36, 32], units=pq.ms) + self.assertQuantityEqual( + q/(4 * pq.ms), + Quantity([11, 10, 9, 8], units=pq.dimensionless) + ) + + def test_floordiv(self): + q = Quantity([45, 43, 39, 32], units=pq.ms) + self.assertQuantityEqual( + q//(4 * pq.ms), + Quantity([11, 10, 9, 8], units=pq.dimensionless) + ) + def test_mixed_addition(self): self.assertQuantityEqual(1*pq.ft + 1*pq.m, 4.280839895 * pq.ft) self.assertQuantityEqual(1*pq.ft + pq.m, 4.280839895 * pq.ft) From 8b9ba5986414bdfc468d54b773f53b4d2d737f83 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Fri, 12 Aug 2022 16:35:28 +0200 Subject: [PATCH 129/212] fix wrong intendation --- quantities/unitquantity.pyi | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index beb43dd..e6fe7d5 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -89,8 +89,9 @@ class UnitQuantity(Quantity): def __rpow__(self, other) -> Quantity: ... - class Dimensionless(UnitQuantity): - @property - def _dimensionality(self) -> Dimensionality: - ... +class Dimensionless(UnitQuantity): + + @property + def _dimensionality(self) -> Dimensionality: + ... From d65873cc7adee90ec71dd86f15f42f0a0a1583d1 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Fri, 12 Aug 2022 16:43:41 +0200 Subject: [PATCH 130/212] [mypy] fix missing attribute in quantity --- quantities/quantity.pyi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 8bae3f6..88f01e5 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Any from quantities.dimensionality import Dimensionality from quantities.typing.quantities import DimensionalityDescriptor, QuantityData @@ -13,6 +13,8 @@ def validate_dimensionality(value: DimensionalityDescriptor) -> Dimensionality: def get_conversion_factor(from_u: Quantity, to_u: Quantity) -> float: ... +def scale_other_units(f: Any) -> None: + ... class Quantity(npt.NDArray): From 742b7b48a13cdb047fd58b62fce08d0c89f4f413 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Fri, 12 Aug 2022 16:45:56 +0200 Subject: [PATCH 131/212] [mypy] fix missing attribute in registry --- quantities/registry.pyi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quantities/registry.pyi b/quantities/registry.pyi index dd0d980..344585d 100644 --- a/quantities/registry.pyi +++ b/quantities/registry.pyi @@ -5,3 +5,5 @@ class UnitRegistry: def __getitem__(self, item: str) -> UnitQuantity: ... + +unit_registry: UnitRegistry \ No newline at end of file From fb295cda20a9570bc4397dac9e6a157a0d1e8b34 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Fri, 12 Aug 2022 17:09:13 +0200 Subject: [PATCH 132/212] [mypy] unitquantity add stubs for irreducibleunits --- quantities/unitquantity.pyi | 148 ++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index e6fe7d5..efb9a72 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -5,6 +5,9 @@ from quantities.dimensionality import Dimensionality class UnitQuantity(Quantity): + _primary_order: int + _secondary_order: int + _reference_quantity: Optional[Quantity] def __new__( cls, name: str, definition: Optional[Union[Quantity, float, int]] = None, symbol: Optional[str] = None, @@ -90,8 +93,153 @@ class UnitQuantity(Quantity): ... +class IrreducibleUnit(UnitQuantity): + _default_unit: Optional[UnitQuantity] + + @property + def simplified(self) -> Quantity: + ... + + @classmethod + def get_default_unit(cls) -> Optional[UnitQuantity]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitQuantity]): + ... + + +class UnitMass(IrreducibleUnit): + _default_unit: Optional[UnitMass] + + @classmethod + def get_default_unit(cls) -> Optional[UnitMass]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitMass]): + ... + + +class UnitLength(IrreducibleUnit): + _default_unit: Optional[UnitLength] + + @classmethod + def get_default_unit(cls) -> Optional[UnitLength]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitLength]): + ... + + +class UnitTime(IrreducibleUnit): + _default_unit: Optional[UnitTime] + + @classmethod + def get_default_unit(cls) -> Optional[UnitTime]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitTime]): + ... + + +class UnitCurrent(IrreducibleUnit): + _default_unit: Optional[UnitCurrent] + + +@classmethod + + +def get_default_unit(cls) -> Optional[UnitCurrent]: + ... + + +@classmethod +def set_default_unit(cls, unit: Union[str, UnitCurrent]): + ... + + +class UnitLuminousIntensity(IrreducibleUnit): + _default_unit: Optional[UnitLuminousIntensity] + + @classmethod + def get_default_unit(cls) -> Optional[UnitLuminousIntensity]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitLuminousIntensity]): + ... + + +class UnitSubstance(IrreducibleUnit): + _default_unit: Optional[UnitSubstance] + + @classmethod + def get_default_unit(cls) -> Optional[UnitSubstance]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitSubstance]): + ... + + +class UnitTemperature(IrreducibleUnit): + _default_unit: Optional[UnitTemperature] + + @classmethod + def get_default_unit(cls) -> Optional[UnitTemperature]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitTemperature]): + ... + + +class UnitInformation(IrreducibleUnit): + _default_unit: Optional[UnitInformation] + + @classmethod + def get_default_unit(cls) -> Optional[UnitInformation]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitInformation]): + ... + + +class UnitCurrency(IrreducibleUnit): + _default_unit: Optional[UnitCurrency] + + @classmethod + def get_default_unit(cls) -> Optional[UnitCurrency]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitCurrency]): + ... + + +class CompoundUnit(UnitQuantity): + ... + + class Dimensionless(UnitQuantity): @property def _dimensionality(self) -> Dimensionality: ... + + +class UnitConstant(UnitQuantity): + ... + + +def set_default_units(system: Optional[str], currency: Optional[Union[str, UnitCurrency]], + current: Optional[Union[str, UnitCurrent]], information: Optional[Union[str, UnitInformation]], + length: Optional[Union[str, UnitLength]], + luminous_intensity: Optional[Union[str, UnitLuminousIntensity]], + mass: Optional[Union[str, UnitMass]], substance: Optional[Union[str, UnitSubstance]], + temperature: Optional[Union[str, UnitTemperature]], time: Optional[Union[str, UnitTime]]): + ... From 4e1afa19cdcc7172d40e99013f4b5a67f8ba731d Mon Sep 17 00:00:00 2001 From: Johannes Pfabe <39130094+Hannnsen@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:12:54 +0100 Subject: [PATCH 133/212] Added femtofarad (fF) to the units. (#210) * Added femtofarad (fF) to the units. * Update electromagnetism.py --- quantities/units/electromagnetism.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/quantities/units/electromagnetism.py b/quantities/units/electromagnetism.py index 8bde6bc..c22ac87 100644 --- a/quantities/units/electromagnetism.py +++ b/quantities/units/electromagnetism.py @@ -137,6 +137,11 @@ nF/1000, symbol='pF' ) +fF = UnitQuantity( + 'femtofarad', + pF/1000, + symbol='fF' +) ohm = Ohm = UnitQuantity( 'ohm', V/A, From 1b3dc2622cac99f6d914ddcf6e7ec38d96038749 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Tue, 6 Dec 2022 14:06:09 +0100 Subject: [PATCH 134/212] [mypy] ignore types --- quantities/dimensionality.pyi | 3 +- quantities/quantity.pyi | 41 +++++++----- quantities/uncertainquantity.py | 2 +- quantities/unitquantity.py | 2 + quantities/unitquantity.pyi | 111 ++++++-------------------------- 5 files changed, 48 insertions(+), 111 deletions(-) diff --git a/quantities/dimensionality.pyi b/quantities/dimensionality.pyi index 216b8cb..aadee6d 100644 --- a/quantities/dimensionality.pyi +++ b/quantities/dimensionality.pyi @@ -23,7 +23,8 @@ class Dimensionality(dict): def html(self) -> str: ... - def __hash__(self) -> int: + + def __hash__(self) -> int: # type: ignore ... def __add__(self, other: Dimensionality) -> Dimensionality: diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 88f01e5..8841a87 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -4,22 +4,27 @@ from quantities.dimensionality import Dimensionality from quantities.typing.quantities import DimensionalityDescriptor, QuantityData import numpy.typing as npt -def validate_unit_quantity(value: Quantity) -> Quantity: + +def validate_unit_quantity(value: Quantity) -> Quantity: #type: ignore ... + def validate_dimensionality(value: DimensionalityDescriptor) -> Dimensionality: ... + def get_conversion_factor(from_u: Quantity, to_u: Quantity) -> float: ... + def scale_other_units(f: Any) -> None: ... + class Quantity(npt.NDArray): def __new__(cls, data: QuantityData, units: DimensionalityDescriptor = '', - dtype: Optional[object] = None, copy: bool = True) -> Quantity: + dtype: Optional[object] = None, copy: bool = True) -> Quantity: #type: ignore ... @property @@ -27,19 +32,19 @@ class Quantity(npt.NDArray): ... @property - def _reference(self) : + def _reference(self): ... @property def magnitude(self) -> npt.NDArray: ... - @property - def real(self) -> Quantity: + @property #type: ignore + def real(self) -> Quantity: #type: ignore ... - @property - def imag(self) -> Quantity: + @property #type: ignore + def imag(self) -> Quantity: #type: ignore ... @property @@ -61,13 +66,16 @@ class Quantity(npt.NDArray): def __iadd__(self, other) -> Quantity: ... - def __sub__(self, other) -> Quantity: + + def __sub__(self, other) -> Quantity: #type: ignore ... - def __rsub__(self, other) -> Quantity: + + def __rsub__(self, other) -> Quantity: #type: ignore ... - def __isub__(self, other) -> Quantity: + + def __isub__(self, other) -> Quantity: #type: ignore ... def __mod__(self, other) -> Quantity: @@ -76,16 +84,17 @@ class Quantity(npt.NDArray): def __imod__(self, other) -> Quantity: ... - def __imul__(self, other) -> Quantity: - ... + # def __imul__(self, other): + # ... def __rmul__(self, other) -> Quantity: ... - def __itruediv__(self, other) -> Quantity: - ... + # def __itruediv__(self, other) : + # ... + - def __rtruediv__(self, other) -> Quantity: + def __rtruediv__(self, other) -> Quantity: #type: ignore ... def __pow__(self, power) -> Quantity: @@ -103,7 +112,7 @@ class Quantity(npt.NDArray): def __str__(self) -> str: ... - def __getitem__(self, item: int) -> Quantity: + def __getitem__(self, item: Any) -> Quantity: ... def __setitem__(self, key: int, value: QuantityData) -> None: diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index 7023675..5d92e8b 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -27,7 +27,7 @@ def __new__(cls, data, units='', uncertainty=None, dtype='d', copy=True): return ret - @Quantity.units.setter + @Quantity.units.setter #type: ignore def units(self, units): super()._set_units(units) self.uncertainty.units = self._dimensionality diff --git a/quantities/unitquantity.py b/quantities/unitquantity.py index 47d7529..d5487fc 100644 --- a/quantities/unitquantity.py +++ b/quantities/unitquantity.py @@ -498,3 +498,5 @@ def set_default_units( UnitSubstance.set_default_unit(substance) UnitTemperature.set_default_unit(temperature) UnitTime.set_default_unit(time) + + diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index efb9a72..db4779e 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -1,4 +1,4 @@ -from typing import Optional, Union, List +from typing import Optional, Union, List, Any, overload from quantities import Quantity from quantities.dimensionality import Dimensionality @@ -62,10 +62,11 @@ class UnitQuantity(Quantity): def __radd__(self, other) -> Quantity: ... - def __sub__(self, other) -> Quantity: + def __sub__(self, other) -> Any: ... - def __rsub__(self, other) -> Quantity: + + def __rsub__(self, other) -> Any: ... def __mod__(self, other) -> Quantity: @@ -80,10 +81,10 @@ class UnitQuantity(Quantity): def __rmul__(self, other) -> Quantity: ... - def __truediv__(self, other) -> Quantity: + def __truediv__(self, other) -> Any: ... - def __rtruediv__(self, other) -> Quantity: + def __rtruediv__(self, other) -> Any: ... def __pow__(self, other) -> Quantity: @@ -94,131 +95,54 @@ class UnitQuantity(Quantity): class IrreducibleUnit(UnitQuantity): - _default_unit: Optional[UnitQuantity] + _default_unit: Optional[Quantity] @property def simplified(self) -> Quantity: ... @classmethod - def get_default_unit(cls) -> Optional[UnitQuantity]: + def get_default_unit(cls) -> Optional[Quantity]: ... @classmethod - def set_default_unit(cls, unit: Union[str, UnitQuantity]): + def set_default_unit(cls, unit: Union[str, Quantity]): ... class UnitMass(IrreducibleUnit): - _default_unit: Optional[UnitMass] - - @classmethod - def get_default_unit(cls) -> Optional[UnitMass]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitMass]): - ... + ... class UnitLength(IrreducibleUnit): - _default_unit: Optional[UnitLength] - - @classmethod - def get_default_unit(cls) -> Optional[UnitLength]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitLength]): - ... + ... class UnitTime(IrreducibleUnit): - _default_unit: Optional[UnitTime] - - @classmethod - def get_default_unit(cls) -> Optional[UnitTime]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitTime]): - ... - - -class UnitCurrent(IrreducibleUnit): - _default_unit: Optional[UnitCurrent] - - -@classmethod - - -def get_default_unit(cls) -> Optional[UnitCurrent]: ... -@classmethod -def set_default_unit(cls, unit: Union[str, UnitCurrent]): +class UnitCurrent(IrreducibleUnit): ... - class UnitLuminousIntensity(IrreducibleUnit): - _default_unit: Optional[UnitLuminousIntensity] - - @classmethod - def get_default_unit(cls) -> Optional[UnitLuminousIntensity]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitLuminousIntensity]): - ... + ... class UnitSubstance(IrreducibleUnit): - _default_unit: Optional[UnitSubstance] - - @classmethod - def get_default_unit(cls) -> Optional[UnitSubstance]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitSubstance]): - ... + ... class UnitTemperature(IrreducibleUnit): - _default_unit: Optional[UnitTemperature] - - @classmethod - def get_default_unit(cls) -> Optional[UnitTemperature]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitTemperature]): - ... + ... class UnitInformation(IrreducibleUnit): - _default_unit: Optional[UnitInformation] - - @classmethod - def get_default_unit(cls) -> Optional[UnitInformation]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitInformation]): - ... + ... class UnitCurrency(IrreducibleUnit): - _default_unit: Optional[UnitCurrency] - - @classmethod - def get_default_unit(cls) -> Optional[UnitCurrency]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitCurrency]): - ... + ... class CompoundUnit(UnitQuantity): @@ -231,6 +155,7 @@ class Dimensionless(UnitQuantity): def _dimensionality(self) -> Dimensionality: ... +dimensionless: Dimensionless class UnitConstant(UnitQuantity): ... From 53fde7628c3d684531436267b87b070b85172cb3 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Tue, 6 Dec 2022 14:06:37 +0100 Subject: [PATCH 135/212] generated annotations for units --- quantities/units/acceleration.pyi | 13 +++ quantities/units/angle.pyi | 41 ++++++++++ quantities/units/area.pyi | 18 +++++ quantities/units/compound.pyi | 3 + quantities/units/concentration.pyi | 9 +++ quantities/units/dimensionless.pyi | 7 ++ quantities/units/electromagnetism.pyi | 110 ++++++++++++++++++++++++++ quantities/units/energy.pyi | 40 ++++++++++ quantities/units/force.pyi | 22 ++++++ quantities/units/frequency.pyi | 14 ++++ quantities/units/heat.pyi | 6 ++ quantities/units/information.pyi | 58 ++++++++++++++ quantities/units/length.pyi | 71 +++++++++++++++++ quantities/units/mass.pyi | 52 ++++++++++++ quantities/units/power.pyi | 21 +++++ quantities/units/prefixes.pyi | 0 quantities/units/pressure.pyi | 58 ++++++++++++++ quantities/units/radiation.pyi | 16 ++++ quantities/units/substance.pyi | 6 ++ quantities/units/temperature.pyi | 15 ++++ quantities/units/time.pyi | 52 ++++++++++++ quantities/units/velocity.pyi | 8 ++ quantities/units/viscosity.pyi | 9 +++ quantities/units/volume.pyi | 78 ++++++++++++++++++ 24 files changed, 727 insertions(+) create mode 100644 quantities/units/acceleration.pyi create mode 100644 quantities/units/angle.pyi create mode 100644 quantities/units/area.pyi create mode 100644 quantities/units/compound.pyi create mode 100644 quantities/units/concentration.pyi create mode 100644 quantities/units/dimensionless.pyi create mode 100644 quantities/units/electromagnetism.pyi create mode 100644 quantities/units/energy.pyi create mode 100644 quantities/units/force.pyi create mode 100644 quantities/units/frequency.pyi create mode 100644 quantities/units/heat.pyi create mode 100644 quantities/units/information.pyi create mode 100644 quantities/units/length.pyi create mode 100644 quantities/units/mass.pyi create mode 100644 quantities/units/power.pyi create mode 100644 quantities/units/prefixes.pyi create mode 100644 quantities/units/pressure.pyi create mode 100644 quantities/units/radiation.pyi create mode 100644 quantities/units/substance.pyi create mode 100644 quantities/units/temperature.pyi create mode 100644 quantities/units/time.pyi create mode 100644 quantities/units/velocity.pyi create mode 100644 quantities/units/viscosity.pyi create mode 100644 quantities/units/volume.pyi diff --git a/quantities/units/acceleration.pyi b/quantities/units/acceleration.pyi new file mode 100644 index 0000000..20bdbb4 --- /dev/null +++ b/quantities/units/acceleration.pyi @@ -0,0 +1,13 @@ +from ..unitquantity import UnitQuantity + +standard_free_fall: UnitQuantity +gp: UnitQuantity +dynamic: UnitQuantity +geopotential: UnitQuantity +g_0: UnitQuantity +g_n: UnitQuantity +gravity: UnitQuantity +standard_gravity: UnitQuantity +gee: UnitQuantity +force: UnitQuantity +free_fall: UnitQuantity \ No newline at end of file diff --git a/quantities/units/angle.pyi b/quantities/units/angle.pyi new file mode 100644 index 0000000..de3f8c0 --- /dev/null +++ b/quantities/units/angle.pyi @@ -0,0 +1,41 @@ +from ..unitquantity import UnitQuantity + +rad: UnitQuantity +radian: UnitQuantity +radians: UnitQuantity +mrad: UnitQuantity +milliradian: UnitQuantity +urad: UnitQuantity +microradian: UnitQuantity +turn: UnitQuantity +revolution: UnitQuantity +cycle: UnitQuantity +turns: UnitQuantity +circle: UnitQuantity +circles: UnitQuantity +deg: UnitQuantity +degree: UnitQuantity +degrees: UnitQuantity +arcdeg: UnitQuantity +arcdegree: UnitQuantity +angular_degree: UnitQuantity +arcminute: UnitQuantity +arcmin: UnitQuantity +arc_minute: UnitQuantity +angular_minute: UnitQuantity +arcsecond: UnitQuantity +arcsec: UnitQuantity +arc_second: UnitQuantity +angular_second: UnitQuantity +grad: UnitQuantity +grade: UnitQuantity +degrees_north: UnitQuantity +degrees_N: UnitQuantity +degrees_east: UnitQuantity +degrees_E: UnitQuantity +degrees_west: UnitQuantity +degrees_W: UnitQuantity +degrees_true: UnitQuantity +degrees_T: UnitQuantity +sr: UnitQuantity +steradian: UnitQuantity diff --git a/quantities/units/area.pyi b/quantities/units/area.pyi new file mode 100644 index 0000000..34da5b7 --- /dev/null +++ b/quantities/units/area.pyi @@ -0,0 +1,18 @@ +from ..unitquantity import UnitQuantity + + +are: UnitQuantity +ares: UnitQuantity +b: UnitQuantity +barn: UnitQuantity +cmil: UnitQuantity +circular_mil: UnitQuantity +D: UnitQuantity +darcy: UnitQuantity +mD: UnitQuantity +millidarcy: UnitQuantity +ha: UnitQuantity +hectare: UnitQuantity +acre: UnitQuantity +international_acre: UnitQuantity +US_survey_acre: UnitQuantity diff --git a/quantities/units/compound.pyi b/quantities/units/compound.pyi new file mode 100644 index 0000000..4a8e0e6 --- /dev/null +++ b/quantities/units/compound.pyi @@ -0,0 +1,3 @@ +from ..unitquantity import CompoundUnit + +pc_per_cc: CompoundUnit diff --git a/quantities/units/concentration.pyi b/quantities/units/concentration.pyi new file mode 100644 index 0000000..f82f37f --- /dev/null +++ b/quantities/units/concentration.pyi @@ -0,0 +1,9 @@ +from ..unitquantity import UnitQuantity + + +M: UnitQuantity +molar: UnitQuantity +mM: UnitQuantity +millimolar: UnitQuantity +uM: UnitQuantity +micromolar: UnitQuantity diff --git a/quantities/units/dimensionless.pyi b/quantities/units/dimensionless.pyi new file mode 100644 index 0000000..a18340f --- /dev/null +++ b/quantities/units/dimensionless.pyi @@ -0,0 +1,7 @@ +from ..unitquantity import UnitQuantity + + +percent: UnitQuantity +count: UnitQuantity +counts: UnitQuantity +lsb: UnitQuantity diff --git a/quantities/units/electromagnetism.pyi b/quantities/units/electromagnetism.pyi new file mode 100644 index 0000000..bdc6ddb --- /dev/null +++ b/quantities/units/electromagnetism.pyi @@ -0,0 +1,110 @@ +from ..unitquantity import UnitCurrent, UnitLuminousIntensity, UnitQuantity + +A: UnitCurrent +amp: UnitCurrent +amps: UnitCurrent +ampere: UnitCurrent +amperes: UnitCurrent +mA: UnitCurrent +milliamp: UnitCurrent +milliampere: UnitCurrent +uA: UnitCurrent +microampere: UnitCurrent +nA: UnitCurrent +nanoamp: UnitCurrent +nanoampere: UnitCurrent +pA: UnitCurrent +picoamp: UnitCurrent +picoampere: UnitCurrent +aA: UnitCurrent +abampere: UnitCurrent +biot: UnitCurrent +esu: UnitQuantity +statcoulomb: UnitQuantity +statC: UnitQuantity +franklin: UnitQuantity +Fr: UnitQuantity +esu_per_second: UnitCurrent +statampere: UnitCurrent +ampere_turn: UnitQuantity +Gi: UnitQuantity +gilbert: UnitQuantity +C: UnitQuantity +coulomb: UnitQuantity +mC: UnitQuantity +millicoulomb: UnitQuantity +uC: UnitQuantity +microcoulomb: UnitQuantity +V: UnitQuantity +volt: UnitQuantity +kV: UnitQuantity +kilovolt: UnitQuantity +mV: UnitQuantity +millivolt: UnitQuantity +uV: UnitQuantity +microvolt: UnitQuantity +F: UnitQuantity +farad: UnitQuantity +mF: UnitQuantity +uF: UnitQuantity +nF: UnitQuantity +pF: UnitQuantity +ohm: UnitQuantity +Ohm: UnitQuantity +kOhm: UnitQuantity +MOhm: UnitQuantity +S: UnitQuantity +siemens: UnitQuantity +mS: UnitQuantity +millisiemens: UnitQuantity +uS: UnitQuantity +microsiemens: UnitQuantity +nS: UnitQuantity +nanosiemens: UnitQuantity +pS: UnitQuantity +picosiemens: UnitQuantity +Wb: UnitQuantity +weber: UnitQuantity +T: UnitQuantity +tesla: UnitQuantity +H: UnitQuantity +henry: UnitQuantity +abfarad: UnitQuantity +abhenry: UnitQuantity +abmho: UnitQuantity +abohm: UnitQuantity +abvolt: UnitQuantity +e: UnitQuantity +elementary_charge: UnitQuantity +chemical_faraday: UnitQuantity +physical_faraday: UnitQuantity +faraday: UnitQuantity +C12_faraday: UnitQuantity +gamma: UnitQuantity +gauss: UnitQuantity +maxwell: UnitQuantity +Oe: UnitQuantity +oersted: UnitQuantity +statfarad: UnitQuantity +statF: UnitQuantity +stF: UnitQuantity +stathenry: UnitQuantity +statH: UnitQuantity +stH: UnitQuantity +statmho: UnitQuantity +statS: UnitQuantity +stS: UnitQuantity +statohm: UnitQuantity +statvolt: UnitQuantity +statV: UnitQuantity +stV: UnitQuantity +unit_pole: UnitQuantity +vacuum_permeability: UnitQuantity +mu_0: UnitQuantity +magnetic_constant: UnitQuantity +vacuum_permittivity: UnitQuantity +epsilon_0: UnitQuantity +electric_constant: UnitQuantity +cd: UnitLuminousIntensity +candle: UnitLuminousIntensity +candela: UnitLuminousIntensity diff --git a/quantities/units/energy.pyi b/quantities/units/energy.pyi new file mode 100644 index 0000000..e811e97 --- /dev/null +++ b/quantities/units/energy.pyi @@ -0,0 +1,40 @@ +from ..unitquantity import UnitQuantity + +J: UnitQuantity +joule: UnitQuantity +erg: UnitQuantity +btu: UnitQuantity +Btu: UnitQuantity +BTU: UnitQuantity +british_thermal_unit: UnitQuantity +eV: UnitQuantity +electron_volt: UnitQuantity +meV: UnitQuantity +keV: UnitQuantity +MeV: UnitQuantity +bev: UnitQuantity +GeV: UnitQuantity +thm: UnitQuantity +therm: UnitQuantity +EC_therm: UnitQuantity +cal: UnitQuantity +calorie: UnitQuantity +thermochemical_calorie: UnitQuantity +international_steam_table_calorie: UnitQuantity +ton_TNT: UnitQuantity +US_therm: UnitQuantity +Wh: UnitQuantity +watthour: UnitQuantity +watt_hour: UnitQuantity +kWh: UnitQuantity +kilowatthour: UnitQuantity +kilowatt_hour: UnitQuantity +MWh: UnitQuantity +megawatthour: UnitQuantity +megawatt_hour: UnitQuantity +GWh: UnitQuantity +gigawatthour: UnitQuantity +gigawatt_hour: UnitQuantity +E_h: UnitQuantity +hartree: UnitQuantity +hartree_energy: UnitQuantity diff --git a/quantities/units/force.pyi b/quantities/units/force.pyi new file mode 100644 index 0000000..46062b7 --- /dev/null +++ b/quantities/units/force.pyi @@ -0,0 +1,22 @@ +from ..unitquantity import UnitQuantity + +N: UnitQuantity +newton: UnitQuantity +dyne: UnitQuantity +pond: UnitQuantity +kgf: UnitQuantity +force_kilogram: UnitQuantity +kilogram_force: UnitQuantity +ozf: UnitQuantity +force_ounce: UnitQuantity +ounce_force: UnitQuantity +lbf: UnitQuantity +force_pound: UnitQuantity +pound_force: UnitQuantity +poundal: UnitQuantity +gf: UnitQuantity +gram_force: UnitQuantity +force_gram: UnitQuantity +force_ton: UnitQuantity +ton_force: UnitQuantity +kip: UnitQuantity diff --git a/quantities/units/frequency.pyi b/quantities/units/frequency.pyi new file mode 100644 index 0000000..3f9bb67 --- /dev/null +++ b/quantities/units/frequency.pyi @@ -0,0 +1,14 @@ +from ..unitquantity import UnitQuantity + +Hz: UnitQuantity +hertz: UnitQuantity +rps: UnitQuantity +kHz: UnitQuantity +kilohertz: UnitQuantity +MHz: UnitQuantity +megahertz: UnitQuantity +GHz: UnitQuantity +gigahertz: UnitQuantity +rpm: UnitQuantity +revolutions_per_minute: UnitQuantity +cps: UnitQuantity diff --git a/quantities/units/heat.pyi b/quantities/units/heat.pyi new file mode 100644 index 0000000..9ed86a7 --- /dev/null +++ b/quantities/units/heat.pyi @@ -0,0 +1,6 @@ +from ..unitquantity import UnitQuantity + +RSI: UnitQuantity +clo: UnitQuantity +clos: UnitQuantity +R_value: UnitQuantity diff --git a/quantities/units/information.pyi b/quantities/units/information.pyi new file mode 100644 index 0000000..423243f --- /dev/null +++ b/quantities/units/information.pyi @@ -0,0 +1,58 @@ +from ..unitquantity import UnitQuantity, UnitInformation + +bit: UnitInformation +B: UnitInformation +byte: UnitInformation +o: UnitInformation +octet: UnitInformation +kB: UnitInformation +kilobyte: UnitInformation +ko: UnitInformation +MB: UnitInformation +megabyte: UnitInformation +Mo: UnitInformation +GB: UnitInformation +gigabyte: UnitInformation +Go: UnitInformation +TB: UnitInformation +terabyte: UnitInformation +To: UnitInformation +PB: UnitInformation +petabyte: UnitInformation +Po: UnitInformation +EB: UnitInformation +exabyte: UnitInformation +Eo: UnitInformation +ZB: UnitInformation +zettabyte: UnitInformation +Zo: UnitInformation +YB: UnitInformation +yottabyte: UnitInformation +Yo: UnitInformation +Bd: UnitQuantity +baud: UnitQuantity +bps: UnitQuantity +KiB: UnitInformation +kibibyte: UnitInformation +Kio: UnitInformation +MiB: UnitInformation +mebibyte: UnitInformation +Mio: UnitInformation +GiB: UnitInformation +gibibyte: UnitInformation +Gio: UnitInformation +TiB: UnitInformation +tebibyte: UnitInformation +Tio: UnitInformation +PiB: UnitInformation +pebibyte: UnitInformation +Pio: UnitInformation +EiB: UnitInformation +exbibyte: UnitInformation +Eio: UnitInformation +ZiB: UnitInformation +zebibyte: UnitInformation +Zio: UnitInformation +YiB: UnitInformation +yobibyte: UnitInformation +Yio: UnitInformation diff --git a/quantities/units/length.pyi b/quantities/units/length.pyi new file mode 100644 index 0000000..94a4f00 --- /dev/null +++ b/quantities/units/length.pyi @@ -0,0 +1,71 @@ +from ..unitquantity import UnitLength, UnitQuantity + +m: UnitLength +meter: UnitLength +metre: UnitLength +km: UnitLength +kilometer: UnitLength +kilometre: UnitLength +dm: UnitLength +decimeter: UnitLength +decimetre: UnitLength +cm: UnitLength +centimeter: UnitLength +centimetre: UnitLength +mm: UnitLength +millimeter: UnitLength +millimetre: UnitLength +um: UnitLength +micrometer: UnitLength +micrometre: UnitLength +micron: UnitLength +nm: UnitLength +nanometer: UnitLength +nanometre: UnitLength +pm: UnitLength +picometer: UnitLength +picometre: UnitLength +angstrom: UnitLength +fm: UnitLength +femtometer: UnitLength +femtometre: UnitLength +fermi: UnitLength +inch: UnitLength +international_inch: UnitLength +ft: UnitLength +foot: UnitLength +international_foot: UnitLength +mi: UnitLength +mile: UnitLength +international_mile: UnitLength +yd: UnitLength +yard: UnitLength +international_yard: UnitLength +mil: UnitLength +thou: UnitLength +pc: UnitLength +parsec: UnitLength +ly: UnitLength +light_year: UnitLength +au: UnitLength +astronomical_unit: UnitLength +nmi: UnitLength +nautical_mile: UnitLength +pt: UnitLength +printers_point: UnitLength +point: UnitLength +pica: UnitLength +US_survey_foot: UnitLength +US_survey_yard: UnitLength +US_survey_mile: UnitLength +US_statute_mile: UnitLength +rod: UnitLength +pole: UnitLength +perch: UnitLength +furlong: UnitLength +fathom: UnitLength +chain: UnitLength +barleycorn: UnitLength +arpentlin: UnitLength +kayser: UnitQuantity +wavenumber: UnitQuantity diff --git a/quantities/units/mass.pyi b/quantities/units/mass.pyi new file mode 100644 index 0000000..6917562 --- /dev/null +++ b/quantities/units/mass.pyi @@ -0,0 +1,52 @@ +from ..unitquantity import UnitQuantity, UnitMass + +kg: UnitMass +kilogram: UnitMass +g: UnitMass +gram: UnitMass +mg: UnitMass +milligram: UnitMass +oz: UnitMass +ounce: UnitMass +avoirdupois_ounce: UnitMass +lb: UnitMass +pound: UnitMass +avoirdupois_pound: UnitMass +st: UnitMass +stone: UnitMass +carat: UnitMass +gr: UnitMass +grain: UnitMass +long_hundredweight: UnitMass +short_hundredweight: UnitMass +t: UnitMass +metric_ton: UnitMass +tonne: UnitMass +dwt: UnitMass +pennyweight: UnitMass +slug: UnitMass +slugs: UnitMass +toz: UnitMass +troy_ounce: UnitMass +apounce: UnitMass +apothecary_ounce: UnitMass +troy_pound: UnitMass +appound: UnitMass +apothecary_pound: UnitMass +u: UnitMass +amu: UnitMass +atomic_mass_unit: UnitMass +dalton: UnitMass +Da: UnitMass +scruple: UnitMass +dr: UnitMass +dram: UnitMass +drachm: UnitMass +apdram: UnitMass +bag: UnitMass +ton: UnitMass +short_ton: UnitMass +long_ton: UnitMass +denier: UnitQuantity +tex: UnitQuantity +dtex: UnitQuantity diff --git a/quantities/units/power.pyi b/quantities/units/power.pyi new file mode 100644 index 0000000..b4d6b31 --- /dev/null +++ b/quantities/units/power.pyi @@ -0,0 +1,21 @@ +from ..unitquantity import UnitQuantity + +W: UnitQuantity +watt: UnitQuantity +volt_ampere: UnitQuantity +mW: UnitQuantity +milliwatt: UnitQuantity +kW: UnitQuantity +kilowatt: UnitQuantity +MW: UnitQuantity +megawatt: UnitQuantity +hp: UnitQuantity +horsepower: UnitQuantity +UK_horsepower: UnitQuantity +British_horsepower: UnitQuantity +boiler_horsepower: UnitQuantity +metric_horsepower: UnitQuantity +electric_horsepower: UnitQuantity +water_horsepower: UnitQuantity +refrigeration_ton: UnitQuantity +ton_of_refrigeration: UnitQuantity diff --git a/quantities/units/prefixes.pyi b/quantities/units/prefixes.pyi new file mode 100644 index 0000000..e69de29 diff --git a/quantities/units/pressure.pyi b/quantities/units/pressure.pyi new file mode 100644 index 0000000..5eadcde --- /dev/null +++ b/quantities/units/pressure.pyi @@ -0,0 +1,58 @@ +from ..unitquantity import UnitQuantity + +Hg: UnitQuantity +mercury: UnitQuantity +conventional_mercury: UnitQuantity +Pa: UnitQuantity +pascal: UnitQuantity +hPa: UnitQuantity +hectopascal: UnitQuantity +kPa: UnitQuantity +kilopascal: UnitQuantity +MPa: UnitQuantity +megapascal: UnitQuantity +GPa: UnitQuantity +gigapascal: UnitQuantity +bar: UnitQuantity +mbar: UnitQuantity +millibar: UnitQuantity +kbar: UnitQuantity +kilobar: UnitQuantity +Mbar: UnitQuantity +megabar: UnitQuantity +Gbar: UnitQuantity +gigabar: UnitQuantity +atm: UnitQuantity +atmosphere: UnitQuantity +standard_atmosphere: UnitQuantity +at: UnitQuantity +technical_atmosphere: UnitQuantity +torr: UnitQuantity +psi: UnitQuantity +pound_force_per_square_inch: UnitQuantity +ksi: UnitQuantity +kip_per_square_inch: UnitQuantity +barye: UnitQuantity +barie: UnitQuantity +barad: UnitQuantity +barad: UnitQuantity +barrie: UnitQuantity +baryd: UnitQuantity +mmHg: UnitQuantity +mm_Hg: UnitQuantity +millimeter_Hg: UnitQuantity +millimeter_Hg_0C: UnitQuantity +cmHg: UnitQuantity +cm_Hg: UnitQuantity +centimeter_Hg: UnitQuantity +inHg: UnitQuantity +in_Hg: UnitQuantity +inch_Hg: UnitQuantity +inch_Hg_32F: UnitQuantity +inch_Hg_60F: UnitQuantity +inch_H2O_39F: UnitQuantity +inch_H2O_60F: UnitQuantity +footH2O: UnitQuantity +cmH2O: UnitQuantity +foot_H2O: UnitQuantity +ftH2O: UnitQuantity diff --git a/quantities/units/radiation.pyi b/quantities/units/radiation.pyi new file mode 100644 index 0000000..21b271f --- /dev/null +++ b/quantities/units/radiation.pyi @@ -0,0 +1,16 @@ +from ..unitquantity import UnitQuantity + +Bq: UnitQuantity +becquerel: UnitQuantity +Ci: UnitQuantity +curie: UnitQuantity +rd: UnitQuantity +rutherford: UnitQuantity +Gy: UnitQuantity +gray: UnitQuantity +Sv: UnitQuantity +sievert: UnitQuantity +rem: UnitQuantity +rads: UnitQuantity +R: UnitQuantity +roentgen: UnitQuantity diff --git a/quantities/units/substance.pyi b/quantities/units/substance.pyi new file mode 100644 index 0000000..5c06c12 --- /dev/null +++ b/quantities/units/substance.pyi @@ -0,0 +1,6 @@ +from ..unitquantity import UnitSubstance + +mol: UnitSubstance +mole: UnitSubstance +mmol: UnitSubstance +umol: UnitSubstance diff --git a/quantities/units/temperature.pyi b/quantities/units/temperature.pyi new file mode 100644 index 0000000..2a79414 --- /dev/null +++ b/quantities/units/temperature.pyi @@ -0,0 +1,15 @@ +from ..unitquantity import UnitTemperature + +K: UnitTemperature +degK: UnitTemperature +kelvin: UnitTemperature +Kelvin: UnitTemperature +degR: UnitTemperature +rankine: UnitTemperature +Rankine: UnitTemperature +degC: UnitTemperature +celsius: UnitTemperature +Celsius: UnitTemperature +degF: UnitTemperature +fahrenheit: UnitTemperature +Fahrenheit: UnitTemperature diff --git a/quantities/units/time.pyi b/quantities/units/time.pyi new file mode 100644 index 0000000..d581a7a --- /dev/null +++ b/quantities/units/time.pyi @@ -0,0 +1,52 @@ +from ..unitquantity import UnitQuantity, UnitTime + +s: UnitTime +sec: UnitTime +second: UnitTime +ks: UnitTime +kilosecond: UnitTime +Ms: UnitTime +megasecond: UnitTime +ms: UnitTime +millisecond: UnitTime +us: UnitTime +microsecond: UnitTime +ns: UnitTime +nanosecond: UnitTime +ps: UnitTime +picosecond: UnitTime +fs: UnitTime +femtosecond: UnitTime +attosecond: UnitTime +min: UnitTime +minute: UnitTime +h: UnitTime +hr: UnitTime +hour: UnitTime +d: UnitTime +day: UnitTime +week: UnitTime +fortnight: UnitTime +yr: UnitTime +year: UnitTime +tropical_year: UnitTime +a: UnitTime +month: UnitTime +shake: UnitTime +sidereal_day: UnitTime +sidereal_hour: UnitTime +sidereal_minute: UnitTime +sidereal_second: UnitTime +sidereal_year: UnitTime +sidereal_month: UnitTime +tropical_month: UnitTime +synodic_month: UnitTime +lunar_month: UnitTime +common_year: UnitTime +leap_year: UnitTime +Julian_year: UnitTime +Gregorian_year: UnitTime +millenium: UnitTime +eon: UnitTime +work_year: UnitQuantity +work_month: UnitQuantity diff --git a/quantities/units/velocity.pyi b/quantities/units/velocity.pyi new file mode 100644 index 0000000..3d65bcc --- /dev/null +++ b/quantities/units/velocity.pyi @@ -0,0 +1,8 @@ +from ..unitquantity import UnitQuantity + +c: UnitQuantity +speed_of_light: UnitQuantity +kt: UnitQuantity +knot: UnitQuantity +knot_international: UnitQuantity +international_knot: UnitQuantity diff --git a/quantities/units/viscosity.pyi b/quantities/units/viscosity.pyi new file mode 100644 index 0000000..9290be4 --- /dev/null +++ b/quantities/units/viscosity.pyi @@ -0,0 +1,9 @@ +from ..unitquantity import UnitQuantity + +P: UnitQuantity +poise: UnitQuantity +cP: UnitQuantity +centipoise: UnitQuantity +St: UnitQuantity +stokes: UnitQuantity +rhe: UnitQuantity diff --git a/quantities/units/volume.pyi b/quantities/units/volume.pyi new file mode 100644 index 0000000..ad241b3 --- /dev/null +++ b/quantities/units/volume.pyi @@ -0,0 +1,78 @@ +from ..unitquantity import UnitQuantity + +l: UnitQuantity +L: UnitQuantity +liter: UnitQuantity +litre: UnitQuantity +mL: UnitQuantity +milliliter: UnitQuantity +millilitre: UnitQuantity +kL: UnitQuantity +kiloliter: UnitQuantity +kilolitre: UnitQuantity +ML: UnitQuantity +megaliter: UnitQuantity +megalitre: UnitQuantity +GL: UnitQuantity +gigaliter: UnitQuantity +gigalitre: UnitQuantity +cc: UnitQuantity +cubic_centimeter: UnitQuantity +milliliter: UnitQuantity +stere: UnitQuantity +gross_register_ton: UnitQuantity +register_ton: UnitQuantity +acre_foot: UnitQuantity +board_foot: UnitQuantity +bu: UnitQuantity +bushel: UnitQuantity +US_bushel: UnitQuantity +US_dry_gallon: UnitQuantity +gallon: UnitQuantity +liquid_gallon: UnitQuantity +US_liquid_gallon: UnitQuantity +dry_quart: UnitQuantity +US_dry_quart: UnitQuantity +dry_pint: UnitQuantity +US_dry_pint: UnitQuantity +quart: UnitQuantity +liquid_quart: UnitQuantity +US_liquid_quart: UnitQuantity +pt: UnitQuantity +pint: UnitQuantity +liquid_pint: UnitQuantity +US_liquid_pint: UnitQuantity +cup: UnitQuantity +US_liquid_cup: UnitQuantity +gill: UnitQuantity +US_liquid_gill: UnitQuantity +floz: UnitQuantity +fluid_ounce: UnitQuantity +US_fluid_ounce: UnitQuantity +US_liquid_ounce: UnitQuantity +Imperial_bushel: UnitQuantity +UK_liquid_gallon: UnitQuantity +Canadian_liquid_gallon: UnitQuantity +UK_liquid_quart: UnitQuantity +UK_liquid_pint: UnitQuantity +UK_liquid_cup: UnitQuantity +UK_liquid_gill: UnitQuantity +UK_fluid_ounce: UnitQuantity +UK_liquid_ounce: UnitQuantity +bbl: UnitQuantity +barrel: UnitQuantity +tbsp: UnitQuantity +Tbsp: UnitQuantity +Tblsp: UnitQuantity +tblsp: UnitQuantity +tbs: UnitQuantity +Tbl: UnitQuantity +tablespoon: UnitQuantity +tsp: UnitQuantity +teaspoon: UnitQuantity +pk: UnitQuantity +peck: UnitQuantity +fldr: UnitQuantity +fluid_dram: UnitQuantity +fluidram: UnitQuantity +firkin: UnitQuantity From b2ac718277007194e07a55e558a91e68a0dc6d78 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 11 Jan 2023 16:43:30 +0000 Subject: [PATCH 136/212] Updating test matrix following NEP29 + 1 year (#212) * Updating test matrix following NEP29 + 1 year * add NumPy 1.24 to test matrix * Add Python 3.11 to test matrix * exclude a combination which doesn't build on Github Actions --- .github/workflows/test.yml | 22 ++++++++++++---------- doc/user/installation.rst | 4 ++-- setup.py | 4 ++-- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 626bde6..43c7790 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,20 +12,22 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "3.7", "3.8", "3.9" ] - numpy-version: [ "1.16", "1.17", "1.18", "1.19", "1.20", "1.21" ] + python-version: [ "3.8", "3.9", "3.10", "3.11" ] + numpy-version: [ "1.19", "1.20", "1.21", "1.22", "1.23", "1.24" ] exclude: - - python-version: "3.9" - numpy-version: "1.16" + - python-version: "3.10" + numpy-version: "1.19" + os: ubuntu-latest + - python-version: "3.10" + numpy-version: "1.20" os: ubuntu-latest - - python-version: "3.9" - numpy-version: "1.17" + - python-version: "3.11" + numpy-version: "1.19" os: ubuntu-latest - - python-version: "3.8" - numpy-version: "1.17" + - python-version: "3.11" + numpy-version: "1.20" os: ubuntu-latest - include: - - python-version: "3.10" + - python-version: "3.11" numpy-version: "1.21" os: ubuntu-latest steps: diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 9e1bdb5..13a4651 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -8,8 +8,8 @@ Prerequisites Quantities has a few dependencies: -* Python_ (>=3.7) -* NumPy_ (>=1.16) +* Python_ (>=3.8) +* NumPy_ (>=1.19) (bearing in mind that not all combinations of Python and NumPy versions necessarily work). diff --git a/setup.py b/setup.py index 038df8a..9c4c9fb 100755 --- a/setup.py +++ b/setup.py @@ -136,8 +136,8 @@ def run(self): packages = packages, platforms = 'Any', requires = [ - 'python (>=3.7)', - 'numpy (>=1.16)', + 'python (>=3.8)', + 'numpy (>=1.19)', ], url = 'http://python-quantities.readthedocs.io/', version = versioneer.get_version(), From 9212b56979ac4de62127edee693edb8b3ee0f9c1 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 11 Jan 2023 17:47:02 +0100 Subject: [PATCH 137/212] try to fix readthedocs build pngmath has been replaced by imgmath in Sphinx --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index d0cd127..0bed75e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -23,7 +23,7 @@ # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', - 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', + 'sphinx.ext.coverage', 'sphinx.ext.imgmath', 'sphinx.ext.ifconfig', 'sphinx.ext.autosummary', 'ipython_console_highlighting', 'numpydoc.numpydoc' ] From 3f5ac5e8b28099716090e9f2a63f24f1d26efe20 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 11 Jan 2023 17:00:17 +0100 Subject: [PATCH 138/212] Starting to work on converting to pyproject.toml-based build/install --- README.rst | 11 +++++ pyproject.toml | 58 ++++++++++++++++++++++ quantities/_version.py | 42 ++++++++++------ setup.cfg | 12 ++--- setup.py | 106 ++++------------------------------------- 5 files changed, 111 insertions(+), 118 deletions(-) create mode 100644 pyproject.toml diff --git a/README.rst b/README.rst index 6831ada..2597cd2 100644 --- a/README.rst +++ b/README.rst @@ -2,12 +2,23 @@ quantities ========== +Quantities is designed to handle arithmetic and +conversions of physical quantities, which have a magnitude, dimensionality +specified by various units, and possibly an uncertainty. See the tutorial_ +for examples. Quantities builds on the popular numpy library and is +designed to work with numpy ufuncs, many of which are already +supported. Quantities is actively developed, and while the current features +and API are stable, test coverage is incomplete so the package is not +suggested for mission-critical applications. + |pypi version|_ |Build status|_ .. |pypi version| image:: https://img.shields.io/pypi/v/quantities.png .. _`pypi version`: https://pypi.python.org/pypi/quantities .. |Build status| image:: https://github.com/python-quantities/python-quantities/actions/workflows/test.yml/badge.svg?branch=master .. _`Build status`: https://github.com/python-quantities/python-quantities/actions/workflows/test.yml +.. _tutorial: http://python-quantities.readthedocs.io/en/latest/user/tutorial.html + A Python package for handling physical quantities. The source code and issue tracker are hosted on GitHub: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2c677dd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,58 @@ +[project] +name = "quantities" +version = "0.13.1.dev" +description = "Support for physical quantities with units, based on numpy" +readme = "README.rst" +requires-python = ">=3.7" +license = {file = "doc/user/license.rst"} +authors = [ + {name = "Darren Dale", email = "dsdale24@gmail.com"} +] +maintainers = [ + {name = "Andrew Davison", email = "andrew.davison@cnrs.fr"} +] +keywords = ["quantities", "units", "physical", "constants"] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Education", + "Topic :: Scientific/Engineering" +] +dependencies = [ + "numpy>=1.16" +] +dynamic = ["entry-points"] + +[project.optional-dependencies] +test = [ + "pytest", + "wheel" +] +doc = [ + "sphinx" +] + +[project.urls] +documentation = "http://python-quantities.readthedocs.io/" +repository = "https://github.com/python-quantities/python-quantities" +changelog = "https://github.com/python-quantities/python-quantities/blob/master/CHANGES.txt" +download = "http://pypi.python.org/pypi/quantities" + +[build-system] +requires = ["setuptools", "versioneer[toml]"] +build-backend = "setuptools.build_meta" + +[tool.versioneer] +VCS = "git" +style = "pep440" +versionfile_source = "quantities/_version.py" +versionfile_build = "quantities/_version.py" +tag_prefix = "v" +parentdir_prefix = "quantities-" diff --git a/quantities/_version.py b/quantities/_version.py index 19b5da1..ece826d 100644 --- a/quantities/_version.py +++ b/quantities/_version.py @@ -5,8 +5,9 @@ # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. -# This file is released into the public domain. Generated by -# versioneer-0.21 (https://github.com/python-versioneer/python-versioneer) +# This file is released into the public domain. +# Generated by versioneer-0.27 +# https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" @@ -16,6 +17,7 @@ import subprocess import sys from typing import Callable, Dict +import functools def get_keywords(): @@ -73,6 +75,14 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, """Call the given command(s).""" assert isinstance(commands, list) process = None + + popen_kwargs = {} + if sys.platform == "win32": + # This hides the console window if pythonw.exe is used + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + popen_kwargs["startupinfo"] = startupinfo + for command in commands: try: dispcmd = str([command] + args) @@ -80,7 +90,7 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, process = subprocess.Popen([command] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr - else None)) + else None), **popen_kwargs) break except OSError: e = sys.exc_info()[1] @@ -228,13 +238,18 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): version string, meaning we're inside a checked out source tree. """ GITS = ["git"] - TAG_PREFIX_REGEX = "*" if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - TAG_PREFIX_REGEX = r"\*" + + # GIT_DIR can interfere with correct operation of Versioneer. + # It may be intended to be passed to the Versioneer-versioned project, + # but that should not change where we get our version from. + env = os.environ.copy() + env.pop("GIT_DIR", None) + runner = functools.partial(runner, env=env) _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -242,11 +257,10 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", - "%s%s" % (tag_prefix, TAG_PREFIX_REGEX)], - cwd=root) + describe_out, rc = runner(GITS, [ + "describe", "--tags", "--dirty", "--always", "--long", + "--match", f"{tag_prefix}[[:digit:]]*" + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -335,8 +349,8 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) - pieces["distance"] = int(count_out) # total number of commits + out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) + pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() @@ -432,7 +446,7 @@ def render_pep440_pre(pieces): tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: - rendered += ".post%d.dev%d" % (post_version+1, pieces["distance"]) + rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%d" % (pieces["distance"]) else: diff --git a/setup.cfg b/setup.cfg index 4b37c9d..722e9b7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,5 @@ -[versioneer] -VCS = git -style = pep440 -versionfile_source = quantities/_version.py -versionfile_build = quantities/_version.py -tag_prefix = v -parentdir_prefix = quantities- \ No newline at end of file +[options.entry_points] + distutils.commands = + data = "setup:data" + sdist = "setup:sdist" + build = "setup:build" \ No newline at end of file diff --git a/setup.py b/setup.py index 9c4c9fb..23ae0c9 100755 --- a/setup.py +++ b/setup.py @@ -1,23 +1,15 @@ -from distutils.cmd import Command -from distutils.core import setup -from distutils.command.build import build as _build -import os -import sys - +from setuptools import Command, setup +from setuptools.command.build_py import build_py as _build +from setuptools.command.sdist import sdist as _sdist import versioneer - -TEST_RESULT = None - cmdclass = versioneer.get_cmdclass() _sdist = cmdclass['sdist'] - +from datetime import datetime class data(Command): - description = "Convert the NIST databas of constants" - + description = "Convert the NIST database of constants" user_options = [] - boolean_options = [] def initialize_options(self): @@ -33,7 +25,8 @@ def run(self): with open('quantities/constants/_codata.py', 'w') as f: f.write('# THIS FILE IS AUTOMATICALLY GENERATED\n') - f.write('# ANY CHANGES MADE HERE WILL BE LOST\n\n') + f.write('# ANY CHANGES MADE HERE WILL BE LOST\n') + f.write(f'# LAST GENERATED: {datetime.now()}\n\n') f.write('physical_constants = {}\n\n') for line in data: name = line[:55].rstrip().replace('mag.','magnetic') @@ -47,7 +40,6 @@ def run(self): cmdclass['data'] = data - class sdist(_sdist): def run(self): @@ -56,7 +48,6 @@ def run(self): cmdclass['sdist'] = sdist - class build(_build): def run(self): @@ -65,86 +56,7 @@ def run(self): cmdclass['build'] = build - -class test(Command): - - """Run the test suite.""" - - description = "Run the test suite" - - user_options = [('verbosity=', 'V', 'set test report verbosity')] - - def initialize_options(self): - self.verbosity = 0 - - def finalize_options(self): - try: - self.verbosity = int(self.verbosity) - except ValueError: - raise ValueError('verbosity must be an integer.') - - def run(self): - import sys - import unittest - suite = unittest.TestLoader().discover('.') - global TEST_RESULT - TEST_RESULT = unittest.TextTestRunner(verbosity=self.verbosity+1).run(suite) - -cmdclass['test'] = test - - -packages = [] -for dirpath, dirnames, filenames in os.walk('quantities'): - if '__init__.py' in filenames: - packages.append('.'.join(dirpath.split(os.sep))) - else: - del(dirnames[:]) - - setup( - author = 'Darren Dale', - author_email = 'dsdale24@gmail.com', -# classifiers = """Development Status :: 4 - Beta -# Environment :: Console -# Intended Audience :: Developers -# Intended Audience :: Education -# Intended Audience :: End Users/Desktop -# Intended Audience :: Science/Research -# License :: OSI Approved :: BSD License -# Operating System :: OS Independent -# Programming Language :: Python -# Topic :: Education -# Topic :: Scientific/Engineering -# """, - cmdclass = cmdclass, - description = "Support for physical quantities with units, based on numpy", - download_url = "http://pypi.python.org/pypi/quantities", - keywords = ['quantities', 'units', 'physical', 'constants'], - license = 'BSD', - long_description = """Quantities is designed to handle arithmetic and - conversions of physical quantities, which have a magnitude, dimensionality - specified by various units, and possibly an uncertainty. See the tutorial_ - for examples. Quantities builds on the popular numpy library and is - designed to work with numpy ufuncs, many of which are already - supported. Quantities is actively developed, and while the current features - and API are stable, test coverage is incomplete so the package is not - suggested for mission-critical applications. - - .. _tutorial: http://python-quantities.readthedocs.io/en/latest/user/tutorial.html - """, - name = 'quantities', - packages = packages, - platforms = 'Any', - requires = [ - 'python (>=3.8)', - 'numpy (>=1.19)', - ], - url = 'http://python-quantities.readthedocs.io/', - version = versioneer.get_version(), + version=versioneer.get_version(), + cmdclass=versioneer.get_cmdclass() ) - -if __name__ == '__main__': - if TEST_RESULT is not None: - if len(TEST_RESULT.errors) > 0: - # failing test -> Set non-success exit-code - sys.exit(os.EX_OK+1) From e630f95077315bbf7b9195f745a154204a4a33dc Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Thu, 12 Jan 2023 13:56:26 +0100 Subject: [PATCH 139/212] Switch from versioneer to setuptools_scm --- MANIFEST.in | 4 - pyproject.toml | 18 +- quantities/__init__.py | 3 +- quantities/_version.py | 658 ------------- setup.cfg | 5 - setup.py | 12 +- versioneer.py | 2109 ---------------------------------------- 7 files changed, 9 insertions(+), 2800 deletions(-) delete mode 100644 quantities/_version.py delete mode 100644 setup.cfg delete mode 100644 versioneer.py diff --git a/MANIFEST.in b/MANIFEST.in index 0a19b7f..de7ac30 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,2 @@ -include distribute_setup.py include CHANGES.txt -include py3tool.py include quantities/constants/NIST_codata.txt -include versioneer.py -include quantities/_version.py diff --git a/pyproject.toml b/pyproject.toml index 2c677dd..2c60291 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,8 @@ [project] name = "quantities" -version = "0.13.1.dev" description = "Support for physical quantities with units, based on numpy" readme = "README.rst" -requires-python = ">=3.7" +requires-python = ">=3.8" license = {file = "doc/user/license.rst"} authors = [ {name = "Darren Dale", email = "dsdale24@gmail.com"} @@ -26,9 +25,9 @@ classifiers = [ "Topic :: Scientific/Engineering" ] dependencies = [ - "numpy>=1.16" + "numpy>=1.19" ] -dynamic = ["entry-points"] +dynamic = ["version"] [project.optional-dependencies] test = [ @@ -46,13 +45,8 @@ changelog = "https://github.com/python-quantities/python-quantities/blob/master/ download = "http://pypi.python.org/pypi/quantities" [build-system] -requires = ["setuptools", "versioneer[toml]"] +requires = ["setuptools", "setuptools_scm[toml]"] build-backend = "setuptools.build_meta" -[tool.versioneer] -VCS = "git" -style = "pep440" -versionfile_source = "quantities/_version.py" -versionfile_build = "quantities/_version.py" -tag_prefix = "v" -parentdir_prefix = "quantities-" +[tool.setuptools_scm] +write_to = "quantities/_version.py" \ No newline at end of file diff --git a/quantities/__init__.py b/quantities/__init__.py index 1a20c9e..d315fbb 100644 --- a/quantities/__init__.py +++ b/quantities/__init__.py @@ -266,8 +266,7 @@ """ -from . import _version -__version__ = _version.get_versions()['version'] +from ._version import __version__ from .registry import unit_registry diff --git a/quantities/_version.py b/quantities/_version.py deleted file mode 100644 index ece826d..0000000 --- a/quantities/_version.py +++ /dev/null @@ -1,658 +0,0 @@ - -# This file helps to compute a version number in source trees obtained from -# git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (built by setup.py sdist) and build -# directories (produced by setup.py build) will contain a much shorter file -# that just contains the computed version number. - -# This file is released into the public domain. -# Generated by versioneer-0.27 -# https://github.com/python-versioneer/python-versioneer - -"""Git implementation of _version.py.""" - -import errno -import os -import re -import subprocess -import sys -from typing import Callable, Dict -import functools - - -def get_keywords(): - """Get the keywords needed to look up the version information.""" - # these strings will be replaced by git during git-archive. - # setup.py/versioneer.py will grep for the variable names, so they must - # each be defined on a line of their own. _version.py will just call - # get_keywords(). - git_refnames = "$Format:%d$" - git_full = "$Format:%H$" - git_date = "$Format:%ci$" - keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} - return keywords - - -class VersioneerConfig: - """Container for Versioneer configuration parameters.""" - - -def get_config(): - """Create, populate and return the VersioneerConfig() object.""" - # these strings are filled in when 'setup.py versioneer' creates - # _version.py - cfg = VersioneerConfig() - cfg.VCS = "git" - cfg.style = "pep440" - cfg.tag_prefix = "v" - cfg.parentdir_prefix = "quantities-" - cfg.versionfile_source = "quantities/_version.py" - cfg.verbose = False - return cfg - - -class NotThisMethod(Exception): - """Exception raised if a method is not valid for the current scenario.""" - - -LONG_VERSION_PY: Dict[str, str] = {} -HANDLERS: Dict[str, Dict[str, Callable]] = {} - - -def register_vcs_handler(vcs, method): # decorator - """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f): - """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f - return f - return decorate - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): - """Call the given command(s).""" - assert isinstance(commands, list) - process = None - - popen_kwargs = {} - if sys.platform == "win32": - # This hides the console window if pythonw.exe is used - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - popen_kwargs["startupinfo"] = startupinfo - - for command in commands: - try: - dispcmd = str([command] + args) - # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen([command] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None), **popen_kwargs) - break - except OSError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %s" % dispcmd) - print(e) - return None, None - else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None, None - stdout = process.communicate()[0].strip().decode() - if process.returncode != 0: - if verbose: - print("unable to run %s (error)" % dispcmd) - print("stdout was %s" % stdout) - return None, process.returncode - return stdout, process.returncode - - -def versions_from_parentdir(parentdir_prefix, root, verbose): - """Try to determine the version from the parent directory name. - - Source tarballs conventionally unpack into a directory that includes both - the project name and a version string. We will also support searching up - two directory levels for an appropriately named parent directory - """ - rootdirs = [] - - for _ in range(3): - dirname = os.path.basename(root) - if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} - rootdirs.append(root) - root = os.path.dirname(root) # up a level - - if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - - -@register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): - """Extract version information from the given file.""" - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - with open(versionfile_abs, "r") as fobj: - for line in fobj: - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - except OSError: - pass - return keywords - - -@register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): - """Get version information from git keywords.""" - if "refnames" not in keywords: - raise NotThisMethod("Short version file found") - date = keywords.get("date") - if date is not None: - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - - # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant - # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 - # -like" string, which we must then edit to make compliant), because - # it's been around since git-1.5.3, and it's too difficult to - # discover which version we're using, or to work around using an - # older one. - date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = {r.strip() for r in refnames.strip("()").split(",")} - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = {r for r in refs if re.search(r'\d', r)} - if verbose: - print("discarding '%s', no digits" % ",".join(refs - tags)) - if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - # Filter out refs that exactly match prefix or that don't start - # with a number once the prefix is stripped (mostly a concern - # when prefix is '') - if not re.match(r'\d', r): - continue - if verbose: - print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} - # no suitable tags, so version is "0+unknown", but full hex is still there - if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} - - -@register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): - """Get version from 'git describe' in the root of the source tree. - - This only gets called if the git-archive 'subst' keywords were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - - # GIT_DIR can interfere with correct operation of Versioneer. - # It may be intended to be passed to the Versioneer-versioned project, - # but that should not change where we get our version from. - env = os.environ.copy() - env.pop("GIT_DIR", None) - runner = functools.partial(runner, env=env) - - _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=not verbose) - if rc != 0: - if verbose: - print("Directory %s not under git control" % root) - raise NotThisMethod("'git rev-parse --git-dir' returned error") - - # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] - # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner(GITS, [ - "describe", "--tags", "--dirty", "--always", "--long", - "--match", f"{tag_prefix}[[:digit:]]*" - ], cwd=root) - # --long was added in git-1.5.5 - if describe_out is None: - raise NotThisMethod("'git describe' failed") - describe_out = describe_out.strip() - full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) - if full_out is None: - raise NotThisMethod("'git rev-parse' failed") - full_out = full_out.strip() - - pieces = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None - - branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], - cwd=root) - # --abbrev-ref was added in git-1.6.3 - if rc != 0 or branch_name is None: - raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") - branch_name = branch_name.strip() - - if branch_name == "HEAD": - # If we aren't exactly on a branch, pick a branch which represents - # the current commit. If all else fails, we are on a branchless - # commit. - branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) - # --contains was added in git-1.5.4 - if rc != 0 or branches is None: - raise NotThisMethod("'git branch --contains' returned error") - branches = branches.split("\n") - - # Remove the first line if we're running detached - if "(" in branches[0]: - branches.pop(0) - - # Strip off the leading "* " from the list of branches. - branches = [branch[2:] for branch in branches] - if "master" in branches: - branch_name = "master" - elif not branches: - branch_name = None - else: - # Pick the first branch that is returned. Good or bad. - branch_name = branches[0] - - pieces["branch"] = branch_name - - # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] - # TAG might have hyphens. - git_describe = describe_out - - # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty - if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] - - # now we have TAG-NUM-gHEX or HEX - - if "-" in git_describe: - # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) - if not mo: - # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) - return pieces - - # tag - full_tag = mo.group(1) - if not full_tag.startswith(tag_prefix): - if verbose: - fmt = "tag '%s' doesn't start with prefix '%s'" - print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) - return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] - - # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) - - # commit: short hex revision ID - pieces["short"] = mo.group(3) - - else: - # HEX: no tags - pieces["closest-tag"] = None - out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) - pieces["distance"] = len(out.split()) # total number of commits - - # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - - return pieces - - -def plus_or_dot(pieces): - """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" - - -def render_pep440(pieces): - """Build up version string, with post-release "local version identifier". - - Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty - - Exceptions: - 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_branch(pieces): - """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . - - The ".dev0" means not master branch. Note that .dev0 sorts backwards - (a feature branch will appear "older" than the master branch). - - Exceptions: - 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0" - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def pep440_split_post(ver): - """Split pep440 version string at the post-release segment. - - Returns the release segments before the post-release and the - post-release version number (or -1 if no post-release segment is present). - """ - vc = str.split(ver, ".post") - return vc[0], int(vc[1] or 0) if len(vc) == 2 else None - - -def render_pep440_pre(pieces): - """TAG[.postN.devDISTANCE] -- No -dirty. - - Exceptions: - 1: no tags. 0.post0.devDISTANCE - """ - if pieces["closest-tag"]: - if pieces["distance"]: - # update the post release segment - tag_version, post_version = pep440_split_post(pieces["closest-tag"]) - rendered = tag_version - if post_version is not None: - rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) - else: - rendered += ".post0.dev%d" % (pieces["distance"]) - else: - # no commits, use the tag as the version - rendered = pieces["closest-tag"] - else: - # exception #1 - rendered = "0.post0.dev%d" % pieces["distance"] - return rendered - - -def render_pep440_post(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX] . - - The ".dev0" means dirty. Note that .dev0 sorts backwards - (a dirty tree will appear "older" than the corresponding clean one), - but you shouldn't be releasing software with -dirty anyways. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - return rendered - - -def render_pep440_post_branch(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . - - The ".dev0" means not master branch. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_old(pieces): - """TAG[.postDISTANCE[.dev0]] . - - The ".dev0" means dirty. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - return rendered - - -def render_git_describe(pieces): - """TAG[-DISTANCE-gHEX][-dirty]. - - Like 'git describe --tags --dirty --always'. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render_git_describe_long(pieces): - """TAG-DISTANCE-gHEX[-dirty]. - - Like 'git describe --tags --dirty --always -long'. - The distance/hash is unconditional. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render(pieces, style): - """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": - rendered = render_pep440(pieces) - elif style == "pep440-branch": - rendered = render_pep440_branch(pieces) - elif style == "pep440-pre": - rendered = render_pep440_pre(pieces) - elif style == "pep440-post": - rendered = render_pep440_post(pieces) - elif style == "pep440-post-branch": - rendered = render_pep440_post_branch(pieces) - elif style == "pep440-old": - rendered = render_pep440_old(pieces) - elif style == "git-describe": - rendered = render_git_describe(pieces) - elif style == "git-describe-long": - rendered = render_git_describe_long(pieces) - else: - raise ValueError("unknown style '%s'" % style) - - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} - - -def get_versions(): - """Get version information or return default if unable to do so.""" - # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have - # __file__, we can work backwards from there to the root. Some - # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded keywords. - - cfg = get_config() - verbose = cfg.verbose - - try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) - except NotThisMethod: - pass - - try: - root = os.path.realpath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to this file. Invert - # this to find the root from __file__. - for _ in cfg.versionfile_source.split('/'): - root = os.path.dirname(root) - except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree", - "date": None} - - try: - pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) - return render(pieces, cfg.style) - except NotThisMethod: - pass - - try: - if cfg.parentdir_prefix: - return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) - except NotThisMethod: - pass - - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", "date": None} diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 722e9b7..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[options.entry_points] - distutils.commands = - data = "setup:data" - sdist = "setup:sdist" - build = "setup:build" \ No newline at end of file diff --git a/setup.py b/setup.py index 23ae0c9..1d139de 100755 --- a/setup.py +++ b/setup.py @@ -1,11 +1,9 @@ from setuptools import Command, setup from setuptools.command.build_py import build_py as _build from setuptools.command.sdist import sdist as _sdist -import versioneer -cmdclass = versioneer.get_cmdclass() -_sdist = cmdclass['sdist'] from datetime import datetime + class data(Command): description = "Convert the NIST database of constants" @@ -38,7 +36,6 @@ def run(self): %(val, prec, unit) f.write("physical_constants['%s'] = %s\n"%(name, d)) -cmdclass['data'] = data class sdist(_sdist): @@ -46,7 +43,6 @@ def run(self): self.run_command('data') _sdist.run(self) -cmdclass['sdist'] = sdist class build(_build): @@ -54,9 +50,5 @@ def run(self): self.run_command('data') _build.run(self) -cmdclass['build'] = build -setup( - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass() -) +setup(cmdclass={"build_py": build, "sdist": sdist, "data": data}) diff --git a/versioneer.py b/versioneer.py deleted file mode 100644 index b4cd1d6..0000000 --- a/versioneer.py +++ /dev/null @@ -1,2109 +0,0 @@ - -# Version: 0.21 - -"""The Versioneer - like a rocketeer, but for versions. - -The Versioneer -============== - -* like a rocketeer, but for versions! -* https://github.com/python-versioneer/python-versioneer -* Brian Warner -* License: Public Domain -* Compatible with: Python 3.6, 3.7, 3.8, 3.9 and pypy3 -* [![Latest Version][pypi-image]][pypi-url] -* [![Build Status][travis-image]][travis-url] - -This is a tool for managing a recorded version number in distutils-based -python projects. The goal is to remove the tedious and error-prone "update -the embedded version string" step from your release process. Making a new -release should be as easy as recording a new tag in your version-control -system, and maybe making new tarballs. - - -## Quick Install - -* `pip install versioneer` to somewhere in your $PATH -* add a `[versioneer]` section to your setup.cfg (see [Install](INSTALL.md)) -* run `versioneer install` in your source tree, commit the results -* Verify version information with `python setup.py version` - -## Version Identifiers - -Source trees come from a variety of places: - -* a version-control system checkout (mostly used by developers) -* a nightly tarball, produced by build automation -* a snapshot tarball, produced by a web-based VCS browser, like github's - "tarball from tag" feature -* a release tarball, produced by "setup.py sdist", distributed through PyPI - -Within each source tree, the version identifier (either a string or a number, -this tool is format-agnostic) can come from a variety of places: - -* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows - about recent "tags" and an absolute revision-id -* the name of the directory into which the tarball was unpacked -* an expanded VCS keyword ($Id$, etc) -* a `_version.py` created by some earlier build step - -For released software, the version identifier is closely related to a VCS -tag. Some projects use tag names that include more than just the version -string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool -needs to strip the tag prefix to extract the version identifier. For -unreleased software (between tags), the version identifier should provide -enough information to help developers recreate the same tree, while also -giving them an idea of roughly how old the tree is (after version 1.2, before -version 1.3). Many VCS systems can report a description that captures this, -for example `git describe --tags --dirty --always` reports things like -"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the -0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has -uncommitted changes). - -The version identifier is used for multiple purposes: - -* to allow the module to self-identify its version: `myproject.__version__` -* to choose a name and prefix for a 'setup.py sdist' tarball - -## Theory of Operation - -Versioneer works by adding a special `_version.py` file into your source -tree, where your `__init__.py` can import it. This `_version.py` knows how to -dynamically ask the VCS tool for version information at import time. - -`_version.py` also contains `$Revision$` markers, and the installation -process marks `_version.py` to have this marker rewritten with a tag name -during the `git archive` command. As a result, generated tarballs will -contain enough information to get the proper version. - -To allow `setup.py` to compute a version too, a `versioneer.py` is added to -the top level of your source tree, next to `setup.py` and the `setup.cfg` -that configures it. This overrides several distutils/setuptools commands to -compute the version when invoked, and changes `setup.py build` and `setup.py -sdist` to replace `_version.py` with a small static file that contains just -the generated version data. - -## Installation - -See [INSTALL.md](./INSTALL.md) for detailed installation instructions. - -## Version-String Flavors - -Code which uses Versioneer can learn about its version string at runtime by -importing `_version` from your main `__init__.py` file and running the -`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can -import the top-level `versioneer.py` and run `get_versions()`. - -Both functions return a dictionary with different flavors of version -information: - -* `['version']`: A condensed version string, rendered using the selected - style. This is the most commonly used value for the project's version - string. The default "pep440" style yields strings like `0.11`, - `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section - below for alternative styles. - -* `['full-revisionid']`: detailed revision identifier. For Git, this is the - full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". - -* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the - commit date in ISO 8601 format. This will be None if the date is not - available. - -* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that - this is only accurate if run in a VCS checkout, otherwise it is likely to - be False or None - -* `['error']`: if the version string could not be computed, this will be set - to a string describing the problem, otherwise it will be None. It may be - useful to throw an exception in setup.py if this is set, to avoid e.g. - creating tarballs with a version string of "unknown". - -Some variants are more useful than others. Including `full-revisionid` in a -bug report should allow developers to reconstruct the exact code being tested -(or indicate the presence of local changes that should be shared with the -developers). `version` is suitable for display in an "about" box or a CLI -`--version` output: it can be easily compared against release notes and lists -of bugs fixed in various releases. - -The installer adds the following text to your `__init__.py` to place a basic -version in `YOURPROJECT.__version__`: - - from ._version import get_versions - __version__ = get_versions()['version'] - del get_versions - -## Styles - -The setup.cfg `style=` configuration controls how the VCS information is -rendered into a version string. - -The default style, "pep440", produces a PEP440-compliant string, equal to the -un-prefixed tag name for actual releases, and containing an additional "local -version" section with more detail for in-between builds. For Git, this is -TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags ---dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the -tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and -that this commit is two revisions ("+2") beyond the "0.11" tag. For released -software (exactly equal to a known tag), the identifier will only contain the -stripped tag, e.g. "0.11". - -Other styles are available. See [details.md](details.md) in the Versioneer -source tree for descriptions. - -## Debugging - -Versioneer tries to avoid fatal errors: if something goes wrong, it will tend -to return a version of "0+unknown". To investigate the problem, run `setup.py -version`, which will run the version-lookup code in a verbose mode, and will -display the full contents of `get_versions()` (including the `error` string, -which may help identify what went wrong). - -## Known Limitations - -Some situations are known to cause problems for Versioneer. This details the -most significant ones. More can be found on Github -[issues page](https://github.com/python-versioneer/python-versioneer/issues). - -### Subprojects - -Versioneer has limited support for source trees in which `setup.py` is not in -the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are -two common reasons why `setup.py` might not be in the root: - -* Source trees which contain multiple subprojects, such as - [Buildbot](https://github.com/buildbot/buildbot), which contains both - "master" and "slave" subprojects, each with their own `setup.py`, - `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI - distributions (and upload multiple independently-installable tarballs). -* Source trees whose main purpose is to contain a C library, but which also - provide bindings to Python (and perhaps other languages) in subdirectories. - -Versioneer will look for `.git` in parent directories, and most operations -should get the right version string. However `pip` and `setuptools` have bugs -and implementation details which frequently cause `pip install .` from a -subproject directory to fail to find a correct version string (so it usually -defaults to `0+unknown`). - -`pip install --editable .` should work correctly. `setup.py install` might -work too. - -Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in -some later version. - -[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking -this issue. The discussion in -[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the -issue from the Versioneer side in more detail. -[pip PR#3176](https://github.com/pypa/pip/pull/3176) and -[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve -pip to let Versioneer work correctly. - -Versioneer-0.16 and earlier only looked for a `.git` directory next to the -`setup.cfg`, so subprojects were completely unsupported with those releases. - -### Editable installs with setuptools <= 18.5 - -`setup.py develop` and `pip install --editable .` allow you to install a -project into a virtualenv once, then continue editing the source code (and -test) without re-installing after every change. - -"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a -convenient way to specify executable scripts that should be installed along -with the python package. - -These both work as expected when using modern setuptools. When using -setuptools-18.5 or earlier, however, certain operations will cause -`pkg_resources.DistributionNotFound` errors when running the entrypoint -script, which must be resolved by re-installing the package. This happens -when the install happens with one version, then the egg_info data is -regenerated while a different version is checked out. Many setup.py commands -cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into -a different virtualenv), so this can be surprising. - -[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes -this one, but upgrading to a newer version of setuptools should probably -resolve it. - - -## Updating Versioneer - -To upgrade your project to a new release of Versioneer, do the following: - -* install the new Versioneer (`pip install -U versioneer` or equivalent) -* edit `setup.cfg`, if necessary, to include any new configuration settings - indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. -* re-run `versioneer install` in your source tree, to replace - `SRC/_version.py` -* commit any changed files - -## Future Directions - -This tool is designed to make it easily extended to other version-control -systems: all VCS-specific components are in separate directories like -src/git/ . The top-level `versioneer.py` script is assembled from these -components by running make-versioneer.py . In the future, make-versioneer.py -will take a VCS name as an argument, and will construct a version of -`versioneer.py` that is specific to the given VCS. It might also take the -configuration arguments that are currently provided manually during -installation by editing setup.py . Alternatively, it might go the other -direction and include code from all supported VCS systems, reducing the -number of intermediate scripts. - -## Similar projects - -* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time - dependency -* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of - versioneer -* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools - plugin - -## License - -To make Versioneer easier to embed, all its code is dedicated to the public -domain. The `_version.py` that it creates is also in the public domain. -Specifically, both are released under the Creative Commons "Public Domain -Dedication" license (CC0-1.0), as described in -https://creativecommons.org/publicdomain/zero/1.0/ . - -[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg -[pypi-url]: https://pypi.python.org/pypi/versioneer/ -[travis-image]: -https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg -[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer - -""" -# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring -# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements -# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error -# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with -# pylint:disable=attribute-defined-outside-init,too-many-arguments - -import configparser -import errno -import json -import os -import re -import subprocess -import sys -from typing import Callable, Dict - - -class VersioneerConfig: - """Container for Versioneer configuration parameters.""" - - -def get_root(): - """Get the project root directory. - - We require that all commands are run from the project root, i.e. the - directory that contains setup.py, setup.cfg, and versioneer.py . - """ - root = os.path.realpath(os.path.abspath(os.getcwd())) - setup_py = os.path.join(root, "setup.py") - versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): - # allow 'python path/to/setup.py COMMAND' - root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) - setup_py = os.path.join(root, "setup.py") - versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): - err = ("Versioneer was unable to run the project root directory. " - "Versioneer requires setup.py to be executed from " - "its immediate directory (like 'python setup.py COMMAND'), " - "or in a way that lets it use sys.argv[0] to find the root " - "(like 'python path/to/setup.py COMMAND').") - raise VersioneerBadRootError(err) - try: - # Certain runtime workflows (setup.py install/develop in a setuptools - # tree) execute all dependencies in a single python process, so - # "versioneer" may be imported multiple times, and python's shared - # module-import table will cache the first one. So we can't use - # os.path.dirname(__file__), as that will find whichever - # versioneer.py was first imported, even in later projects. - my_path = os.path.realpath(os.path.abspath(__file__)) - me_dir = os.path.normcase(os.path.splitext(my_path)[0]) - vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) - if me_dir != vsr_dir: - print("Warning: build in %s is using versioneer.py from %s" - % (os.path.dirname(my_path), versioneer_py)) - except NameError: - pass - return root - - -def get_config_from_root(root): - """Read the project setup.cfg file to determine Versioneer config.""" - # This might raise OSError (if setup.cfg is missing), or - # configparser.NoSectionError (if it lacks a [versioneer] section), or - # configparser.NoOptionError (if it lacks "VCS="). See the docstring at - # the top of versioneer.py for instructions on writing your setup.cfg . - setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.ConfigParser() - with open(setup_cfg, "r") as cfg_file: - parser.read_file(cfg_file) - VCS = parser.get("versioneer", "VCS") # mandatory - - # Dict-like interface for non-mandatory entries - section = parser["versioneer"] - - cfg = VersioneerConfig() - cfg.VCS = VCS - cfg.style = section.get("style", "") - cfg.versionfile_source = section.get("versionfile_source") - cfg.versionfile_build = section.get("versionfile_build") - cfg.tag_prefix = section.get("tag_prefix") - if cfg.tag_prefix in ("''", '""'): - cfg.tag_prefix = "" - cfg.parentdir_prefix = section.get("parentdir_prefix") - cfg.verbose = section.get("verbose") - return cfg - - -class NotThisMethod(Exception): - """Exception raised if a method is not valid for the current scenario.""" - - -# these dictionaries contain VCS-specific tools -LONG_VERSION_PY: Dict[str, str] = {} -HANDLERS: Dict[str, Dict[str, Callable]] = {} - - -def register_vcs_handler(vcs, method): # decorator - """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f): - """Store f in HANDLERS[vcs][method].""" - HANDLERS.setdefault(vcs, {})[method] = f - return f - return decorate - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): - """Call the given command(s).""" - assert isinstance(commands, list) - process = None - for command in commands: - try: - dispcmd = str([command] + args) - # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen([command] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except OSError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %s" % dispcmd) - print(e) - return None, None - else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None, None - stdout = process.communicate()[0].strip().decode() - if process.returncode != 0: - if verbose: - print("unable to run %s (error)" % dispcmd) - print("stdout was %s" % stdout) - return None, process.returncode - return stdout, process.returncode - - -LONG_VERSION_PY['git'] = r''' -# This file helps to compute a version number in source trees obtained from -# git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (built by setup.py sdist) and build -# directories (produced by setup.py build) will contain a much shorter file -# that just contains the computed version number. - -# This file is released into the public domain. Generated by -# versioneer-0.21 (https://github.com/python-versioneer/python-versioneer) - -"""Git implementation of _version.py.""" - -import errno -import os -import re -import subprocess -import sys -from typing import Callable, Dict - - -def get_keywords(): - """Get the keywords needed to look up the version information.""" - # these strings will be replaced by git during git-archive. - # setup.py/versioneer.py will grep for the variable names, so they must - # each be defined on a line of their own. _version.py will just call - # get_keywords(). - git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" - git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" - git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" - keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} - return keywords - - -class VersioneerConfig: - """Container for Versioneer configuration parameters.""" - - -def get_config(): - """Create, populate and return the VersioneerConfig() object.""" - # these strings are filled in when 'setup.py versioneer' creates - # _version.py - cfg = VersioneerConfig() - cfg.VCS = "git" - cfg.style = "%(STYLE)s" - cfg.tag_prefix = "%(TAG_PREFIX)s" - cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" - cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" - cfg.verbose = False - return cfg - - -class NotThisMethod(Exception): - """Exception raised if a method is not valid for the current scenario.""" - - -LONG_VERSION_PY: Dict[str, str] = {} -HANDLERS: Dict[str, Dict[str, Callable]] = {} - - -def register_vcs_handler(vcs, method): # decorator - """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f): - """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f - return f - return decorate - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): - """Call the given command(s).""" - assert isinstance(commands, list) - process = None - for command in commands: - try: - dispcmd = str([command] + args) - # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen([command] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except OSError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %%s" %% dispcmd) - print(e) - return None, None - else: - if verbose: - print("unable to find command, tried %%s" %% (commands,)) - return None, None - stdout = process.communicate()[0].strip().decode() - if process.returncode != 0: - if verbose: - print("unable to run %%s (error)" %% dispcmd) - print("stdout was %%s" %% stdout) - return None, process.returncode - return stdout, process.returncode - - -def versions_from_parentdir(parentdir_prefix, root, verbose): - """Try to determine the version from the parent directory name. - - Source tarballs conventionally unpack into a directory that includes both - the project name and a version string. We will also support searching up - two directory levels for an appropriately named parent directory - """ - rootdirs = [] - - for _ in range(3): - dirname = os.path.basename(root) - if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} - rootdirs.append(root) - root = os.path.dirname(root) # up a level - - if verbose: - print("Tried directories %%s but none started with prefix %%s" %% - (str(rootdirs), parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - - -@register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): - """Extract version information from the given file.""" - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - with open(versionfile_abs, "r") as fobj: - for line in fobj: - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - except OSError: - pass - return keywords - - -@register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): - """Get version information from git keywords.""" - if "refnames" not in keywords: - raise NotThisMethod("Short version file found") - date = keywords.get("date") - if date is not None: - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - - # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant - # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 - # -like" string, which we must then edit to make compliant), because - # it's been around since git-1.5.3, and it's too difficult to - # discover which version we're using, or to work around using an - # older one. - date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = {r.strip() for r in refnames.strip("()").split(",")} - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %%d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = {r for r in refs if re.search(r'\d', r)} - if verbose: - print("discarding '%%s', no digits" %% ",".join(refs - tags)) - if verbose: - print("likely tags: %%s" %% ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - # Filter out refs that exactly match prefix or that don't start - # with a number once the prefix is stripped (mostly a concern - # when prefix is '') - if not re.match(r'\d', r): - continue - if verbose: - print("picking %%s" %% r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} - # no suitable tags, so version is "0+unknown", but full hex is still there - if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} - - -@register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): - """Get version from 'git describe' in the root of the source tree. - - This only gets called if the git-archive 'subst' keywords were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - GITS = ["git"] - TAG_PREFIX_REGEX = "*" - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - TAG_PREFIX_REGEX = r"\*" - - _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) - if rc != 0: - if verbose: - print("Directory %%s not under git control" %% root) - raise NotThisMethod("'git rev-parse --git-dir' returned error") - - # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] - # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", - "%%s%%s" %% (tag_prefix, TAG_PREFIX_REGEX)], - cwd=root) - # --long was added in git-1.5.5 - if describe_out is None: - raise NotThisMethod("'git describe' failed") - describe_out = describe_out.strip() - full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) - if full_out is None: - raise NotThisMethod("'git rev-parse' failed") - full_out = full_out.strip() - - pieces = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None - - branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], - cwd=root) - # --abbrev-ref was added in git-1.6.3 - if rc != 0 or branch_name is None: - raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") - branch_name = branch_name.strip() - - if branch_name == "HEAD": - # If we aren't exactly on a branch, pick a branch which represents - # the current commit. If all else fails, we are on a branchless - # commit. - branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) - # --contains was added in git-1.5.4 - if rc != 0 or branches is None: - raise NotThisMethod("'git branch --contains' returned error") - branches = branches.split("\n") - - # Remove the first line if we're running detached - if "(" in branches[0]: - branches.pop(0) - - # Strip off the leading "* " from the list of branches. - branches = [branch[2:] for branch in branches] - if "master" in branches: - branch_name = "master" - elif not branches: - branch_name = None - else: - # Pick the first branch that is returned. Good or bad. - branch_name = branches[0] - - pieces["branch"] = branch_name - - # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] - # TAG might have hyphens. - git_describe = describe_out - - # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty - if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] - - # now we have TAG-NUM-gHEX or HEX - - if "-" in git_describe: - # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) - if not mo: - # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%%s'" - %% describe_out) - return pieces - - # tag - full_tag = mo.group(1) - if not full_tag.startswith(tag_prefix): - if verbose: - fmt = "tag '%%s' doesn't start with prefix '%%s'" - print(fmt %% (full_tag, tag_prefix)) - pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" - %% (full_tag, tag_prefix)) - return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] - - # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) - - # commit: short hex revision ID - pieces["short"] = mo.group(3) - - else: - # HEX: no tags - pieces["closest-tag"] = None - count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) - pieces["distance"] = int(count_out) # total number of commits - - # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - - return pieces - - -def plus_or_dot(pieces): - """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" - - -def render_pep440(pieces): - """Build up version string, with post-release "local version identifier". - - Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty - - Exceptions: - 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += plus_or_dot(pieces) - rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_branch(pieces): - """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . - - The ".dev0" means not master branch. Note that .dev0 sorts backwards - (a feature branch will appear "older" than the master branch). - - Exceptions: - 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0" - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+untagged.%%d.g%%s" %% (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def pep440_split_post(ver): - """Split pep440 version string at the post-release segment. - - Returns the release segments before the post-release and the - post-release version number (or -1 if no post-release segment is present). - """ - vc = str.split(ver, ".post") - return vc[0], int(vc[1] or 0) if len(vc) == 2 else None - - -def render_pep440_pre(pieces): - """TAG[.postN.devDISTANCE] -- No -dirty. - - Exceptions: - 1: no tags. 0.post0.devDISTANCE - """ - if pieces["closest-tag"]: - if pieces["distance"]: - # update the post release segment - tag_version, post_version = pep440_split_post(pieces["closest-tag"]) - rendered = tag_version - if post_version is not None: - rendered += ".post%%d.dev%%d" %% (post_version+1, pieces["distance"]) - else: - rendered += ".post0.dev%%d" %% (pieces["distance"]) - else: - # no commits, use the tag as the version - rendered = pieces["closest-tag"] - else: - # exception #1 - rendered = "0.post0.dev%%d" %% pieces["distance"] - return rendered - - -def render_pep440_post(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX] . - - The ".dev0" means dirty. Note that .dev0 sorts backwards - (a dirty tree will appear "older" than the corresponding clean one), - but you shouldn't be releasing software with -dirty anyways. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%%s" %% pieces["short"] - else: - # exception #1 - rendered = "0.post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%%s" %% pieces["short"] - return rendered - - -def render_pep440_post_branch(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . - - The ".dev0" means not master branch. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%%d" %% pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%%s" %% pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0.post%%d" %% pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+g%%s" %% pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_old(pieces): - """TAG[.postDISTANCE[.dev0]] . - - The ".dev0" means dirty. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - else: - # exception #1 - rendered = "0.post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - return rendered - - -def render_git_describe(pieces): - """TAG[-DISTANCE-gHEX][-dirty]. - - Like 'git describe --tags --dirty --always'. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render_git_describe_long(pieces): - """TAG-DISTANCE-gHEX[-dirty]. - - Like 'git describe --tags --dirty --always -long'. - The distance/hash is unconditional. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render(pieces, style): - """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": - rendered = render_pep440(pieces) - elif style == "pep440-branch": - rendered = render_pep440_branch(pieces) - elif style == "pep440-pre": - rendered = render_pep440_pre(pieces) - elif style == "pep440-post": - rendered = render_pep440_post(pieces) - elif style == "pep440-post-branch": - rendered = render_pep440_post_branch(pieces) - elif style == "pep440-old": - rendered = render_pep440_old(pieces) - elif style == "git-describe": - rendered = render_git_describe(pieces) - elif style == "git-describe-long": - rendered = render_git_describe_long(pieces) - else: - raise ValueError("unknown style '%%s'" %% style) - - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} - - -def get_versions(): - """Get version information or return default if unable to do so.""" - # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have - # __file__, we can work backwards from there to the root. Some - # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded keywords. - - cfg = get_config() - verbose = cfg.verbose - - try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) - except NotThisMethod: - pass - - try: - root = os.path.realpath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to this file. Invert - # this to find the root from __file__. - for _ in cfg.versionfile_source.split('/'): - root = os.path.dirname(root) - except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree", - "date": None} - - try: - pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) - return render(pieces, cfg.style) - except NotThisMethod: - pass - - try: - if cfg.parentdir_prefix: - return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) - except NotThisMethod: - pass - - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", "date": None} -''' - - -@register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): - """Extract version information from the given file.""" - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - with open(versionfile_abs, "r") as fobj: - for line in fobj: - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - except OSError: - pass - return keywords - - -@register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): - """Get version information from git keywords.""" - if "refnames" not in keywords: - raise NotThisMethod("Short version file found") - date = keywords.get("date") - if date is not None: - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - - # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant - # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 - # -like" string, which we must then edit to make compliant), because - # it's been around since git-1.5.3, and it's too difficult to - # discover which version we're using, or to work around using an - # older one. - date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = {r.strip() for r in refnames.strip("()").split(",")} - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = {r for r in refs if re.search(r'\d', r)} - if verbose: - print("discarding '%s', no digits" % ",".join(refs - tags)) - if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - # Filter out refs that exactly match prefix or that don't start - # with a number once the prefix is stripped (mostly a concern - # when prefix is '') - if not re.match(r'\d', r): - continue - if verbose: - print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} - # no suitable tags, so version is "0+unknown", but full hex is still there - if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} - - -@register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): - """Get version from 'git describe' in the root of the source tree. - - This only gets called if the git-archive 'subst' keywords were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - GITS = ["git"] - TAG_PREFIX_REGEX = "*" - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - TAG_PREFIX_REGEX = r"\*" - - _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) - if rc != 0: - if verbose: - print("Directory %s not under git control" % root) - raise NotThisMethod("'git rev-parse --git-dir' returned error") - - # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] - # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", - "%s%s" % (tag_prefix, TAG_PREFIX_REGEX)], - cwd=root) - # --long was added in git-1.5.5 - if describe_out is None: - raise NotThisMethod("'git describe' failed") - describe_out = describe_out.strip() - full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) - if full_out is None: - raise NotThisMethod("'git rev-parse' failed") - full_out = full_out.strip() - - pieces = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None - - branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], - cwd=root) - # --abbrev-ref was added in git-1.6.3 - if rc != 0 or branch_name is None: - raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") - branch_name = branch_name.strip() - - if branch_name == "HEAD": - # If we aren't exactly on a branch, pick a branch which represents - # the current commit. If all else fails, we are on a branchless - # commit. - branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) - # --contains was added in git-1.5.4 - if rc != 0 or branches is None: - raise NotThisMethod("'git branch --contains' returned error") - branches = branches.split("\n") - - # Remove the first line if we're running detached - if "(" in branches[0]: - branches.pop(0) - - # Strip off the leading "* " from the list of branches. - branches = [branch[2:] for branch in branches] - if "master" in branches: - branch_name = "master" - elif not branches: - branch_name = None - else: - # Pick the first branch that is returned. Good or bad. - branch_name = branches[0] - - pieces["branch"] = branch_name - - # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] - # TAG might have hyphens. - git_describe = describe_out - - # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty - if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] - - # now we have TAG-NUM-gHEX or HEX - - if "-" in git_describe: - # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) - if not mo: - # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) - return pieces - - # tag - full_tag = mo.group(1) - if not full_tag.startswith(tag_prefix): - if verbose: - fmt = "tag '%s' doesn't start with prefix '%s'" - print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) - return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] - - # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) - - # commit: short hex revision ID - pieces["short"] = mo.group(3) - - else: - # HEX: no tags - pieces["closest-tag"] = None - count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) - pieces["distance"] = int(count_out) # total number of commits - - # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - - return pieces - - -def do_vcs_install(manifest_in, versionfile_source, ipy): - """Git-specific installation logic for Versioneer. - - For Git, this means creating/changing .gitattributes to mark _version.py - for export-subst keyword substitution. - """ - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - files = [manifest_in, versionfile_source] - if ipy: - files.append(ipy) - try: - my_path = __file__ - if my_path.endswith(".pyc") or my_path.endswith(".pyo"): - my_path = os.path.splitext(my_path)[0] + ".py" - versioneer_file = os.path.relpath(my_path) - except NameError: - versioneer_file = "versioneer.py" - files.append(versioneer_file) - present = False - try: - with open(".gitattributes", "r") as fobj: - for line in fobj: - if line.strip().startswith(versionfile_source): - if "export-subst" in line.strip().split()[1:]: - present = True - break - except OSError: - pass - if not present: - with open(".gitattributes", "a+") as fobj: - fobj.write(f"{versionfile_source} export-subst\n") - files.append(".gitattributes") - run_command(GITS, ["add", "--"] + files) - - -def versions_from_parentdir(parentdir_prefix, root, verbose): - """Try to determine the version from the parent directory name. - - Source tarballs conventionally unpack into a directory that includes both - the project name and a version string. We will also support searching up - two directory levels for an appropriately named parent directory - """ - rootdirs = [] - - for _ in range(3): - dirname = os.path.basename(root) - if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} - rootdirs.append(root) - root = os.path.dirname(root) # up a level - - if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - - -SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.21) from -# revision-control system data, or from the parent directory name of an -# unpacked source archive. Distribution tarballs contain a pre-generated copy -# of this file. - -import json - -version_json = ''' -%s -''' # END VERSION_JSON - - -def get_versions(): - return json.loads(version_json) -""" - - -def versions_from_file(filename): - """Try to determine the version from _version.py if present.""" - try: - with open(filename) as f: - contents = f.read() - except OSError: - raise NotThisMethod("unable to read _version.py") - mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", - contents, re.M | re.S) - if not mo: - mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", - contents, re.M | re.S) - if not mo: - raise NotThisMethod("no version_json in _version.py") - return json.loads(mo.group(1)) - - -def write_to_version_file(filename, versions): - """Write the given version number to the given _version.py file.""" - os.unlink(filename) - contents = json.dumps(versions, sort_keys=True, - indent=1, separators=(",", ": ")) - with open(filename, "w") as f: - f.write(SHORT_VERSION_PY % contents) - - print("set %s to '%s'" % (filename, versions["version"])) - - -def plus_or_dot(pieces): - """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" - - -def render_pep440(pieces): - """Build up version string, with post-release "local version identifier". - - Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty - - Exceptions: - 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_branch(pieces): - """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . - - The ".dev0" means not master branch. Note that .dev0 sorts backwards - (a feature branch will appear "older" than the master branch). - - Exceptions: - 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0" - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def pep440_split_post(ver): - """Split pep440 version string at the post-release segment. - - Returns the release segments before the post-release and the - post-release version number (or -1 if no post-release segment is present). - """ - vc = str.split(ver, ".post") - return vc[0], int(vc[1] or 0) if len(vc) == 2 else None - - -def render_pep440_pre(pieces): - """TAG[.postN.devDISTANCE] -- No -dirty. - - Exceptions: - 1: no tags. 0.post0.devDISTANCE - """ - if pieces["closest-tag"]: - if pieces["distance"]: - # update the post release segment - tag_version, post_version = pep440_split_post(pieces["closest-tag"]) - rendered = tag_version - if post_version is not None: - rendered += ".post%d.dev%d" % (post_version+1, pieces["distance"]) - else: - rendered += ".post0.dev%d" % (pieces["distance"]) - else: - # no commits, use the tag as the version - rendered = pieces["closest-tag"] - else: - # exception #1 - rendered = "0.post0.dev%d" % pieces["distance"] - return rendered - - -def render_pep440_post(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX] . - - The ".dev0" means dirty. Note that .dev0 sorts backwards - (a dirty tree will appear "older" than the corresponding clean one), - but you shouldn't be releasing software with -dirty anyways. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - return rendered - - -def render_pep440_post_branch(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . - - The ".dev0" means not master branch. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_old(pieces): - """TAG[.postDISTANCE[.dev0]] . - - The ".dev0" means dirty. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - return rendered - - -def render_git_describe(pieces): - """TAG[-DISTANCE-gHEX][-dirty]. - - Like 'git describe --tags --dirty --always'. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render_git_describe_long(pieces): - """TAG-DISTANCE-gHEX[-dirty]. - - Like 'git describe --tags --dirty --always -long'. - The distance/hash is unconditional. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render(pieces, style): - """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": - rendered = render_pep440(pieces) - elif style == "pep440-branch": - rendered = render_pep440_branch(pieces) - elif style == "pep440-pre": - rendered = render_pep440_pre(pieces) - elif style == "pep440-post": - rendered = render_pep440_post(pieces) - elif style == "pep440-post-branch": - rendered = render_pep440_post_branch(pieces) - elif style == "pep440-old": - rendered = render_pep440_old(pieces) - elif style == "git-describe": - rendered = render_git_describe(pieces) - elif style == "git-describe-long": - rendered = render_git_describe_long(pieces) - else: - raise ValueError("unknown style '%s'" % style) - - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} - - -class VersioneerBadRootError(Exception): - """The project root directory is unknown or missing key files.""" - - -def get_versions(verbose=False): - """Get the project version from whatever source is available. - - Returns dict with two keys: 'version' and 'full'. - """ - if "versioneer" in sys.modules: - # see the discussion in cmdclass.py:get_cmdclass() - del sys.modules["versioneer"] - - root = get_root() - cfg = get_config_from_root(root) - - assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" - handlers = HANDLERS.get(cfg.VCS) - assert handlers, "unrecognized VCS '%s'" % cfg.VCS - verbose = verbose or cfg.verbose - assert cfg.versionfile_source is not None, \ - "please set versioneer.versionfile_source" - assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" - - versionfile_abs = os.path.join(root, cfg.versionfile_source) - - # extract version from first of: _version.py, VCS command (e.g. 'git - # describe'), parentdir. This is meant to work for developers using a - # source checkout, for users of a tarball created by 'setup.py sdist', - # and for users of a tarball/zipball created by 'git archive' or github's - # download-from-tag feature or the equivalent in other VCSes. - - get_keywords_f = handlers.get("get_keywords") - from_keywords_f = handlers.get("keywords") - if get_keywords_f and from_keywords_f: - try: - keywords = get_keywords_f(versionfile_abs) - ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) - if verbose: - print("got version from expanded keyword %s" % ver) - return ver - except NotThisMethod: - pass - - try: - ver = versions_from_file(versionfile_abs) - if verbose: - print("got version from file %s %s" % (versionfile_abs, ver)) - return ver - except NotThisMethod: - pass - - from_vcs_f = handlers.get("pieces_from_vcs") - if from_vcs_f: - try: - pieces = from_vcs_f(cfg.tag_prefix, root, verbose) - ver = render(pieces, cfg.style) - if verbose: - print("got version from VCS %s" % ver) - return ver - except NotThisMethod: - pass - - try: - if cfg.parentdir_prefix: - ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) - if verbose: - print("got version from parentdir %s" % ver) - return ver - except NotThisMethod: - pass - - if verbose: - print("unable to compute version") - - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, "error": "unable to compute version", - "date": None} - - -def get_version(): - """Get the short version string for this project.""" - return get_versions()["version"] - - -def get_cmdclass(cmdclass=None): - """Get the custom setuptools/distutils subclasses used by Versioneer. - - If the package uses a different cmdclass (e.g. one from numpy), it - should be provide as an argument. - """ - if "versioneer" in sys.modules: - del sys.modules["versioneer"] - # this fixes the "python setup.py develop" case (also 'install' and - # 'easy_install .'), in which subdependencies of the main project are - # built (using setup.py bdist_egg) in the same python process. Assume - # a main project A and a dependency B, which use different versions - # of Versioneer. A's setup.py imports A's Versioneer, leaving it in - # sys.modules by the time B's setup.py is executed, causing B to run - # with the wrong versioneer. Setuptools wraps the sub-dep builds in a - # sandbox that restores sys.modules to it's pre-build state, so the - # parent is protected against the child's "import versioneer". By - # removing ourselves from sys.modules here, before the child build - # happens, we protect the child from the parent's versioneer too. - # Also see https://github.com/python-versioneer/python-versioneer/issues/52 - - cmds = {} if cmdclass is None else cmdclass.copy() - - # we add "version" to both distutils and setuptools - from distutils.core import Command - - class cmd_version(Command): - description = "report generated version string" - user_options = [] - boolean_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - vers = get_versions(verbose=True) - print("Version: %s" % vers["version"]) - print(" full-revisionid: %s" % vers.get("full-revisionid")) - print(" dirty: %s" % vers.get("dirty")) - print(" date: %s" % vers.get("date")) - if vers["error"]: - print(" error: %s" % vers["error"]) - cmds["version"] = cmd_version - - # we override "build_py" in both distutils and setuptools - # - # most invocation pathways end up running build_py: - # distutils/build -> build_py - # distutils/install -> distutils/build ->.. - # setuptools/bdist_wheel -> distutils/install ->.. - # setuptools/bdist_egg -> distutils/install_lib -> build_py - # setuptools/install -> bdist_egg ->.. - # setuptools/develop -> ? - # pip install: - # copies source tree to a tempdir before running egg_info/etc - # if .git isn't copied too, 'git describe' will fail - # then does setup.py bdist_wheel, or sometimes setup.py install - # setup.py egg_info -> ? - - # we override different "build_py" commands for both environments - if 'build_py' in cmds: - _build_py = cmds['build_py'] - elif "setuptools" in sys.modules: - from setuptools.command.build_py import build_py as _build_py - else: - from distutils.command.build_py import build_py as _build_py - - class cmd_build_py(_build_py): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - _build_py.run(self) - # now locate _version.py in the new build/ directory and replace - # it with an updated value - if cfg.versionfile_build: - target_versionfile = os.path.join(self.build_lib, - cfg.versionfile_build) - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - cmds["build_py"] = cmd_build_py - - if 'build_ext' in cmds: - _build_ext = cmds['build_ext'] - elif "setuptools" in sys.modules: - from setuptools.command.build_ext import build_ext as _build_ext - else: - from distutils.command.build_ext import build_ext as _build_ext - - class cmd_build_ext(_build_ext): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - _build_ext.run(self) - if self.inplace: - # build_ext --inplace will only build extensions in - # build/lib<..> dir with no _version.py to write to. - # As in place builds will already have a _version.py - # in the module dir, we do not need to write one. - return - # now locate _version.py in the new build/ directory and replace - # it with an updated value - target_versionfile = os.path.join(self.build_lib, - cfg.versionfile_build) - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - cmds["build_ext"] = cmd_build_ext - - if "cx_Freeze" in sys.modules: # cx_freeze enabled? - from cx_Freeze.dist import build_exe as _build_exe - # nczeczulin reports that py2exe won't like the pep440-style string - # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. - # setup(console=[{ - # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION - # "product_version": versioneer.get_version(), - # ... - - class cmd_build_exe(_build_exe): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - target_versionfile = cfg.versionfile_source - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - - _build_exe.run(self) - os.unlink(target_versionfile) - with open(cfg.versionfile_source, "w") as f: - LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % - {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) - cmds["build_exe"] = cmd_build_exe - del cmds["build_py"] - - if 'py2exe' in sys.modules: # py2exe enabled? - from py2exe.distutils_buildexe import py2exe as _py2exe - - class cmd_py2exe(_py2exe): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - target_versionfile = cfg.versionfile_source - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - - _py2exe.run(self) - os.unlink(target_versionfile) - with open(cfg.versionfile_source, "w") as f: - LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % - {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) - cmds["py2exe"] = cmd_py2exe - - # we override different "sdist" commands for both environments - if 'sdist' in cmds: - _sdist = cmds['sdist'] - elif "setuptools" in sys.modules: - from setuptools.command.sdist import sdist as _sdist - else: - from distutils.command.sdist import sdist as _sdist - - class cmd_sdist(_sdist): - def run(self): - versions = get_versions() - self._versioneer_generated_versions = versions - # unless we update this, the command will keep using the old - # version - self.distribution.metadata.version = versions["version"] - return _sdist.run(self) - - def make_release_tree(self, base_dir, files): - root = get_root() - cfg = get_config_from_root(root) - _sdist.make_release_tree(self, base_dir, files) - # now locate _version.py in the new base_dir directory - # (remembering that it may be a hardlink) and replace it with an - # updated value - target_versionfile = os.path.join(base_dir, cfg.versionfile_source) - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, - self._versioneer_generated_versions) - cmds["sdist"] = cmd_sdist - - return cmds - - -CONFIG_ERROR = """ -setup.cfg is missing the necessary Versioneer configuration. You need -a section like: - - [versioneer] - VCS = git - style = pep440 - versionfile_source = src/myproject/_version.py - versionfile_build = myproject/_version.py - tag_prefix = - parentdir_prefix = myproject- - -You will also need to edit your setup.py to use the results: - - import versioneer - setup(version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), ...) - -Please read the docstring in ./versioneer.py for configuration instructions, -edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. -""" - -SAMPLE_CONFIG = """ -# See the docstring in versioneer.py for instructions. Note that you must -# re-run 'versioneer.py setup' after changing this section, and commit the -# resulting files. - -[versioneer] -#VCS = git -#style = pep440 -#versionfile_source = -#versionfile_build = -#tag_prefix = -#parentdir_prefix = - -""" - -OLD_SNIPPET = """ -from ._version import get_versions -__version__ = get_versions()['version'] -del get_versions -""" - -INIT_PY_SNIPPET = """ -from . import {0} -__version__ = {0}.get_versions()['version'] -""" - - -def do_setup(): - """Do main VCS-independent setup function for installing Versioneer.""" - root = get_root() - try: - cfg = get_config_from_root(root) - except (OSError, configparser.NoSectionError, - configparser.NoOptionError) as e: - if isinstance(e, (OSError, configparser.NoSectionError)): - print("Adding sample versioneer config to setup.cfg", - file=sys.stderr) - with open(os.path.join(root, "setup.cfg"), "a") as f: - f.write(SAMPLE_CONFIG) - print(CONFIG_ERROR, file=sys.stderr) - return 1 - - print(" creating %s" % cfg.versionfile_source) - with open(cfg.versionfile_source, "w") as f: - LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) - - ipy = os.path.join(os.path.dirname(cfg.versionfile_source), - "__init__.py") - if os.path.exists(ipy): - try: - with open(ipy, "r") as f: - old = f.read() - except OSError: - old = "" - module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] - snippet = INIT_PY_SNIPPET.format(module) - if OLD_SNIPPET in old: - print(" replacing boilerplate in %s" % ipy) - with open(ipy, "w") as f: - f.write(old.replace(OLD_SNIPPET, snippet)) - elif snippet not in old: - print(" appending to %s" % ipy) - with open(ipy, "a") as f: - f.write(snippet) - else: - print(" %s unmodified" % ipy) - else: - print(" %s doesn't exist, ok" % ipy) - ipy = None - - # Make sure both the top-level "versioneer.py" and versionfile_source - # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so - # they'll be copied into source distributions. Pip won't be able to - # install the package without this. - manifest_in = os.path.join(root, "MANIFEST.in") - simple_includes = set() - try: - with open(manifest_in, "r") as f: - for line in f: - if line.startswith("include "): - for include in line.split()[1:]: - simple_includes.add(include) - except OSError: - pass - # That doesn't cover everything MANIFEST.in can do - # (http://docs.python.org/2/distutils/sourcedist.html#commands), so - # it might give some false negatives. Appending redundant 'include' - # lines is safe, though. - if "versioneer.py" not in simple_includes: - print(" appending 'versioneer.py' to MANIFEST.in") - with open(manifest_in, "a") as f: - f.write("include versioneer.py\n") - else: - print(" 'versioneer.py' already in MANIFEST.in") - if cfg.versionfile_source not in simple_includes: - print(" appending versionfile_source ('%s') to MANIFEST.in" % - cfg.versionfile_source) - with open(manifest_in, "a") as f: - f.write("include %s\n" % cfg.versionfile_source) - else: - print(" versionfile_source already in MANIFEST.in") - - # Make VCS-specific changes. For git, this means creating/changing - # .gitattributes to mark _version.py for export-subst keyword - # substitution. - do_vcs_install(manifest_in, cfg.versionfile_source, ipy) - return 0 - - -def scan_setup_py(): - """Validate the contents of setup.py against Versioneer's expectations.""" - found = set() - setters = False - errors = 0 - with open("setup.py", "r") as f: - for line in f.readlines(): - if "import versioneer" in line: - found.add("import") - if "versioneer.get_cmdclass()" in line: - found.add("cmdclass") - if "versioneer.get_version()" in line: - found.add("get_version") - if "versioneer.VCS" in line: - setters = True - if "versioneer.versionfile_source" in line: - setters = True - if len(found) != 3: - print("") - print("Your setup.py appears to be missing some important items") - print("(but I might be wrong). Please make sure it has something") - print("roughly like the following:") - print("") - print(" import versioneer") - print(" setup( version=versioneer.get_version(),") - print(" cmdclass=versioneer.get_cmdclass(), ...)") - print("") - errors += 1 - if setters: - print("You should remove lines like 'versioneer.VCS = ' and") - print("'versioneer.versionfile_source = ' . This configuration") - print("now lives in setup.cfg, and should be removed from setup.py") - print("") - errors += 1 - return errors - - -if __name__ == "__main__": - cmd = sys.argv[1] - if cmd == "setup": - errors = do_setup() - errors += scan_setup_py() - if errors: - sys.exit(1) From 5927a652ed842612e31dad6fae5956197c05fa48 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Thu, 12 Jan 2023 14:27:19 +0100 Subject: [PATCH 140/212] update docs to reflect move away from setup.py --- .gitignore | 2 ++ MANIFEST.in | 1 + README.rst | 5 +---- conda.recipe/meta.yaml | 2 +- doc/devel/devnotes.rst | 5 +---- doc/devel/documenting.rst | 5 ++--- doc/devel/release.rst | 19 +++---------------- doc/user/installation.rst | 5 ++--- 8 files changed, 13 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 5d19806..b4d65eb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ dist .settings .idea .*cache/ +_version.py +MANIFEST \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index de7ac30..aa5ef33 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include CHANGES.txt include quantities/constants/NIST_codata.txt +exclude conda.recipe \ No newline at end of file diff --git a/README.rst b/README.rst index 2597cd2..12c56fd 100644 --- a/README.rst +++ b/README.rst @@ -65,11 +65,8 @@ http://docs.scipy.org/doc/numpy/user/install.html To install quantities itself, then simply run:: - $ python setup.py install --user + $ pip install quantities -If you install it system-wide, you may need to prefix the previous command with ``sudo``:: - - $ sudo python setup.py install Tests ----- diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 3b416ca..8ef6016 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -22,5 +22,5 @@ test: about: license: BSD - home: http://pythonhosted.org//quantities/ + home: https://github.com/python-quantities/python-quantities/ summary: Physical quantities with units, based upon Numpy diff --git a/doc/devel/devnotes.rst b/doc/devel/devnotes.rst index cacebbb..e48ce96 100644 --- a/doc/devel/devnotes.rst +++ b/doc/devel/devnotes.rst @@ -5,7 +5,4 @@ Quantities development uses the principles of test-driven development. New features or bug fixes need to be accompanied by unit tests based on Python's unittest package. Unit tests can be run with the following:: - python setup.py test - -This works with the version of unittest provided by the python-2.7 and -python-3.2+ standard library. + pytest diff --git a/doc/devel/documenting.rst b/doc/devel/documenting.rst index 64ffd69..f43eb01 100644 --- a/doc/devel/documenting.rst +++ b/doc/devel/documenting.rst @@ -13,7 +13,7 @@ Sphinx extension. Sphinx-0.6.3 or later is required. You can obtain Sphinx and numpydoc from the `Python Package Index`_ or by doing:: - easy_install sphinx + pip install sphinx .. _Sphinx: http://sphinx.pocoo.org/ .. _numpydoc: http://pypi.python.org/pypi/numpydoc @@ -37,7 +37,7 @@ Organization of Quantities' documentation ========================================== The actual ReStructured Text files are kept in :file:`doc`. The main -entry point is :file:`doc/index.rst`, which pulls in the +entry point is :file:`doc/index.rst`, which pulls in the :file:`index.rst` file for the user guide and the developers guide. The documentation suite is built as a single document in order to make the most effective use of cross referencing, we want to make @@ -314,4 +314,3 @@ Some helpful functions:: C-c C-r rst-shift-region-right Shift region to the right - diff --git a/doc/devel/release.rst b/doc/devel/release.rst index 62600de..f434043 100644 --- a/doc/devel/release.rst +++ b/doc/devel/release.rst @@ -8,8 +8,8 @@ Creating Source Releases Quantities is distributed as a source release for Linux and Mac OS. To create a source release, just do:: - python setup.py register - python setup.py sdist --formats=zip,gztar + pip install build + python -m build twine upload dist/quantities-.* (replacing `x`, `y` and `z` appropriately). @@ -23,24 +23,11 @@ like:: You can create a source distribution without uploading by doing:: - python setup.py sdist + python -m build --sdist This creates a source distribution in the `dist/` directory. -Creating Windows Installers -=========================== - -We distribute binary installers for the windows platform. In order to build the -windows installer, open a DOS window, cd into the quantities source directory -and run:: - - python setup.py build - python setup.py bdist_msi - -This creates the executable windows installer in the `dist/` directory. - - Building Quantities documentation ================================= diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 13a4651..f3c40c9 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -17,9 +17,8 @@ Quantities has a few dependencies: Source Code Installation ======================== -To install Quantities, download the Quantites sourcecode from PyPI_ -and run "python setup.py install" in the quantities source directory, -or run "pip install quantities". +To install Quantities, run "pip install quantities". + Development =========== From 315ccddf61f12a38482cb568033832576ade0d54 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Thu, 12 Jan 2023 14:33:35 +0100 Subject: [PATCH 141/212] trying to fix problem with _version.py not being generated when running CI tests --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 43c7790..c3aeb53 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,7 +61,7 @@ jobs: - name: Install run: | - python setup.py install + pip install . - name: Test run: | From 57d2db45030780c804738b83ba0ecf8cc76bdebe Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Mon, 6 Feb 2023 11:16:07 +0100 Subject: [PATCH 142/212] updated changelog --- CHANGES.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 337b3c3..b31cc65 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,21 @@ CHANGES ======= +------ +0.14.0 +------ + +- Added decimeter to list of supported units [`PR#202 `_] +- Removed deprecated mb as symbol for millibar unit [`PR#203 `_] +- Fixed failure to preserve dtype in rescale [`PR#204 `_] +- Added exp2 as a supported ufunc +- Fixed failure to handle units with floordiv [`PR#207 `_] +- Added femtofarad (fF) to list of supported units +- Dropped support for Python 3.7 +- Dropped support for NumPy versions older than 1.19 +- Converted the project packaging from setup.py-based to pyproject.toml-based + + ------ 0.13.0 ------ From 92122093b7afdc6fe72c272855ce28ff5ace4f2c Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Mon, 6 Feb 2023 11:37:05 +0100 Subject: [PATCH 143/212] Adding .readthedocs.yaml to ensure the package is installed with pip, not `setup.py install` --- .readthedocs.yaml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..5b39a43 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,27 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: doc/conf.py + +# If using Sphinx, optionally build your docs in additional formats such as PDF +formats: + - pdf + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: doc/rtd-requirements.txt + - method: pip + path: . From abefd96105d671532d4ce07461ade4a5e406aaf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Fri, 17 Feb 2023 05:59:13 +0100 Subject: [PATCH 144/212] Fix gh-215, dtype conversion in rescale --- quantities/quantity.py | 4 +++- quantities/tests/test_conversion.py | 9 +++++---- quantities/tests/test_methods.py | 13 ++++++++++--- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 4ff645b..9328525 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -219,7 +219,9 @@ def rescale(self, units=None, dtype=None): 'Unable to convert between units of "%s" and "%s"' %(from_u._dimensionality, to_u._dimensionality) ) - return Quantity(cf*self.magnitude, to_u, dtype=dtype) + new_magnitude = cf*self.magnitude + dtype = np.result_type(dtype, new_magnitude) + return Quantity(new_magnitude, to_u, dtype=dtype) def rescale_preferred(self): """ diff --git a/quantities/tests/test_conversion.py b/quantities/tests/test_conversion.py index ca82a34..aede463 100644 --- a/quantities/tests/test_conversion.py +++ b/quantities/tests/test_conversion.py @@ -1,4 +1,5 @@ import unittest +import numpy as np from .. import units as pq from .. import quantity from .common import TestCase @@ -15,7 +16,7 @@ def test_inplace_conversion(self): def test_rescale(self): for u in ('ft', 'feet', pq.ft): self.assertQuantityEqual((10*pq.m).rescale(u), 32.80839895 * pq.ft) - + def test_rescale_preferred(self): quantity.PREFERRED = [pq.mV, pq.pA] q = 10*pq.V @@ -23,7 +24,7 @@ def test_rescale_preferred(self): q = 5*pq.A self.assertQuantityEqual(q.rescale_preferred(), q.rescale(pq.pA)) quantity.PREFERRED = [] - + def test_rescale_preferred_failure(self): quantity.PREFERRED = [pq.pA] q = 10*pq.V @@ -34,7 +35,7 @@ def test_rescale_preferred_failure(self): else: self.assertTrue(False) quantity.PREFERRED = [] - + def test_rescale_noargs(self): quantity.PREFERRED = [pq.mV, pq.pA] q = 10*pq.V @@ -42,7 +43,7 @@ def test_rescale_noargs(self): q = 5*pq.A self.assertQuantityEqual(q.rescale(), q.rescale(pq.pA)) quantity.PREFERRED = [] - + def test_rescale_noargs_failure(self): quantity.PREFERRED = [pq.pA] q = 10*pq.V diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index f9ce372..0c5a9ff 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -188,7 +188,7 @@ def test_argmin(self): def test_nanargmax(self): q = np.append(self.q, np.nan) * self.q.units self.assertEqual(self.q.nanargmin(), 0) - + def test_ptp(self): self.methodWithOut('ptp', 3 * pq.m) self.methodWithOut('ptp', [2, 2] * pq.m, axis=0) @@ -248,7 +248,7 @@ def test_mean(self): self.methodWithOut('mean', [1.5, 3.5] * pq.m, axis=1) def test_nanmean(self): - import numpy as np + import numpy as np q = [[1,2], [3,4], [np.nan,np.nan]] * pq.m self.assertQuantityEqual(q.nanmean(), self.q.mean()) @@ -263,7 +263,7 @@ def test_std(self): self.methodWithOut('std', [0.5, 0.5] * pq.m, axis=1) def test_nanstd(self): - import numpy as np + import numpy as np q0 = [[1,2], [3,4]] * pq.m q1 = [[1,2], [3,4], [np.nan,np.nan]] * pq.m self.assertQuantityEqual(q0.std(), q1.nanstd()) @@ -345,3 +345,10 @@ def test_setitem (self): def test_iterator(self): for q in self.q.flatten(): self.assertQuantityEqual(q.units, pq.m) + + def test_rescale_integer_argument(self): + from .. import Quantity + self.assertQuantityEqual( + Quantity(10, pq.deg).rescale(pq.rad), + np.pi/18*pq.rad + ) From 8239f669ad1176cc02f6577e27f1493e1e5d51fb Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 17 Feb 2023 09:57:22 +0100 Subject: [PATCH 145/212] add more unit tests, including a regression test for gh-215 --- quantities/tests/test_arithmetic.py | 12 ++++++++++++ quantities/tests/test_conversion.py | 10 ++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/quantities/tests/test_arithmetic.py b/quantities/tests/test_arithmetic.py index 939e6ee..7f955b4 100644 --- a/quantities/tests/test_arithmetic.py +++ b/quantities/tests/test_arithmetic.py @@ -139,6 +139,12 @@ def test_truediv(self): Quantity([11, 10, 9, 8], units=pq.dimensionless) ) + q = Quantity([46, 42, 38, 34], units=pq.ms) + self.assertQuantityEqual( + q/(4 * pq.ms), + Quantity([11.5, 10.5, 9.5, 8.5], units=pq.dimensionless) + ) + def test_floordiv(self): q = Quantity([45, 43, 39, 32], units=pq.ms) self.assertQuantityEqual( @@ -146,6 +152,12 @@ def test_floordiv(self): Quantity([11, 10, 9, 8], units=pq.dimensionless) ) + q = Quantity([46, 42, 38, 34], units=pq.ms) + self.assertQuantityEqual( + q//(4 * pq.ms), + Quantity([11, 10, 9, 8], units=pq.dimensionless) + ) + def test_mixed_addition(self): self.assertQuantityEqual(1*pq.ft + 1*pq.m, 4.280839895 * pq.ft) self.assertQuantityEqual(1*pq.ft + pq.m, 4.280839895 * pq.ft) diff --git a/quantities/tests/test_conversion.py b/quantities/tests/test_conversion.py index ca82a34..d8010a3 100644 --- a/quantities/tests/test_conversion.py +++ b/quantities/tests/test_conversion.py @@ -15,7 +15,9 @@ def test_inplace_conversion(self): def test_rescale(self): for u in ('ft', 'feet', pq.ft): self.assertQuantityEqual((10*pq.m).rescale(u), 32.80839895 * pq.ft) - + self.assertQuantityEqual((10 * pq.deg).rescale(pq.rad), 0.17453293 * pq.rad) + self.assertQuantityEqual(quantity.Quantity(10, pq.deg).rescale(pq.rad), 0.17453293 * pq.rad) + def test_rescale_preferred(self): quantity.PREFERRED = [pq.mV, pq.pA] q = 10*pq.V @@ -23,7 +25,7 @@ def test_rescale_preferred(self): q = 5*pq.A self.assertQuantityEqual(q.rescale_preferred(), q.rescale(pq.pA)) quantity.PREFERRED = [] - + def test_rescale_preferred_failure(self): quantity.PREFERRED = [pq.pA] q = 10*pq.V @@ -34,7 +36,7 @@ def test_rescale_preferred_failure(self): else: self.assertTrue(False) quantity.PREFERRED = [] - + def test_rescale_noargs(self): quantity.PREFERRED = [pq.mV, pq.pA] q = 10*pq.V @@ -42,7 +44,7 @@ def test_rescale_noargs(self): q = 5*pq.A self.assertQuantityEqual(q.rescale(), q.rescale(pq.pA)) quantity.PREFERRED = [] - + def test_rescale_noargs_failure(self): quantity.PREFERRED = [pq.pA] q = 10*pq.V From 1fe4723e7eb190cca263b8b2869e4328de673bf8 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 17 Feb 2023 10:08:01 +0100 Subject: [PATCH 146/212] updated changelog [skip ci] --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index b31cc65..ea3b080 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,12 @@ CHANGES ======= +------ +0.14.1 +------ + +- Fixed a bug when scaling quantities with integer dtype [`PR#216 `_] + ------ 0.14.0 ------ From 1f65bb8626c8397611072599a9d1541b5fe4ab8a Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:55:34 +0200 Subject: [PATCH 147/212] Shared quantity types --- quantities/typing/__init__.py | 0 quantities/typing/quantities.pyi | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 quantities/typing/__init__.py create mode 100644 quantities/typing/quantities.pyi diff --git a/quantities/typing/__init__.py b/quantities/typing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quantities/typing/quantities.pyi b/quantities/typing/quantities.pyi new file mode 100644 index 0000000..1a82a8d --- /dev/null +++ b/quantities/typing/quantities.pyi @@ -0,0 +1,8 @@ +from typing import Union, Iterable + +from quantities import Quantity +from quantities.dimensionality import Dimensionality +import numpy.typing as npt + +DimensionalityDescriptor = Union[str, Quantity, Dimensionality] +QuantityData = Union[Quantity, npt.NDArray[Union[float,int]], Iterable[Union[float,int]], float, int] From 3ffd8cc51c5ec606777609bb3a7edd60081d8b87 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:55:57 +0200 Subject: [PATCH 148/212] Dimensionality type stub --- quantities/dimensionality.pyi | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 quantities/dimensionality.pyi diff --git a/quantities/dimensionality.pyi b/quantities/dimensionality.pyi new file mode 100644 index 0000000..6d3f459 --- /dev/null +++ b/quantities/dimensionality.pyi @@ -0,0 +1,84 @@ +class Dimensionality(dict): + @property + def ndims(self) -> int: + ... + + @property + def simplified(self) -> Dimensionality: + ... + + @property + def string(self) -> str: + ... + + @property + def unicode(self) -> str: + ... + + @property + def latex(self) -> str: + ... + + @property + def html(self) -> str: + ... + + def __hash__(self) -> int: + ... + + def __add__(self, other: Dimensionality) -> Dimensionality: + ... + + def __iadd__(self, other: Dimensionality) -> Dimensionality: + ... + + def __sub__(self, other: Dimensionality) -> Dimensionality: + ... + + def __isub__(self, other: Dimensionality) -> Dimensionality: + ... + + def __mul__(self, other: Dimensionality) -> Dimensionality: + ... + + def __imul__(self, other: Dimensionality) -> Dimensionality: + ... + + def __truediv__(self, other: Dimensionality) -> Dimensionality: + ... + + def __itruediv__(self, other: Dimensionality) -> Dimensionality: + ... + + def __pow__(self, other : Dimensionality) -> Dimensionality: + ... + + def __ipow__(self, other: Dimensionality) -> Dimensionality: + ... + + def __repr__(self) -> str: + ... + + def __str__(self) -> str: + ... + + def __eq__(self, other: object) -> bool: + ... + + def __ne__(self, other: object) -> bool: + ... + + def __gt__(self, other: Dimensionality) -> bool: + ... + + def __ge__(self, other: Dimensionality) -> bool: + ... + + def __lt__(self, other: Dimensionality) -> bool: + ... + + def __le__(self, other: Dimensionality)-> bool: + ... + + def copy(self) -> Dimensionality: + ... \ No newline at end of file From 0dc5b3e7a2fe94707869ec890c84350ba77445cf Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:56:10 +0200 Subject: [PATCH 149/212] Quantity type stub --- quantities/quantity.pyi | 109 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 quantities/quantity.pyi diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi new file mode 100644 index 0000000..0ef6962 --- /dev/null +++ b/quantities/quantity.pyi @@ -0,0 +1,109 @@ +from typing import Optional + +from quantities.dimensionality import Dimensionality +from quantities.typing.quantities import DimensionalityDescriptor, QuantityData +import numpy.typing as npt + +def validate_unit_quantity(value: Quantity) -> Quantity: + ... + +def validate_dimensionality(value: DimensionalityDescriptor) -> Dimensionality: + ... + +def get_conversion_factor(from_u: Quantity, to_u: Quantity) -> float: + ... + + +class Quantity(npt.NDArray): + + def __new__(cls, data: QuantityData, units: DimensionalityDescriptor = '', + dtype: Optional[object] = None, copy: bool = True) -> Quantity: + ... + + @property + def dimensionality(self) -> Dimensionality: + ... + + @property + def _reference(self) : + ... + + @property + def magnitude(self) -> npt.NDArray: + ... + + @property + def real(self) -> Quantity: + ... + + @property + def imag(self) -> Quantity: + ... + + @property + def units(self) -> Quantity: + ... + + def rescale(self, units: Optional[DimensionalityDescriptor] = None) -> Quantity: + ... + + def rescale_preferred(self) -> Quantity: + ... + + def __add__(self, other) -> Quantity: + ... + + def __radd__(self, other) -> Quantity: + ... + + def __iadd__(self, other) -> Quantity: + ... + + def __sub__(self, other) -> Quantity: + ... + + def __rsub__(self, other) -> Quantity: + ... + + def __isub__(self, other) -> Quantity: + ... + + def __mod__(self, other) -> Quantity: + ... + + def __imod__(self, other) -> Quantity: + ... + + def __imul__(self, other) -> Quantity: + ... + + def __rmul__(self, other) -> Quantity: + ... + + def __itruediv__(self, other) -> Quantity: + ... + + def __rtruediv__(self, other) -> Quantity: + ... + + def __pow__(self, power) -> Quantity: + ... + + def __ipow__(self, other) -> Quantity: + ... + + def __round__(self, decimals: int = 0) -> Quantity: + ... + + def __repr__(self) -> str: + ... + + def __str__(self) -> str: + ... + + def __getitem__(self, item: int) -> Quantity: + ... + + def __setitem__(self, key: int, value: QuantityData) -> None: + ... + From 75bbf028df3eadb2ec18b3341df5d102debd7984 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:56:32 +0200 Subject: [PATCH 150/212] UnitQuantity type stub --- quantities/unitquantity.pyi | 96 +++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 quantities/unitquantity.pyi diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi new file mode 100644 index 0000000..5f25159 --- /dev/null +++ b/quantities/unitquantity.pyi @@ -0,0 +1,96 @@ +from typing import Optional, Union, List + +from quantities import Quantity +from quantities.dimensionality import Dimensionality + + +class UnitQuantity(Quantity): + + def __new__( + cls, name: str, definition: Optional[Union[Quantity, float, int]] = None, symbol: Optional[str] = None, + u_symbol: Optional[str] = None, + aliases: List[str] = [], doc=None + ) -> UnitQuantity: + ... + + def __init__( + self, name: str, definition: Optional[Union[Quantity, float, int]] = None, symbol: Optional[str] = None, + u_symbol: Optional[str] = None, + aliases: List[str] = [], doc=None + ) -> None: + ... + + def __hash__(self) -> int: + ... + + @property + def _reference(self) -> UnitQuantity: + ... + + @property + def _dimensionality(self) -> Dimensionality: + ... + + @property + def name(self) -> str: + ... + + @property + def symbol(self) -> str: + ... + + @property + def u_symbol(self) -> str: + ... + + @property + def units(self) -> UnitQuantity: + ... + + def __repr__(self) -> str: + ... + + def __str__(self) -> str: + ... + + def __add__(self, other) -> Quantity: + ... + + def __radd__(self, other) -> Quantity: + ... + + def __sub__(self, other) -> Quantity: + ... + + def __rsub__(self, other) -> Quantity: + ... + + def __mod__(self, other) -> Quantity: + ... + + def __rmod__(self, other) -> Quantity: + ... + + def __mul__(self, other) -> Quantity: + ... + + def __rmul__(self, other) -> Quantity: + ... + + def __truediv__(self, other) -> Quantity: + ... + + def __rtruediv__(self, other) -> Quantity: + ... + + def __pow__(self, other) -> Quantity: + ... + + def __rpow__(self, other) -> Quantity: + ... + + class Dimensionless(UnitQuantity): + + @property + def _dimensionality(self) -> Dimensionality: + ... \ No newline at end of file From 5d46937761d3689f09a7bcd6cc582d224c89b830 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 11:56:44 +0200 Subject: [PATCH 151/212] Registry type stub --- quantities/registry.pyi | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 quantities/registry.pyi diff --git a/quantities/registry.pyi b/quantities/registry.pyi new file mode 100644 index 0000000..ad149cc --- /dev/null +++ b/quantities/registry.pyi @@ -0,0 +1,7 @@ +from quantities import UnitQuantity + + +class UnitRegistry: + + def __getitem__(self, item: str) -> UnitQuantity: + ... \ No newline at end of file From baf2552be21c974cf82f7f4ed4116735169ccca4 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Mon, 6 Jun 2022 12:01:07 +0200 Subject: [PATCH 152/212] EOF new lines --- quantities/dimensionality.pyi | 2 +- quantities/quantity.pyi | 1 - quantities/registry.pyi | 2 +- quantities/unitquantity.pyi | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/quantities/dimensionality.pyi b/quantities/dimensionality.pyi index 6d3f459..216b8cb 100644 --- a/quantities/dimensionality.pyi +++ b/quantities/dimensionality.pyi @@ -81,4 +81,4 @@ class Dimensionality(dict): ... def copy(self) -> Dimensionality: - ... \ No newline at end of file + ... diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 0ef6962..8bae3f6 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -106,4 +106,3 @@ class Quantity(npt.NDArray): def __setitem__(self, key: int, value: QuantityData) -> None: ... - diff --git a/quantities/registry.pyi b/quantities/registry.pyi index ad149cc..dd0d980 100644 --- a/quantities/registry.pyi +++ b/quantities/registry.pyi @@ -4,4 +4,4 @@ from quantities import UnitQuantity class UnitRegistry: def __getitem__(self, item: str) -> UnitQuantity: - ... \ No newline at end of file + ... diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index 5f25159..beb43dd 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -93,4 +93,4 @@ class UnitQuantity(Quantity): @property def _dimensionality(self) -> Dimensionality: - ... \ No newline at end of file + ... From e625f58f56b285d48c20a22a70fbbd6fafbb32d0 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Fri, 12 Aug 2022 16:35:28 +0200 Subject: [PATCH 153/212] fix wrong intendation --- quantities/unitquantity.pyi | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index beb43dd..e6fe7d5 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -89,8 +89,9 @@ class UnitQuantity(Quantity): def __rpow__(self, other) -> Quantity: ... - class Dimensionless(UnitQuantity): - @property - def _dimensionality(self) -> Dimensionality: - ... +class Dimensionless(UnitQuantity): + + @property + def _dimensionality(self) -> Dimensionality: + ... From 5f77627089d51dae6878bebf24e3452da3d005a8 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Fri, 12 Aug 2022 16:43:41 +0200 Subject: [PATCH 154/212] [mypy] fix missing attribute in quantity --- quantities/quantity.pyi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 8bae3f6..88f01e5 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Any from quantities.dimensionality import Dimensionality from quantities.typing.quantities import DimensionalityDescriptor, QuantityData @@ -13,6 +13,8 @@ def validate_dimensionality(value: DimensionalityDescriptor) -> Dimensionality: def get_conversion_factor(from_u: Quantity, to_u: Quantity) -> float: ... +def scale_other_units(f: Any) -> None: + ... class Quantity(npt.NDArray): From dd592f97dc7943544c09dc40a0d5929b299baa41 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Fri, 12 Aug 2022 16:45:56 +0200 Subject: [PATCH 155/212] [mypy] fix missing attribute in registry --- quantities/registry.pyi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quantities/registry.pyi b/quantities/registry.pyi index dd0d980..344585d 100644 --- a/quantities/registry.pyi +++ b/quantities/registry.pyi @@ -5,3 +5,5 @@ class UnitRegistry: def __getitem__(self, item: str) -> UnitQuantity: ... + +unit_registry: UnitRegistry \ No newline at end of file From 5a418ff0d5241c70edb7eae78146a849293c99a0 Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Fri, 12 Aug 2022 17:09:13 +0200 Subject: [PATCH 156/212] [mypy] unitquantity add stubs for irreducibleunits --- quantities/unitquantity.pyi | 148 ++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index e6fe7d5..efb9a72 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -5,6 +5,9 @@ from quantities.dimensionality import Dimensionality class UnitQuantity(Quantity): + _primary_order: int + _secondary_order: int + _reference_quantity: Optional[Quantity] def __new__( cls, name: str, definition: Optional[Union[Quantity, float, int]] = None, symbol: Optional[str] = None, @@ -90,8 +93,153 @@ class UnitQuantity(Quantity): ... +class IrreducibleUnit(UnitQuantity): + _default_unit: Optional[UnitQuantity] + + @property + def simplified(self) -> Quantity: + ... + + @classmethod + def get_default_unit(cls) -> Optional[UnitQuantity]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitQuantity]): + ... + + +class UnitMass(IrreducibleUnit): + _default_unit: Optional[UnitMass] + + @classmethod + def get_default_unit(cls) -> Optional[UnitMass]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitMass]): + ... + + +class UnitLength(IrreducibleUnit): + _default_unit: Optional[UnitLength] + + @classmethod + def get_default_unit(cls) -> Optional[UnitLength]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitLength]): + ... + + +class UnitTime(IrreducibleUnit): + _default_unit: Optional[UnitTime] + + @classmethod + def get_default_unit(cls) -> Optional[UnitTime]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitTime]): + ... + + +class UnitCurrent(IrreducibleUnit): + _default_unit: Optional[UnitCurrent] + + +@classmethod + + +def get_default_unit(cls) -> Optional[UnitCurrent]: + ... + + +@classmethod +def set_default_unit(cls, unit: Union[str, UnitCurrent]): + ... + + +class UnitLuminousIntensity(IrreducibleUnit): + _default_unit: Optional[UnitLuminousIntensity] + + @classmethod + def get_default_unit(cls) -> Optional[UnitLuminousIntensity]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitLuminousIntensity]): + ... + + +class UnitSubstance(IrreducibleUnit): + _default_unit: Optional[UnitSubstance] + + @classmethod + def get_default_unit(cls) -> Optional[UnitSubstance]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitSubstance]): + ... + + +class UnitTemperature(IrreducibleUnit): + _default_unit: Optional[UnitTemperature] + + @classmethod + def get_default_unit(cls) -> Optional[UnitTemperature]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitTemperature]): + ... + + +class UnitInformation(IrreducibleUnit): + _default_unit: Optional[UnitInformation] + + @classmethod + def get_default_unit(cls) -> Optional[UnitInformation]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitInformation]): + ... + + +class UnitCurrency(IrreducibleUnit): + _default_unit: Optional[UnitCurrency] + + @classmethod + def get_default_unit(cls) -> Optional[UnitCurrency]: + ... + + @classmethod + def set_default_unit(cls, unit: Union[str, UnitCurrency]): + ... + + +class CompoundUnit(UnitQuantity): + ... + + class Dimensionless(UnitQuantity): @property def _dimensionality(self) -> Dimensionality: ... + + +class UnitConstant(UnitQuantity): + ... + + +def set_default_units(system: Optional[str], currency: Optional[Union[str, UnitCurrency]], + current: Optional[Union[str, UnitCurrent]], information: Optional[Union[str, UnitInformation]], + length: Optional[Union[str, UnitLength]], + luminous_intensity: Optional[Union[str, UnitLuminousIntensity]], + mass: Optional[Union[str, UnitMass]], substance: Optional[Union[str, UnitSubstance]], + temperature: Optional[Union[str, UnitTemperature]], time: Optional[Union[str, UnitTime]]): + ... From 7723dde3b55360972413f021e3b5a4fea133729a Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Tue, 6 Dec 2022 14:06:09 +0100 Subject: [PATCH 157/212] [mypy] ignore types --- quantities/dimensionality.pyi | 3 +- quantities/quantity.pyi | 41 +++++++----- quantities/uncertainquantity.py | 2 +- quantities/unitquantity.py | 2 + quantities/unitquantity.pyi | 111 ++++++-------------------------- 5 files changed, 48 insertions(+), 111 deletions(-) diff --git a/quantities/dimensionality.pyi b/quantities/dimensionality.pyi index 216b8cb..aadee6d 100644 --- a/quantities/dimensionality.pyi +++ b/quantities/dimensionality.pyi @@ -23,7 +23,8 @@ class Dimensionality(dict): def html(self) -> str: ... - def __hash__(self) -> int: + + def __hash__(self) -> int: # type: ignore ... def __add__(self, other: Dimensionality) -> Dimensionality: diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 88f01e5..8841a87 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -4,22 +4,27 @@ from quantities.dimensionality import Dimensionality from quantities.typing.quantities import DimensionalityDescriptor, QuantityData import numpy.typing as npt -def validate_unit_quantity(value: Quantity) -> Quantity: + +def validate_unit_quantity(value: Quantity) -> Quantity: #type: ignore ... + def validate_dimensionality(value: DimensionalityDescriptor) -> Dimensionality: ... + def get_conversion_factor(from_u: Quantity, to_u: Quantity) -> float: ... + def scale_other_units(f: Any) -> None: ... + class Quantity(npt.NDArray): def __new__(cls, data: QuantityData, units: DimensionalityDescriptor = '', - dtype: Optional[object] = None, copy: bool = True) -> Quantity: + dtype: Optional[object] = None, copy: bool = True) -> Quantity: #type: ignore ... @property @@ -27,19 +32,19 @@ class Quantity(npt.NDArray): ... @property - def _reference(self) : + def _reference(self): ... @property def magnitude(self) -> npt.NDArray: ... - @property - def real(self) -> Quantity: + @property #type: ignore + def real(self) -> Quantity: #type: ignore ... - @property - def imag(self) -> Quantity: + @property #type: ignore + def imag(self) -> Quantity: #type: ignore ... @property @@ -61,13 +66,16 @@ class Quantity(npt.NDArray): def __iadd__(self, other) -> Quantity: ... - def __sub__(self, other) -> Quantity: + + def __sub__(self, other) -> Quantity: #type: ignore ... - def __rsub__(self, other) -> Quantity: + + def __rsub__(self, other) -> Quantity: #type: ignore ... - def __isub__(self, other) -> Quantity: + + def __isub__(self, other) -> Quantity: #type: ignore ... def __mod__(self, other) -> Quantity: @@ -76,16 +84,17 @@ class Quantity(npt.NDArray): def __imod__(self, other) -> Quantity: ... - def __imul__(self, other) -> Quantity: - ... + # def __imul__(self, other): + # ... def __rmul__(self, other) -> Quantity: ... - def __itruediv__(self, other) -> Quantity: - ... + # def __itruediv__(self, other) : + # ... + - def __rtruediv__(self, other) -> Quantity: + def __rtruediv__(self, other) -> Quantity: #type: ignore ... def __pow__(self, power) -> Quantity: @@ -103,7 +112,7 @@ class Quantity(npt.NDArray): def __str__(self) -> str: ... - def __getitem__(self, item: int) -> Quantity: + def __getitem__(self, item: Any) -> Quantity: ... def __setitem__(self, key: int, value: QuantityData) -> None: diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index b7658a6..ffa37cb 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -27,7 +27,7 @@ def __new__(cls, data, units='', uncertainty=None, dtype='d', copy=True): return ret - @Quantity.units.setter + @Quantity.units.setter #type: ignore def units(self, units): super()._set_units(units) self.uncertainty.units = self._dimensionality diff --git a/quantities/unitquantity.py b/quantities/unitquantity.py index 47d7529..d5487fc 100644 --- a/quantities/unitquantity.py +++ b/quantities/unitquantity.py @@ -498,3 +498,5 @@ def set_default_units( UnitSubstance.set_default_unit(substance) UnitTemperature.set_default_unit(temperature) UnitTime.set_default_unit(time) + + diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index efb9a72..db4779e 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -1,4 +1,4 @@ -from typing import Optional, Union, List +from typing import Optional, Union, List, Any, overload from quantities import Quantity from quantities.dimensionality import Dimensionality @@ -62,10 +62,11 @@ class UnitQuantity(Quantity): def __radd__(self, other) -> Quantity: ... - def __sub__(self, other) -> Quantity: + def __sub__(self, other) -> Any: ... - def __rsub__(self, other) -> Quantity: + + def __rsub__(self, other) -> Any: ... def __mod__(self, other) -> Quantity: @@ -80,10 +81,10 @@ class UnitQuantity(Quantity): def __rmul__(self, other) -> Quantity: ... - def __truediv__(self, other) -> Quantity: + def __truediv__(self, other) -> Any: ... - def __rtruediv__(self, other) -> Quantity: + def __rtruediv__(self, other) -> Any: ... def __pow__(self, other) -> Quantity: @@ -94,131 +95,54 @@ class UnitQuantity(Quantity): class IrreducibleUnit(UnitQuantity): - _default_unit: Optional[UnitQuantity] + _default_unit: Optional[Quantity] @property def simplified(self) -> Quantity: ... @classmethod - def get_default_unit(cls) -> Optional[UnitQuantity]: + def get_default_unit(cls) -> Optional[Quantity]: ... @classmethod - def set_default_unit(cls, unit: Union[str, UnitQuantity]): + def set_default_unit(cls, unit: Union[str, Quantity]): ... class UnitMass(IrreducibleUnit): - _default_unit: Optional[UnitMass] - - @classmethod - def get_default_unit(cls) -> Optional[UnitMass]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitMass]): - ... + ... class UnitLength(IrreducibleUnit): - _default_unit: Optional[UnitLength] - - @classmethod - def get_default_unit(cls) -> Optional[UnitLength]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitLength]): - ... + ... class UnitTime(IrreducibleUnit): - _default_unit: Optional[UnitTime] - - @classmethod - def get_default_unit(cls) -> Optional[UnitTime]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitTime]): - ... - - -class UnitCurrent(IrreducibleUnit): - _default_unit: Optional[UnitCurrent] - - -@classmethod - - -def get_default_unit(cls) -> Optional[UnitCurrent]: ... -@classmethod -def set_default_unit(cls, unit: Union[str, UnitCurrent]): +class UnitCurrent(IrreducibleUnit): ... - class UnitLuminousIntensity(IrreducibleUnit): - _default_unit: Optional[UnitLuminousIntensity] - - @classmethod - def get_default_unit(cls) -> Optional[UnitLuminousIntensity]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitLuminousIntensity]): - ... + ... class UnitSubstance(IrreducibleUnit): - _default_unit: Optional[UnitSubstance] - - @classmethod - def get_default_unit(cls) -> Optional[UnitSubstance]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitSubstance]): - ... + ... class UnitTemperature(IrreducibleUnit): - _default_unit: Optional[UnitTemperature] - - @classmethod - def get_default_unit(cls) -> Optional[UnitTemperature]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitTemperature]): - ... + ... class UnitInformation(IrreducibleUnit): - _default_unit: Optional[UnitInformation] - - @classmethod - def get_default_unit(cls) -> Optional[UnitInformation]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitInformation]): - ... + ... class UnitCurrency(IrreducibleUnit): - _default_unit: Optional[UnitCurrency] - - @classmethod - def get_default_unit(cls) -> Optional[UnitCurrency]: - ... - - @classmethod - def set_default_unit(cls, unit: Union[str, UnitCurrency]): - ... + ... class CompoundUnit(UnitQuantity): @@ -231,6 +155,7 @@ class Dimensionless(UnitQuantity): def _dimensionality(self) -> Dimensionality: ... +dimensionless: Dimensionless class UnitConstant(UnitQuantity): ... From 0e3f10affe121e95d95211f458c0a002928328dd Mon Sep 17 00:00:00 2001 From: Peter Konradi Date: Tue, 6 Dec 2022 14:06:37 +0100 Subject: [PATCH 158/212] generated annotations for units --- quantities/units/acceleration.pyi | 13 +++ quantities/units/angle.pyi | 41 ++++++++++ quantities/units/area.pyi | 18 +++++ quantities/units/compound.pyi | 3 + quantities/units/concentration.pyi | 9 +++ quantities/units/dimensionless.pyi | 7 ++ quantities/units/electromagnetism.pyi | 110 ++++++++++++++++++++++++++ quantities/units/energy.pyi | 40 ++++++++++ quantities/units/force.pyi | 22 ++++++ quantities/units/frequency.pyi | 14 ++++ quantities/units/heat.pyi | 6 ++ quantities/units/information.pyi | 58 ++++++++++++++ quantities/units/length.pyi | 71 +++++++++++++++++ quantities/units/mass.pyi | 52 ++++++++++++ quantities/units/power.pyi | 21 +++++ quantities/units/prefixes.pyi | 0 quantities/units/pressure.pyi | 58 ++++++++++++++ quantities/units/radiation.pyi | 16 ++++ quantities/units/substance.pyi | 6 ++ quantities/units/temperature.pyi | 15 ++++ quantities/units/time.pyi | 52 ++++++++++++ quantities/units/velocity.pyi | 8 ++ quantities/units/viscosity.pyi | 9 +++ quantities/units/volume.pyi | 78 ++++++++++++++++++ 24 files changed, 727 insertions(+) create mode 100644 quantities/units/acceleration.pyi create mode 100644 quantities/units/angle.pyi create mode 100644 quantities/units/area.pyi create mode 100644 quantities/units/compound.pyi create mode 100644 quantities/units/concentration.pyi create mode 100644 quantities/units/dimensionless.pyi create mode 100644 quantities/units/electromagnetism.pyi create mode 100644 quantities/units/energy.pyi create mode 100644 quantities/units/force.pyi create mode 100644 quantities/units/frequency.pyi create mode 100644 quantities/units/heat.pyi create mode 100644 quantities/units/information.pyi create mode 100644 quantities/units/length.pyi create mode 100644 quantities/units/mass.pyi create mode 100644 quantities/units/power.pyi create mode 100644 quantities/units/prefixes.pyi create mode 100644 quantities/units/pressure.pyi create mode 100644 quantities/units/radiation.pyi create mode 100644 quantities/units/substance.pyi create mode 100644 quantities/units/temperature.pyi create mode 100644 quantities/units/time.pyi create mode 100644 quantities/units/velocity.pyi create mode 100644 quantities/units/viscosity.pyi create mode 100644 quantities/units/volume.pyi diff --git a/quantities/units/acceleration.pyi b/quantities/units/acceleration.pyi new file mode 100644 index 0000000..20bdbb4 --- /dev/null +++ b/quantities/units/acceleration.pyi @@ -0,0 +1,13 @@ +from ..unitquantity import UnitQuantity + +standard_free_fall: UnitQuantity +gp: UnitQuantity +dynamic: UnitQuantity +geopotential: UnitQuantity +g_0: UnitQuantity +g_n: UnitQuantity +gravity: UnitQuantity +standard_gravity: UnitQuantity +gee: UnitQuantity +force: UnitQuantity +free_fall: UnitQuantity \ No newline at end of file diff --git a/quantities/units/angle.pyi b/quantities/units/angle.pyi new file mode 100644 index 0000000..de3f8c0 --- /dev/null +++ b/quantities/units/angle.pyi @@ -0,0 +1,41 @@ +from ..unitquantity import UnitQuantity + +rad: UnitQuantity +radian: UnitQuantity +radians: UnitQuantity +mrad: UnitQuantity +milliradian: UnitQuantity +urad: UnitQuantity +microradian: UnitQuantity +turn: UnitQuantity +revolution: UnitQuantity +cycle: UnitQuantity +turns: UnitQuantity +circle: UnitQuantity +circles: UnitQuantity +deg: UnitQuantity +degree: UnitQuantity +degrees: UnitQuantity +arcdeg: UnitQuantity +arcdegree: UnitQuantity +angular_degree: UnitQuantity +arcminute: UnitQuantity +arcmin: UnitQuantity +arc_minute: UnitQuantity +angular_minute: UnitQuantity +arcsecond: UnitQuantity +arcsec: UnitQuantity +arc_second: UnitQuantity +angular_second: UnitQuantity +grad: UnitQuantity +grade: UnitQuantity +degrees_north: UnitQuantity +degrees_N: UnitQuantity +degrees_east: UnitQuantity +degrees_E: UnitQuantity +degrees_west: UnitQuantity +degrees_W: UnitQuantity +degrees_true: UnitQuantity +degrees_T: UnitQuantity +sr: UnitQuantity +steradian: UnitQuantity diff --git a/quantities/units/area.pyi b/quantities/units/area.pyi new file mode 100644 index 0000000..34da5b7 --- /dev/null +++ b/quantities/units/area.pyi @@ -0,0 +1,18 @@ +from ..unitquantity import UnitQuantity + + +are: UnitQuantity +ares: UnitQuantity +b: UnitQuantity +barn: UnitQuantity +cmil: UnitQuantity +circular_mil: UnitQuantity +D: UnitQuantity +darcy: UnitQuantity +mD: UnitQuantity +millidarcy: UnitQuantity +ha: UnitQuantity +hectare: UnitQuantity +acre: UnitQuantity +international_acre: UnitQuantity +US_survey_acre: UnitQuantity diff --git a/quantities/units/compound.pyi b/quantities/units/compound.pyi new file mode 100644 index 0000000..4a8e0e6 --- /dev/null +++ b/quantities/units/compound.pyi @@ -0,0 +1,3 @@ +from ..unitquantity import CompoundUnit + +pc_per_cc: CompoundUnit diff --git a/quantities/units/concentration.pyi b/quantities/units/concentration.pyi new file mode 100644 index 0000000..f82f37f --- /dev/null +++ b/quantities/units/concentration.pyi @@ -0,0 +1,9 @@ +from ..unitquantity import UnitQuantity + + +M: UnitQuantity +molar: UnitQuantity +mM: UnitQuantity +millimolar: UnitQuantity +uM: UnitQuantity +micromolar: UnitQuantity diff --git a/quantities/units/dimensionless.pyi b/quantities/units/dimensionless.pyi new file mode 100644 index 0000000..a18340f --- /dev/null +++ b/quantities/units/dimensionless.pyi @@ -0,0 +1,7 @@ +from ..unitquantity import UnitQuantity + + +percent: UnitQuantity +count: UnitQuantity +counts: UnitQuantity +lsb: UnitQuantity diff --git a/quantities/units/electromagnetism.pyi b/quantities/units/electromagnetism.pyi new file mode 100644 index 0000000..bdc6ddb --- /dev/null +++ b/quantities/units/electromagnetism.pyi @@ -0,0 +1,110 @@ +from ..unitquantity import UnitCurrent, UnitLuminousIntensity, UnitQuantity + +A: UnitCurrent +amp: UnitCurrent +amps: UnitCurrent +ampere: UnitCurrent +amperes: UnitCurrent +mA: UnitCurrent +milliamp: UnitCurrent +milliampere: UnitCurrent +uA: UnitCurrent +microampere: UnitCurrent +nA: UnitCurrent +nanoamp: UnitCurrent +nanoampere: UnitCurrent +pA: UnitCurrent +picoamp: UnitCurrent +picoampere: UnitCurrent +aA: UnitCurrent +abampere: UnitCurrent +biot: UnitCurrent +esu: UnitQuantity +statcoulomb: UnitQuantity +statC: UnitQuantity +franklin: UnitQuantity +Fr: UnitQuantity +esu_per_second: UnitCurrent +statampere: UnitCurrent +ampere_turn: UnitQuantity +Gi: UnitQuantity +gilbert: UnitQuantity +C: UnitQuantity +coulomb: UnitQuantity +mC: UnitQuantity +millicoulomb: UnitQuantity +uC: UnitQuantity +microcoulomb: UnitQuantity +V: UnitQuantity +volt: UnitQuantity +kV: UnitQuantity +kilovolt: UnitQuantity +mV: UnitQuantity +millivolt: UnitQuantity +uV: UnitQuantity +microvolt: UnitQuantity +F: UnitQuantity +farad: UnitQuantity +mF: UnitQuantity +uF: UnitQuantity +nF: UnitQuantity +pF: UnitQuantity +ohm: UnitQuantity +Ohm: UnitQuantity +kOhm: UnitQuantity +MOhm: UnitQuantity +S: UnitQuantity +siemens: UnitQuantity +mS: UnitQuantity +millisiemens: UnitQuantity +uS: UnitQuantity +microsiemens: UnitQuantity +nS: UnitQuantity +nanosiemens: UnitQuantity +pS: UnitQuantity +picosiemens: UnitQuantity +Wb: UnitQuantity +weber: UnitQuantity +T: UnitQuantity +tesla: UnitQuantity +H: UnitQuantity +henry: UnitQuantity +abfarad: UnitQuantity +abhenry: UnitQuantity +abmho: UnitQuantity +abohm: UnitQuantity +abvolt: UnitQuantity +e: UnitQuantity +elementary_charge: UnitQuantity +chemical_faraday: UnitQuantity +physical_faraday: UnitQuantity +faraday: UnitQuantity +C12_faraday: UnitQuantity +gamma: UnitQuantity +gauss: UnitQuantity +maxwell: UnitQuantity +Oe: UnitQuantity +oersted: UnitQuantity +statfarad: UnitQuantity +statF: UnitQuantity +stF: UnitQuantity +stathenry: UnitQuantity +statH: UnitQuantity +stH: UnitQuantity +statmho: UnitQuantity +statS: UnitQuantity +stS: UnitQuantity +statohm: UnitQuantity +statvolt: UnitQuantity +statV: UnitQuantity +stV: UnitQuantity +unit_pole: UnitQuantity +vacuum_permeability: UnitQuantity +mu_0: UnitQuantity +magnetic_constant: UnitQuantity +vacuum_permittivity: UnitQuantity +epsilon_0: UnitQuantity +electric_constant: UnitQuantity +cd: UnitLuminousIntensity +candle: UnitLuminousIntensity +candela: UnitLuminousIntensity diff --git a/quantities/units/energy.pyi b/quantities/units/energy.pyi new file mode 100644 index 0000000..e811e97 --- /dev/null +++ b/quantities/units/energy.pyi @@ -0,0 +1,40 @@ +from ..unitquantity import UnitQuantity + +J: UnitQuantity +joule: UnitQuantity +erg: UnitQuantity +btu: UnitQuantity +Btu: UnitQuantity +BTU: UnitQuantity +british_thermal_unit: UnitQuantity +eV: UnitQuantity +electron_volt: UnitQuantity +meV: UnitQuantity +keV: UnitQuantity +MeV: UnitQuantity +bev: UnitQuantity +GeV: UnitQuantity +thm: UnitQuantity +therm: UnitQuantity +EC_therm: UnitQuantity +cal: UnitQuantity +calorie: UnitQuantity +thermochemical_calorie: UnitQuantity +international_steam_table_calorie: UnitQuantity +ton_TNT: UnitQuantity +US_therm: UnitQuantity +Wh: UnitQuantity +watthour: UnitQuantity +watt_hour: UnitQuantity +kWh: UnitQuantity +kilowatthour: UnitQuantity +kilowatt_hour: UnitQuantity +MWh: UnitQuantity +megawatthour: UnitQuantity +megawatt_hour: UnitQuantity +GWh: UnitQuantity +gigawatthour: UnitQuantity +gigawatt_hour: UnitQuantity +E_h: UnitQuantity +hartree: UnitQuantity +hartree_energy: UnitQuantity diff --git a/quantities/units/force.pyi b/quantities/units/force.pyi new file mode 100644 index 0000000..46062b7 --- /dev/null +++ b/quantities/units/force.pyi @@ -0,0 +1,22 @@ +from ..unitquantity import UnitQuantity + +N: UnitQuantity +newton: UnitQuantity +dyne: UnitQuantity +pond: UnitQuantity +kgf: UnitQuantity +force_kilogram: UnitQuantity +kilogram_force: UnitQuantity +ozf: UnitQuantity +force_ounce: UnitQuantity +ounce_force: UnitQuantity +lbf: UnitQuantity +force_pound: UnitQuantity +pound_force: UnitQuantity +poundal: UnitQuantity +gf: UnitQuantity +gram_force: UnitQuantity +force_gram: UnitQuantity +force_ton: UnitQuantity +ton_force: UnitQuantity +kip: UnitQuantity diff --git a/quantities/units/frequency.pyi b/quantities/units/frequency.pyi new file mode 100644 index 0000000..3f9bb67 --- /dev/null +++ b/quantities/units/frequency.pyi @@ -0,0 +1,14 @@ +from ..unitquantity import UnitQuantity + +Hz: UnitQuantity +hertz: UnitQuantity +rps: UnitQuantity +kHz: UnitQuantity +kilohertz: UnitQuantity +MHz: UnitQuantity +megahertz: UnitQuantity +GHz: UnitQuantity +gigahertz: UnitQuantity +rpm: UnitQuantity +revolutions_per_minute: UnitQuantity +cps: UnitQuantity diff --git a/quantities/units/heat.pyi b/quantities/units/heat.pyi new file mode 100644 index 0000000..9ed86a7 --- /dev/null +++ b/quantities/units/heat.pyi @@ -0,0 +1,6 @@ +from ..unitquantity import UnitQuantity + +RSI: UnitQuantity +clo: UnitQuantity +clos: UnitQuantity +R_value: UnitQuantity diff --git a/quantities/units/information.pyi b/quantities/units/information.pyi new file mode 100644 index 0000000..423243f --- /dev/null +++ b/quantities/units/information.pyi @@ -0,0 +1,58 @@ +from ..unitquantity import UnitQuantity, UnitInformation + +bit: UnitInformation +B: UnitInformation +byte: UnitInformation +o: UnitInformation +octet: UnitInformation +kB: UnitInformation +kilobyte: UnitInformation +ko: UnitInformation +MB: UnitInformation +megabyte: UnitInformation +Mo: UnitInformation +GB: UnitInformation +gigabyte: UnitInformation +Go: UnitInformation +TB: UnitInformation +terabyte: UnitInformation +To: UnitInformation +PB: UnitInformation +petabyte: UnitInformation +Po: UnitInformation +EB: UnitInformation +exabyte: UnitInformation +Eo: UnitInformation +ZB: UnitInformation +zettabyte: UnitInformation +Zo: UnitInformation +YB: UnitInformation +yottabyte: UnitInformation +Yo: UnitInformation +Bd: UnitQuantity +baud: UnitQuantity +bps: UnitQuantity +KiB: UnitInformation +kibibyte: UnitInformation +Kio: UnitInformation +MiB: UnitInformation +mebibyte: UnitInformation +Mio: UnitInformation +GiB: UnitInformation +gibibyte: UnitInformation +Gio: UnitInformation +TiB: UnitInformation +tebibyte: UnitInformation +Tio: UnitInformation +PiB: UnitInformation +pebibyte: UnitInformation +Pio: UnitInformation +EiB: UnitInformation +exbibyte: UnitInformation +Eio: UnitInformation +ZiB: UnitInformation +zebibyte: UnitInformation +Zio: UnitInformation +YiB: UnitInformation +yobibyte: UnitInformation +Yio: UnitInformation diff --git a/quantities/units/length.pyi b/quantities/units/length.pyi new file mode 100644 index 0000000..94a4f00 --- /dev/null +++ b/quantities/units/length.pyi @@ -0,0 +1,71 @@ +from ..unitquantity import UnitLength, UnitQuantity + +m: UnitLength +meter: UnitLength +metre: UnitLength +km: UnitLength +kilometer: UnitLength +kilometre: UnitLength +dm: UnitLength +decimeter: UnitLength +decimetre: UnitLength +cm: UnitLength +centimeter: UnitLength +centimetre: UnitLength +mm: UnitLength +millimeter: UnitLength +millimetre: UnitLength +um: UnitLength +micrometer: UnitLength +micrometre: UnitLength +micron: UnitLength +nm: UnitLength +nanometer: UnitLength +nanometre: UnitLength +pm: UnitLength +picometer: UnitLength +picometre: UnitLength +angstrom: UnitLength +fm: UnitLength +femtometer: UnitLength +femtometre: UnitLength +fermi: UnitLength +inch: UnitLength +international_inch: UnitLength +ft: UnitLength +foot: UnitLength +international_foot: UnitLength +mi: UnitLength +mile: UnitLength +international_mile: UnitLength +yd: UnitLength +yard: UnitLength +international_yard: UnitLength +mil: UnitLength +thou: UnitLength +pc: UnitLength +parsec: UnitLength +ly: UnitLength +light_year: UnitLength +au: UnitLength +astronomical_unit: UnitLength +nmi: UnitLength +nautical_mile: UnitLength +pt: UnitLength +printers_point: UnitLength +point: UnitLength +pica: UnitLength +US_survey_foot: UnitLength +US_survey_yard: UnitLength +US_survey_mile: UnitLength +US_statute_mile: UnitLength +rod: UnitLength +pole: UnitLength +perch: UnitLength +furlong: UnitLength +fathom: UnitLength +chain: UnitLength +barleycorn: UnitLength +arpentlin: UnitLength +kayser: UnitQuantity +wavenumber: UnitQuantity diff --git a/quantities/units/mass.pyi b/quantities/units/mass.pyi new file mode 100644 index 0000000..6917562 --- /dev/null +++ b/quantities/units/mass.pyi @@ -0,0 +1,52 @@ +from ..unitquantity import UnitQuantity, UnitMass + +kg: UnitMass +kilogram: UnitMass +g: UnitMass +gram: UnitMass +mg: UnitMass +milligram: UnitMass +oz: UnitMass +ounce: UnitMass +avoirdupois_ounce: UnitMass +lb: UnitMass +pound: UnitMass +avoirdupois_pound: UnitMass +st: UnitMass +stone: UnitMass +carat: UnitMass +gr: UnitMass +grain: UnitMass +long_hundredweight: UnitMass +short_hundredweight: UnitMass +t: UnitMass +metric_ton: UnitMass +tonne: UnitMass +dwt: UnitMass +pennyweight: UnitMass +slug: UnitMass +slugs: UnitMass +toz: UnitMass +troy_ounce: UnitMass +apounce: UnitMass +apothecary_ounce: UnitMass +troy_pound: UnitMass +appound: UnitMass +apothecary_pound: UnitMass +u: UnitMass +amu: UnitMass +atomic_mass_unit: UnitMass +dalton: UnitMass +Da: UnitMass +scruple: UnitMass +dr: UnitMass +dram: UnitMass +drachm: UnitMass +apdram: UnitMass +bag: UnitMass +ton: UnitMass +short_ton: UnitMass +long_ton: UnitMass +denier: UnitQuantity +tex: UnitQuantity +dtex: UnitQuantity diff --git a/quantities/units/power.pyi b/quantities/units/power.pyi new file mode 100644 index 0000000..b4d6b31 --- /dev/null +++ b/quantities/units/power.pyi @@ -0,0 +1,21 @@ +from ..unitquantity import UnitQuantity + +W: UnitQuantity +watt: UnitQuantity +volt_ampere: UnitQuantity +mW: UnitQuantity +milliwatt: UnitQuantity +kW: UnitQuantity +kilowatt: UnitQuantity +MW: UnitQuantity +megawatt: UnitQuantity +hp: UnitQuantity +horsepower: UnitQuantity +UK_horsepower: UnitQuantity +British_horsepower: UnitQuantity +boiler_horsepower: UnitQuantity +metric_horsepower: UnitQuantity +electric_horsepower: UnitQuantity +water_horsepower: UnitQuantity +refrigeration_ton: UnitQuantity +ton_of_refrigeration: UnitQuantity diff --git a/quantities/units/prefixes.pyi b/quantities/units/prefixes.pyi new file mode 100644 index 0000000..e69de29 diff --git a/quantities/units/pressure.pyi b/quantities/units/pressure.pyi new file mode 100644 index 0000000..5eadcde --- /dev/null +++ b/quantities/units/pressure.pyi @@ -0,0 +1,58 @@ +from ..unitquantity import UnitQuantity + +Hg: UnitQuantity +mercury: UnitQuantity +conventional_mercury: UnitQuantity +Pa: UnitQuantity +pascal: UnitQuantity +hPa: UnitQuantity +hectopascal: UnitQuantity +kPa: UnitQuantity +kilopascal: UnitQuantity +MPa: UnitQuantity +megapascal: UnitQuantity +GPa: UnitQuantity +gigapascal: UnitQuantity +bar: UnitQuantity +mbar: UnitQuantity +millibar: UnitQuantity +kbar: UnitQuantity +kilobar: UnitQuantity +Mbar: UnitQuantity +megabar: UnitQuantity +Gbar: UnitQuantity +gigabar: UnitQuantity +atm: UnitQuantity +atmosphere: UnitQuantity +standard_atmosphere: UnitQuantity +at: UnitQuantity +technical_atmosphere: UnitQuantity +torr: UnitQuantity +psi: UnitQuantity +pound_force_per_square_inch: UnitQuantity +ksi: UnitQuantity +kip_per_square_inch: UnitQuantity +barye: UnitQuantity +barie: UnitQuantity +barad: UnitQuantity +barad: UnitQuantity +barrie: UnitQuantity +baryd: UnitQuantity +mmHg: UnitQuantity +mm_Hg: UnitQuantity +millimeter_Hg: UnitQuantity +millimeter_Hg_0C: UnitQuantity +cmHg: UnitQuantity +cm_Hg: UnitQuantity +centimeter_Hg: UnitQuantity +inHg: UnitQuantity +in_Hg: UnitQuantity +inch_Hg: UnitQuantity +inch_Hg_32F: UnitQuantity +inch_Hg_60F: UnitQuantity +inch_H2O_39F: UnitQuantity +inch_H2O_60F: UnitQuantity +footH2O: UnitQuantity +cmH2O: UnitQuantity +foot_H2O: UnitQuantity +ftH2O: UnitQuantity diff --git a/quantities/units/radiation.pyi b/quantities/units/radiation.pyi new file mode 100644 index 0000000..21b271f --- /dev/null +++ b/quantities/units/radiation.pyi @@ -0,0 +1,16 @@ +from ..unitquantity import UnitQuantity + +Bq: UnitQuantity +becquerel: UnitQuantity +Ci: UnitQuantity +curie: UnitQuantity +rd: UnitQuantity +rutherford: UnitQuantity +Gy: UnitQuantity +gray: UnitQuantity +Sv: UnitQuantity +sievert: UnitQuantity +rem: UnitQuantity +rads: UnitQuantity +R: UnitQuantity +roentgen: UnitQuantity diff --git a/quantities/units/substance.pyi b/quantities/units/substance.pyi new file mode 100644 index 0000000..5c06c12 --- /dev/null +++ b/quantities/units/substance.pyi @@ -0,0 +1,6 @@ +from ..unitquantity import UnitSubstance + +mol: UnitSubstance +mole: UnitSubstance +mmol: UnitSubstance +umol: UnitSubstance diff --git a/quantities/units/temperature.pyi b/quantities/units/temperature.pyi new file mode 100644 index 0000000..2a79414 --- /dev/null +++ b/quantities/units/temperature.pyi @@ -0,0 +1,15 @@ +from ..unitquantity import UnitTemperature + +K: UnitTemperature +degK: UnitTemperature +kelvin: UnitTemperature +Kelvin: UnitTemperature +degR: UnitTemperature +rankine: UnitTemperature +Rankine: UnitTemperature +degC: UnitTemperature +celsius: UnitTemperature +Celsius: UnitTemperature +degF: UnitTemperature +fahrenheit: UnitTemperature +Fahrenheit: UnitTemperature diff --git a/quantities/units/time.pyi b/quantities/units/time.pyi new file mode 100644 index 0000000..d581a7a --- /dev/null +++ b/quantities/units/time.pyi @@ -0,0 +1,52 @@ +from ..unitquantity import UnitQuantity, UnitTime + +s: UnitTime +sec: UnitTime +second: UnitTime +ks: UnitTime +kilosecond: UnitTime +Ms: UnitTime +megasecond: UnitTime +ms: UnitTime +millisecond: UnitTime +us: UnitTime +microsecond: UnitTime +ns: UnitTime +nanosecond: UnitTime +ps: UnitTime +picosecond: UnitTime +fs: UnitTime +femtosecond: UnitTime +attosecond: UnitTime +min: UnitTime +minute: UnitTime +h: UnitTime +hr: UnitTime +hour: UnitTime +d: UnitTime +day: UnitTime +week: UnitTime +fortnight: UnitTime +yr: UnitTime +year: UnitTime +tropical_year: UnitTime +a: UnitTime +month: UnitTime +shake: UnitTime +sidereal_day: UnitTime +sidereal_hour: UnitTime +sidereal_minute: UnitTime +sidereal_second: UnitTime +sidereal_year: UnitTime +sidereal_month: UnitTime +tropical_month: UnitTime +synodic_month: UnitTime +lunar_month: UnitTime +common_year: UnitTime +leap_year: UnitTime +Julian_year: UnitTime +Gregorian_year: UnitTime +millenium: UnitTime +eon: UnitTime +work_year: UnitQuantity +work_month: UnitQuantity diff --git a/quantities/units/velocity.pyi b/quantities/units/velocity.pyi new file mode 100644 index 0000000..3d65bcc --- /dev/null +++ b/quantities/units/velocity.pyi @@ -0,0 +1,8 @@ +from ..unitquantity import UnitQuantity + +c: UnitQuantity +speed_of_light: UnitQuantity +kt: UnitQuantity +knot: UnitQuantity +knot_international: UnitQuantity +international_knot: UnitQuantity diff --git a/quantities/units/viscosity.pyi b/quantities/units/viscosity.pyi new file mode 100644 index 0000000..9290be4 --- /dev/null +++ b/quantities/units/viscosity.pyi @@ -0,0 +1,9 @@ +from ..unitquantity import UnitQuantity + +P: UnitQuantity +poise: UnitQuantity +cP: UnitQuantity +centipoise: UnitQuantity +St: UnitQuantity +stokes: UnitQuantity +rhe: UnitQuantity diff --git a/quantities/units/volume.pyi b/quantities/units/volume.pyi new file mode 100644 index 0000000..ad241b3 --- /dev/null +++ b/quantities/units/volume.pyi @@ -0,0 +1,78 @@ +from ..unitquantity import UnitQuantity + +l: UnitQuantity +L: UnitQuantity +liter: UnitQuantity +litre: UnitQuantity +mL: UnitQuantity +milliliter: UnitQuantity +millilitre: UnitQuantity +kL: UnitQuantity +kiloliter: UnitQuantity +kilolitre: UnitQuantity +ML: UnitQuantity +megaliter: UnitQuantity +megalitre: UnitQuantity +GL: UnitQuantity +gigaliter: UnitQuantity +gigalitre: UnitQuantity +cc: UnitQuantity +cubic_centimeter: UnitQuantity +milliliter: UnitQuantity +stere: UnitQuantity +gross_register_ton: UnitQuantity +register_ton: UnitQuantity +acre_foot: UnitQuantity +board_foot: UnitQuantity +bu: UnitQuantity +bushel: UnitQuantity +US_bushel: UnitQuantity +US_dry_gallon: UnitQuantity +gallon: UnitQuantity +liquid_gallon: UnitQuantity +US_liquid_gallon: UnitQuantity +dry_quart: UnitQuantity +US_dry_quart: UnitQuantity +dry_pint: UnitQuantity +US_dry_pint: UnitQuantity +quart: UnitQuantity +liquid_quart: UnitQuantity +US_liquid_quart: UnitQuantity +pt: UnitQuantity +pint: UnitQuantity +liquid_pint: UnitQuantity +US_liquid_pint: UnitQuantity +cup: UnitQuantity +US_liquid_cup: UnitQuantity +gill: UnitQuantity +US_liquid_gill: UnitQuantity +floz: UnitQuantity +fluid_ounce: UnitQuantity +US_fluid_ounce: UnitQuantity +US_liquid_ounce: UnitQuantity +Imperial_bushel: UnitQuantity +UK_liquid_gallon: UnitQuantity +Canadian_liquid_gallon: UnitQuantity +UK_liquid_quart: UnitQuantity +UK_liquid_pint: UnitQuantity +UK_liquid_cup: UnitQuantity +UK_liquid_gill: UnitQuantity +UK_fluid_ounce: UnitQuantity +UK_liquid_ounce: UnitQuantity +bbl: UnitQuantity +barrel: UnitQuantity +tbsp: UnitQuantity +Tbsp: UnitQuantity +Tblsp: UnitQuantity +tblsp: UnitQuantity +tbs: UnitQuantity +Tbl: UnitQuantity +tablespoon: UnitQuantity +tsp: UnitQuantity +teaspoon: UnitQuantity +pk: UnitQuantity +peck: UnitQuantity +fldr: UnitQuantity +fluid_dram: UnitQuantity +fluidram: UnitQuantity +firkin: UnitQuantity From 31ba87d7e80cd0190e22fbb9883c26b8f5c4ea52 Mon Sep 17 00:00:00 2001 From: Takumasa Nakamura Date: Fri, 24 Mar 2023 13:18:05 +0900 Subject: [PATCH 159/212] [units] fix duplicated definisions * quantities.units.puressure.barad * quantities.units.volume.milliliter --- quantities/units/pressure.py | 2 +- quantities/units/pressure.pyi | 1 - quantities/units/volume.py | 4 ++-- quantities/units/volume.pyi | 1 - 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/quantities/units/pressure.py b/quantities/units/pressure.py index fd6daf8..12f5434 100644 --- a/quantities/units/pressure.py +++ b/quantities/units/pressure.py @@ -101,7 +101,7 @@ kip/inch**2, symbol='ksi' ) -barye = barie = barad = barad = barrie = baryd = UnitQuantity( +barye = barie = barad = barrie = baryd = UnitQuantity( 'barye', 0.1*N/m**2, symbol='Ba', diff --git a/quantities/units/pressure.pyi b/quantities/units/pressure.pyi index 5eadcde..67b5cd7 100644 --- a/quantities/units/pressure.pyi +++ b/quantities/units/pressure.pyi @@ -35,7 +35,6 @@ kip_per_square_inch: UnitQuantity barye: UnitQuantity barie: UnitQuantity barad: UnitQuantity -barad: UnitQuantity barrie: UnitQuantity baryd: UnitQuantity mmHg: UnitQuantity diff --git a/quantities/units/volume.py b/quantities/units/volume.py index 56ec30c..a8ee741 100644 --- a/quantities/units/volume.py +++ b/quantities/units/volume.py @@ -35,7 +35,7 @@ symbol='GL', aliases=['Gl', 'gigaliters', 'gigalitre', 'gigalitres'] ) -cc = cubic_centimeter = milliliter = UnitQuantity( +cc = cubic_centimeter = UnitQuantity( 'cubic_centimeter', cm**3, symbol='cc', @@ -203,4 +203,4 @@ barrel/4 ) -del UnitQuantity, cm, m +del UnitQuantity, cm, m, foot, inch, acre diff --git a/quantities/units/volume.pyi b/quantities/units/volume.pyi index ad241b3..8e1fc05 100644 --- a/quantities/units/volume.pyi +++ b/quantities/units/volume.pyi @@ -18,7 +18,6 @@ gigaliter: UnitQuantity gigalitre: UnitQuantity cc: UnitQuantity cubic_centimeter: UnitQuantity -milliliter: UnitQuantity stere: UnitQuantity gross_register_ton: UnitQuantity register_ton: UnitQuantity From 906eb4f2bfe92bf1a6b34479ec30cb47522ba1ae Mon Sep 17 00:00:00 2001 From: Takumasa Nakamura Date: Fri, 24 Mar 2023 13:23:19 +0900 Subject: [PATCH 160/212] [mypy] fix * use quantities.umath.__all__ * ignore UnitQuantity.__hash__ * fix aliases as list[str] in golden_ratio (but not used) * dtype of npt.NDArray must be generic --- quantities/constants/mathematical.py | 2 +- quantities/typing/quantities.pyi | 3 ++- quantities/umath.py | 33 ++++++++++++++++++++++++---- quantities/unitquantity.pyi | 2 +- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/quantities/constants/mathematical.py b/quantities/constants/mathematical.py index abaff18..2c97069 100644 --- a/quantities/constants/mathematical.py +++ b/quantities/constants/mathematical.py @@ -12,5 +12,5 @@ 'golden_ratio', (1 + _math.sqrt(5)) / 2, u_symbol='ϕ', - aliases='golden' + aliases=['golden'] ) diff --git a/quantities/typing/quantities.pyi b/quantities/typing/quantities.pyi index 1a82a8d..5d595a3 100644 --- a/quantities/typing/quantities.pyi +++ b/quantities/typing/quantities.pyi @@ -2,7 +2,8 @@ from typing import Union, Iterable from quantities import Quantity from quantities.dimensionality import Dimensionality +import numpy as np import numpy.typing as npt DimensionalityDescriptor = Union[str, Quantity, Dimensionality] -QuantityData = Union[Quantity, npt.NDArray[Union[float,int]], Iterable[Union[float,int]], float, int] +QuantityData = Union[Quantity, npt.NDArray[Union[np.floating, np.integer]], Iterable[Union[float, int]], float, int] diff --git a/quantities/umath.py b/quantities/umath.py index 45f06a3..0f87070 100644 --- a/quantities/umath.py +++ b/quantities/umath.py @@ -1,13 +1,38 @@ import numpy as np from .quantity import Quantity -from .units import dimensionless, radian, degree +from .units import dimensionless, radian, degree # type: ignore[no-redef] from .decorators import with_doc -#__all__ = [ -# 'exp', 'expm1', 'log', 'log10', 'log1p', 'log2' -#] +__all__ = [ + "arccos", + "arccosh", + "arcsin", + "arcsinh", + "arctan", + "arctan2", + "arctanh", + "cos", + "cosh", + "cross", + "cumprod", + "cumsum", + "diff", + "ediff1d", + "gradient", + "hypot", + "nansum", + "np", + "prod", + "sin", + "sinh", + "sum", + "tan", + "tanh", + "trapz", + "unwrap", +] @with_doc(np.prod) diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index db4779e..dabab64 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -23,7 +23,7 @@ class UnitQuantity(Quantity): ) -> None: ... - def __hash__(self) -> int: + def __hash__(self) -> int: # type: ignore[override] ... @property From 9118678e3fff62dd20c56a21b1745f57b28a4a43 Mon Sep 17 00:00:00 2001 From: Takumasa Nakamura Date: Fri, 24 Mar 2023 13:24:08 +0900 Subject: [PATCH 161/212] [mypy] shadow acceleration.force, volume.pt --- quantities/units/__init__.py | 60 +++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/quantities/units/__init__.py b/quantities/units/__init__.py index 4933a55..f489145 100644 --- a/quantities/units/__init__.py +++ b/quantities/units/__init__.py @@ -6,7 +6,19 @@ from .prefixes import * from . import acceleration -from .acceleration import * +from .acceleration import ( + g_0, + g_n, + gravity, + standard_gravity, + gee, + # force, + free_fall, + standard_free_fall, + gp, + dynamic, + geopotential, +) from . import angle from .angle import * @@ -20,8 +32,12 @@ from . import concentration from .concentration import * -from . import dimensionless -from .dimensionless import * +from . import dimensionless as _dimensionless +from .dimensionless import ( + percent, + count, counts, + lsb, +) from . import electromagnetism from .electromagnetism import * @@ -72,6 +88,42 @@ from .viscosity import * from . import volume -from .volume import * +from .volume import ( + l, L, liter, litre, + mL, milliliter, millilitre, + kL, kiloliter, kilolitre, + ML, megaliter, megalitre, + GL, gigaliter, gigalitre, + cc, cubic_centimeter, milliliter, + stere, + gross_register_ton, register_ton, + acre_foot, + board_foot, + bu, bushel, US_bushel, + US_dry_gallon, + gallon, liquid_gallon, US_liquid_gallon, + dry_quart, US_dry_quart, + dry_pint, US_dry_pint, + quart, liquid_quart, US_liquid_quart, + # pt, + pint, liquid_pint, US_liquid_pint, + cup, US_liquid_cup, + gill, US_liquid_gill, + floz, fluid_ounce, US_fluid_ounce, US_liquid_ounce, + Imperial_bushel, + UK_liquid_gallon, Canadian_liquid_gallon, + UK_liquid_quart, + UK_liquid_pint, + UK_liquid_cup, + UK_liquid_gill, + UK_fluid_ounce, UK_liquid_ounce, + bbl, barrel, + tbsp, Tbsp, Tblsp, tblsp, tbs, Tbl, tablespoon, + tsp, teaspoon, + pk, peck, + fldr, fluid_dram, fluidram, + firkin, +) from ..unitquantity import set_default_units +from ..unitquantity import dimensionless From 3b5b499ec86ea67a561dc7ea420de9f8388d3044 Mon Sep 17 00:00:00 2001 From: Takumasa Nakamura Date: Fri, 24 Mar 2023 13:31:11 +0900 Subject: [PATCH 162/212] [mypy] specify error code to ignore --- quantities/dimensionality.pyi | 2 +- quantities/quantity.pyi | 18 +++++++++--------- quantities/uncertainquantity.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/quantities/dimensionality.pyi b/quantities/dimensionality.pyi index aadee6d..f944306 100644 --- a/quantities/dimensionality.pyi +++ b/quantities/dimensionality.pyi @@ -24,7 +24,7 @@ class Dimensionality(dict): ... - def __hash__(self) -> int: # type: ignore + def __hash__(self) -> int: # type: ignore[override] ... def __add__(self, other: Dimensionality) -> Dimensionality: diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 8841a87..bfdc77b 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -5,7 +5,7 @@ from quantities.typing.quantities import DimensionalityDescriptor, QuantityData import numpy.typing as npt -def validate_unit_quantity(value: Quantity) -> Quantity: #type: ignore +def validate_unit_quantity(value: Quantity) -> Quantity: ... @@ -39,12 +39,12 @@ class Quantity(npt.NDArray): def magnitude(self) -> npt.NDArray: ... - @property #type: ignore - def real(self) -> Quantity: #type: ignore + @property # type: ignore[misc] + def real(self) -> Quantity: # type: ignore[override] ... - @property #type: ignore - def imag(self) -> Quantity: #type: ignore + @property # type: ignore[misc] + def imag(self) -> Quantity: # type: ignore[override] ... @property @@ -67,15 +67,15 @@ class Quantity(npt.NDArray): ... - def __sub__(self, other) -> Quantity: #type: ignore + def __sub__(self, other) -> Quantity: # type: ignore[override] ... - def __rsub__(self, other) -> Quantity: #type: ignore + def __rsub__(self, other) -> Quantity: # type: ignore[override] ... - def __isub__(self, other) -> Quantity: #type: ignore + def __isub__(self, other) -> Quantity: # type: ignore[override] ... def __mod__(self, other) -> Quantity: @@ -94,7 +94,7 @@ class Quantity(npt.NDArray): # ... - def __rtruediv__(self, other) -> Quantity: #type: ignore + def __rtruediv__(self, other) -> Quantity: # type: ignore[override] ... def __pow__(self, power) -> Quantity: diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index ffa37cb..ad6d4c8 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -27,7 +27,7 @@ def __new__(cls, data, units='', uncertainty=None, dtype='d', copy=True): return ret - @Quantity.units.setter #type: ignore + @Quantity.units.setter # type: ignore[attr-defined] def units(self, units): super()._set_units(units) self.uncertainty.units = self._dimensionality From 6058e650d0875fe830014972a1ba7234568a9702 Mon Sep 17 00:00:00 2001 From: Takumasa Nakamura Date: Fri, 24 Mar 2023 14:37:18 +0900 Subject: [PATCH 163/212] [units] fix `pq.pt` is `US_liquid_pint` --- quantities/units/__init__.py | 42 +++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/quantities/units/__init__.py b/quantities/units/__init__.py index f489145..dbd63f4 100644 --- a/quantities/units/__init__.py +++ b/quantities/units/__init__.py @@ -58,7 +58,44 @@ from .information import * from . import length -from .length import * +from .length import ( + m, meter, metre, + km, kilometer, kilometre, + dm, decimeter, decimetre, + cm, centimeter, centimetre, + mm, millimeter, millimetre, + um, micrometer, micrometre, micron, + nm, nanometer, nanometre, + pm, picometer, picometre, + angstrom, + fm, femtometer, femtometre, fermi, + + inch, international_inch, + ft, foot, international_foot, + mi, mile, international_mile, + yd, yard, international_yard, + mil, thou, + pc, parsec, + ly, light_year, + au, astronomical_unit, + + nmi, nautical_mile, + # pt, + printers_point, point, + pica, + + US_survey_foot, + US_survey_yard, + US_survey_mile, US_statute_mile, + rod, pole, perch, + furlong, + fathom, + chain, + barleycorn, + arpentlin, + + kayser, wavenumber +) from . import mass from .mass import * @@ -105,8 +142,7 @@ dry_quart, US_dry_quart, dry_pint, US_dry_pint, quart, liquid_quart, US_liquid_quart, - # pt, - pint, liquid_pint, US_liquid_pint, + pt, pint, liquid_pint, US_liquid_pint, cup, US_liquid_cup, gill, US_liquid_gill, floz, fluid_ounce, US_fluid_ounce, US_liquid_ounce, From 4df83480c084a0b3c078e88371e1743714698126 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 5 Apr 2023 14:30:01 +0200 Subject: [PATCH 164/212] fix duplicate test name --- quantities/tests/test_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index 0c5a9ff..8989074 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -185,7 +185,7 @@ def test_argmin(self): self.assertQuantityEqual(ret, [0, 0]) self.assertEqual(ret.ctypes.data, out.ctypes.data) - def test_nanargmax(self): + def test_nanargmin(self): q = np.append(self.q, np.nan) * self.q.units self.assertEqual(self.q.nanargmin(), 0) From 7a1cfb343b55b746aaac9456c5a58c19c284791d Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 5 Apr 2023 14:32:56 +0200 Subject: [PATCH 165/212] Add type checking with mypy to CI --- .github/workflows/test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c3aeb53..8718f0c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,6 +58,7 @@ jobs: python -m pip install -U wheel python -m pip install -U pytest python -m pip install "numpy==${{ matrix.numpy-version }}" + python -m pip install -U mypy - name: Install run: | @@ -67,3 +68,7 @@ jobs: run: | PY_IGNORE_IMPORTMISMATCH=1 pytest python -m doctest README.rst + + - name: Check type information + run: | + mypy quantities From b470064926b0300901283400b20969b0134e8eb2 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 5 Apr 2023 14:45:55 +0200 Subject: [PATCH 166/212] Don't run static type checks with older versions of NumPy --- .github/workflows/test.yml | 44 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8718f0c..02a14a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,7 +58,6 @@ jobs: python -m pip install -U wheel python -m pip install -U pytest python -m pip install "numpy==${{ matrix.numpy-version }}" - python -m pip install -U mypy - name: Install run: | @@ -69,6 +68,47 @@ jobs: PY_IGNORE_IMPORTMISMATCH=1 pytest python -m doctest README.rst + type-check: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + python-version: [ "3.8", "3.9", "3.10", "3.11" ] + numpy-version: [ "1.22", "1.23", "1.24" ] + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.numpy-version }}-v1-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.numpy-version }}-v1- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U wheel + python -m pip install "numpy==${{ matrix.numpy-version }}" + python -m pip install -U mypy + + - name: Install + run: | + pip install . + - name: Check type information run: | - mypy quantities + mypy quantities \ No newline at end of file From 83155e11112ecd941447f38b3860aa12f674dcf1 Mon Sep 17 00:00:00 2001 From: Takumasa Nakamura Date: Fri, 7 Apr 2023 12:11:24 +0900 Subject: [PATCH 167/212] [mypy] default args in stub must be ellipsis --- quantities/quantity.pyi | 14 +++++++------- quantities/unitquantity.pyi | 31 +++++++++++++++++-------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index bfdc77b..69ff5c0 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -1,9 +1,9 @@ -from typing import Optional, Any +from typing import Any, Optional -from quantities.dimensionality import Dimensionality -from quantities.typing.quantities import DimensionalityDescriptor, QuantityData import numpy.typing as npt +from quantities.dimensionality import Dimensionality +from quantities.typing.quantities import DimensionalityDescriptor, QuantityData def validate_unit_quantity(value: Quantity) -> Quantity: ... @@ -23,8 +23,8 @@ def scale_other_units(f: Any) -> None: class Quantity(npt.NDArray): - def __new__(cls, data: QuantityData, units: DimensionalityDescriptor = '', - dtype: Optional[object] = None, copy: bool = True) -> Quantity: #type: ignore + def __new__(cls, data: QuantityData, units: DimensionalityDescriptor = ..., + dtype: Optional[object] = ..., copy: bool = ...) -> Quantity: ... @property @@ -51,7 +51,7 @@ class Quantity(npt.NDArray): def units(self) -> Quantity: ... - def rescale(self, units: Optional[DimensionalityDescriptor] = None) -> Quantity: + def rescale(self, units: Optional[DimensionalityDescriptor] = ...) -> Quantity: ... def rescale_preferred(self) -> Quantity: @@ -103,7 +103,7 @@ class Quantity(npt.NDArray): def __ipow__(self, other) -> Quantity: ... - def __round__(self, decimals: int = 0) -> Quantity: + def __round__(self, decimals: int = ...) -> Quantity: ... def __repr__(self) -> str: diff --git a/quantities/unitquantity.pyi b/quantities/unitquantity.pyi index dabab64..772799f 100644 --- a/quantities/unitquantity.pyi +++ b/quantities/unitquantity.pyi @@ -1,25 +1,24 @@ -from typing import Optional, Union, List, Any, overload +from typing import Any, List, Optional, Union, overload from quantities import Quantity from quantities.dimensionality import Dimensionality - class UnitQuantity(Quantity): _primary_order: int _secondary_order: int _reference_quantity: Optional[Quantity] def __new__( - cls, name: str, definition: Optional[Union[Quantity, float, int]] = None, symbol: Optional[str] = None, - u_symbol: Optional[str] = None, - aliases: List[str] = [], doc=None + cls, name: str, definition: Optional[Union[Quantity, float, int]] = ..., symbol: Optional[str] = ..., + u_symbol: Optional[str] = ..., + aliases: List[str] = ..., doc=... ) -> UnitQuantity: ... def __init__( - self, name: str, definition: Optional[Union[Quantity, float, int]] = None, symbol: Optional[str] = None, - u_symbol: Optional[str] = None, - aliases: List[str] = [], doc=None + self, name: str, definition: Optional[Union[Quantity, float, int]] = ..., symbol: Optional[str] = ..., + u_symbol: Optional[str] = ..., + aliases: List[str] = ..., doc=... ) -> None: ... @@ -161,10 +160,14 @@ class UnitConstant(UnitQuantity): ... -def set_default_units(system: Optional[str], currency: Optional[Union[str, UnitCurrency]], - current: Optional[Union[str, UnitCurrent]], information: Optional[Union[str, UnitInformation]], - length: Optional[Union[str, UnitLength]], - luminous_intensity: Optional[Union[str, UnitLuminousIntensity]], - mass: Optional[Union[str, UnitMass]], substance: Optional[Union[str, UnitSubstance]], - temperature: Optional[Union[str, UnitTemperature]], time: Optional[Union[str, UnitTime]]): +def set_default_units(system: Optional[str] = ..., + currency: Optional[Union[str, UnitCurrency]] = ..., + current: Optional[Union[str, UnitCurrent]] = ..., + information: Optional[Union[str, UnitInformation]] = ..., + length: Optional[Union[str, UnitLength]] = ..., + luminous_intensity: Optional[Union[str, UnitLuminousIntensity]] = ..., + mass: Optional[Union[str, UnitMass]] = ..., + substance: Optional[Union[str, UnitSubstance]] = ..., + temperature: Optional[Union[str, UnitTemperature]] = ..., + time: Optional[Union[str, UnitTime]] = ...): ... From 28990efcdf3bb988492db1eb900b387c99585917 Mon Sep 17 00:00:00 2001 From: Takumasa Nakamura Date: Fri, 7 Apr 2023 13:24:13 +0900 Subject: [PATCH 168/212] [mypy] fix typing for mul and div --- quantities/quantity.pyi | 65 +++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 69ff5c0..1907b9d 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -57,51 +57,46 @@ class Quantity(npt.NDArray): def rescale_preferred(self) -> Quantity: ... - def __add__(self, other) -> Quantity: - ... - - def __radd__(self, other) -> Quantity: - ... - - def __iadd__(self, other) -> Quantity: - ... + # numeric methods + def __add__(self, other: Quantity) -> Quantity: ... + def __radd__(self, other: Quantity) -> Quantity: ... + def __iadd__(self, other: Quantity) -> Quantity: ... + def __sub__(self, other: Quantity) -> Quantity: ... # type: ignore[override] + def __rsub__(self, other: Quantity) -> Quantity: ... # type: ignore[override] + def __isub__(self, other: Quantity) -> Quantity: ... # type: ignore[override] - def __sub__(self, other) -> Quantity: # type: ignore[override] - ... + def __mul__(self, other) -> Quantity: ... + def __rmul__(self, other) -> Quantity: ... + def __imul__(self, other) -> Quantity: ... + # NOTE matmul is not supported - def __rsub__(self, other) -> Quantity: # type: ignore[override] - ... + def __truediv__(self, other) -> Quantity: ... # type: ignore[override] + def __rtruediv__(self, other) -> Quantity: ... # type: ignore[override] + def __itruediv__(self, other) -> Quantity: ... # type: ignore[override] + def __floordiv__(self, other) -> Quantity: ... # type: ignore[override] + def __rfloordiv__(self, other) -> Quantity: ... # type: ignore[override] + def __ifloordiv__(self, other) -> Quantity: ... # type: ignore[override] - def __isub__(self, other) -> Quantity: # type: ignore[override] - ... + def __mod__(self, other: Quantity) -> Quantity: ... + def __rmod__(self, other: Quantity) -> Quantity: ... + def __imod__(self, other: Quantity) -> Quantity: ... - def __mod__(self, other) -> Quantity: - ... + # NOTE divmod is not supported - def __imod__(self, other) -> Quantity: - ... + def __pow__(self, power) -> Quantity: ... + def __rpow__(self, power) -> Quantity: ... + def __ipow__(self, power) -> Quantity: ... - # def __imul__(self, other): - # ... + # shift and bitwise are not supported - def __rmul__(self, other) -> Quantity: - ... - - # def __itruediv__(self, other) : - # ... - - - def __rtruediv__(self, other) -> Quantity: # type: ignore[override] - ... - - def __pow__(self, power) -> Quantity: - ... - - def __ipow__(self, other) -> Quantity: - ... + # unary methods + def __neg__(self) -> Quantity: ... + def __pos__(self) -> Quantity: ... + def __abs__(self) -> Quantity: ... + # NOTE invert is not supported def __round__(self, decimals: int = ...) -> Quantity: ... From 987193acb5bfbfc298b1271ea214f8e29ba4958b Mon Sep 17 00:00:00 2001 From: Takumasa Nakamura Date: Fri, 7 Apr 2023 13:44:15 +0900 Subject: [PATCH 169/212] [mypy] __pos__ is not supported now --- quantities/quantity.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 1907b9d..8b8b50c 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -94,7 +94,7 @@ class Quantity(npt.NDArray): # unary methods def __neg__(self) -> Quantity: ... - def __pos__(self) -> Quantity: ... + # def __pos__(self) -> Quantity: ... # GH#94 def __abs__(self) -> Quantity: ... # NOTE invert is not supported From 06652fb78e217474a470a2075369ecb2c62b2806 Mon Sep 17 00:00:00 2001 From: Takumasa Nakamura Date: Fri, 7 Apr 2023 13:49:54 +0900 Subject: [PATCH 170/212] [mypy] add ignore comment --- quantities/quantity.pyi | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/quantities/quantity.pyi b/quantities/quantity.pyi index 8b8b50c..2b0bd55 100644 --- a/quantities/quantity.pyi +++ b/quantities/quantity.pyi @@ -58,9 +58,9 @@ class Quantity(npt.NDArray): ... # numeric methods - def __add__(self, other: Quantity) -> Quantity: ... - def __radd__(self, other: Quantity) -> Quantity: ... - def __iadd__(self, other: Quantity) -> Quantity: ... + def __add__(self, other: Quantity) -> Quantity: ... # type: ignore[override] + def __radd__(self, other: Quantity) -> Quantity: ... # type: ignore[override] + def __iadd__(self, other: Quantity) -> Quantity: ... # type: ignore[override] def __sub__(self, other: Quantity) -> Quantity: ... # type: ignore[override] def __rsub__(self, other: Quantity) -> Quantity: ... # type: ignore[override] @@ -80,9 +80,9 @@ class Quantity(npt.NDArray): def __rfloordiv__(self, other) -> Quantity: ... # type: ignore[override] def __ifloordiv__(self, other) -> Quantity: ... # type: ignore[override] - def __mod__(self, other: Quantity) -> Quantity: ... - def __rmod__(self, other: Quantity) -> Quantity: ... - def __imod__(self, other: Quantity) -> Quantity: ... + def __mod__(self, other: Quantity) -> Quantity: ... # type: ignore[override] + def __rmod__(self, other: Quantity) -> Quantity: ... # type: ignore[override] + def __imod__(self, other: Quantity) -> Quantity: ... # type: ignore[override] # NOTE divmod is not supported From 0533898964d70f5c344c1352ed2f2739211e69c8 Mon Sep 17 00:00:00 2001 From: Zach McKenzie <92116279+zm711@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:52:50 -0400 Subject: [PATCH 171/212] np.Inf -> np.inf --- quantities/quantity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 9328525..612992e 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -604,8 +604,8 @@ def clip(self, min=None, max=None, out=None): if min is None and max is None: raise ValueError("at least one of min or max must be set") else: - if min is None: min = Quantity(-np.Inf, self._dimensionality) - if max is None: max = Quantity(np.Inf, self._dimensionality) + if min is None: min = Quantity(-np.inf, self._dimensionality) + if max is None: max = Quantity(np.inf, self._dimensionality) if self.dimensionality and not \ (isinstance(min, Quantity) and isinstance(max, Quantity)): From 005bab0683c23f42228c84fdfcc15f77186603d8 Mon Sep 17 00:00:00 2001 From: Zach McKenzie <92116279+zm711@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:53:56 -0400 Subject: [PATCH 172/212] np.NaN -> np.nan --- quantities/tests/test_umath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 964af98..657bdd3 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -17,7 +17,7 @@ def test_sum(self): self.assertQuantityEqual(np.sum(self.q), 10 * pq.J) def test_nansum(self): - c = [1,2,3, np.NaN] * pq.m + c = [1,2,3, np.nan] * pq.m self.assertQuantityEqual(np.nansum(c), 6 * pq.m) def test_cumprod(self): From 6b745f41a0ed65c50cfd268b19d8af0fed90692b Mon Sep 17 00:00:00 2001 From: Zach McKenzie <92116279+zm711@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:56:56 -0400 Subject: [PATCH 173/212] np.round_ -> np.round --- quantities/tests/test_umath.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 657bdd3..d9caa16 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -114,19 +114,19 @@ def test_around(self): [0, 0, 0, 10] * pq.J ) - def test_round_(self): + def test_round(self): self.assertQuantityEqual( - np.round_([.5, 1.5, 2.5, 3.5, 4.5] * pq.J), + np.round([.5, 1.5, 2.5, 3.5, 4.5] * pq.J), [0., 2., 2., 4., 4.] * pq.J ) self.assertQuantityEqual( - np.round_([1,2,3,11] * pq.J, decimals=1), + np.round([1,2,3,11] * pq.J, decimals=1), [1, 2, 3, 11] * pq.J ) self.assertQuantityEqual( - np.round_([1,2,3,11] * pq.J, decimals=-1), + np.round([1,2,3,11] * pq.J, decimals=-1), [0, 0, 0, 10] * pq.J ) From e32259bb82ea917ffdaa87b519930294fd6557ce Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:50:19 -0400 Subject: [PATCH 174/212] port numpy trapz into umath --- quantities/umath.py | 96 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/quantities/umath.py b/quantities/umath.py index 0f87070..087c631 100644 --- a/quantities/umath.py +++ b/quantities/umath.py @@ -119,8 +119,65 @@ def cross (a, b , axisa=-1, axisb=-1, axisc=-1, axis=None): copy=False ) -@with_doc(np.trapz) + def trapz(y, x=None, dx=1.0, axis=-1): + """ + Integrate along the given axis using the composite trapezoidal rule. + + If `x` is provided, the integration happens in sequence along its + elements - they are not sorted. + + Integrate `y` (`x`) along each 1d slice on the given axis, compute + :math:`\int y(x) dx`. + When `x` is specified, this integrates along the parametric curve, + computing :math:`\int_t y(t) dt = + \int_t y(t) \left.\frac{dx}{dt}\right|_{x=x(t)} dt`. + + Parameters + ---------- + y : array_like + Input array to integrate. + x : array_like, optional + The sample points corresponding to the `y` values. If `x` is None, + the sample points are assumed to be evenly spaced `dx` apart. The + default is None. + dx : scalar, optional + The spacing between sample points when `x` is None. The default is 1. + axis : int, optional + The axis along which to integrate. + + Returns + ------- + trapz : float or ndarray + Definite integral of `y` = n-dimensional array as approximated along + a single axis by the trapezoidal rule. If `y` is a 1-dimensional array, + then the result is a float. If `n` is greater than 1, then the result + is an `n`-1 dimensional array. + + See Also + -------- + sum, cumsum + + Notes + ----- + Image [2]_ illustrates trapezoidal rule -- y-axis locations of points + will be taken from `y` array, by default x-axis distances between + points will be 1.0, alternatively they can be provided with `x` array + or with `dx` scalar. Return value will be equal to combined area under + the red lines. + + Docstring is from the numpy 1.26 code base + https://github.com/numpy/numpy/blob/v1.26.0/numpy/lib/function_base.py#L4857-L4984 + + + References + ---------- + .. [1] Wikipedia page: https://en.wikipedia.org/wiki/Trapezoidal_rule + + .. [2] Illustration image: + https://en.wikipedia.org/wiki/File:Composite_trapezoidal_rule_illustration.png + + """ # this function has a weird input structure, so it is tricky to wrap it # perhaps there is a simpler way to do this if ( @@ -128,7 +185,7 @@ def trapz(y, x=None, dx=1.0, axis=-1): and not isinstance(x, Quantity) and not isinstance(dx, Quantity) ): - return np.trapz(y, x, dx, axis) + return _trapz(y, x, dx, axis) if not isinstance(y, Quantity): y = Quantity(y, copy = False) @@ -138,11 +195,42 @@ def trapz(y, x=None, dx=1.0, axis=-1): dx = Quantity(dx, copy = False) if x is None: - ret = np.trapz(y.magnitude , x, dx.magnitude, axis) + ret = _trapz(y.magnitude , x, dx.magnitude, axis) return Quantity ( ret, y.units * dx.units) else: - ret = np.trapz(y.magnitude , x.magnitude, dx.magnitude, axis) + ret = _trapz(y.magnitude , x.magnitude, dx.magnitude, axis) return Quantity ( ret, y.units * x.units) + +def _trapz(y, x, dx, axis): + """ported from numpy 1.26 since it will be deprecated and removed""" + from numpy.core.numeric import asanyarray + from numpy.core.umath import add + y = asanyarray(y) + if x is None: + d = dx + else: + x = asanyarray(x) + if x.ndim == 1: + d = diff(x) + # reshape to correct shape + shape = [1]*y.ndim + shape[axis] = d.shape[0] + d = d.reshape(shape) + else: + d = diff(x, axis=axis) + nd = y.ndim + slice1 = [slice(None)]*nd + slice2 = [slice(None)]*nd + slice1[axis] = slice(1, None) + slice2[axis] = slice(None, -1) + try: + ret = (d * (y[tuple(slice1)] + y[tuple(slice2)]) / 2.0).sum(axis) + except ValueError: + # Operations didn't work, cast to ndarray + d = np.asarray(d) + y = np.asarray(y) + ret = add.reduce(d * (y[tuple(slice1)]+y[tuple(slice2)])/2.0, axis) + return ret @with_doc(np.sin) def sin(x, out=None): From 45dfd6099a77164a79789f3e3fea09d6bf43125f Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 8 Dec 2023 17:16:26 +0100 Subject: [PATCH 175/212] Suppress `umath.py:124: DeprecationWarning: invalid escape sequence '\i'` --- quantities/umath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/umath.py b/quantities/umath.py index 087c631..e32cdd0 100644 --- a/quantities/umath.py +++ b/quantities/umath.py @@ -121,7 +121,7 @@ def cross (a, b , axisa=-1, axisb=-1, axisc=-1, axis=None): def trapz(y, x=None, dx=1.0, axis=-1): - """ + r""" Integrate along the given axis using the composite trapezoidal rule. If `x` is provided, the integration happens in sequence along its From dfdae840283f40201ffadd65a30aadcfd059fe0c Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 8 Dec 2023 17:17:16 +0100 Subject: [PATCH 176/212] Use scipy implementation of trapz if available --- quantities/umath.py | 63 +++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/quantities/umath.py b/quantities/umath.py index e32cdd0..16bc412 100644 --- a/quantities/umath.py +++ b/quantities/umath.py @@ -200,37 +200,44 @@ def trapz(y, x=None, dx=1.0, axis=-1): else: ret = _trapz(y.magnitude , x.magnitude, dx.magnitude, axis) return Quantity ( ret, y.units * x.units) - + def _trapz(y, x, dx, axis): """ported from numpy 1.26 since it will be deprecated and removed""" - from numpy.core.numeric import asanyarray - from numpy.core.umath import add - y = asanyarray(y) - if x is None: - d = dx - else: - x = asanyarray(x) - if x.ndim == 1: - d = diff(x) - # reshape to correct shape - shape = [1]*y.ndim - shape[axis] = d.shape[0] - d = d.reshape(shape) - else: - d = diff(x, axis=axis) - nd = y.ndim - slice1 = [slice(None)]*nd - slice2 = [slice(None)]*nd - slice1[axis] = slice(1, None) - slice2[axis] = slice(None, -1) try: - ret = (d * (y[tuple(slice1)] + y[tuple(slice2)]) / 2.0).sum(axis) - except ValueError: - # Operations didn't work, cast to ndarray - d = np.asarray(d) - y = np.asarray(y) - ret = add.reduce(d * (y[tuple(slice1)]+y[tuple(slice2)])/2.0, axis) - return ret + # if scipy is available, we use it + from scipy.integrate import trapezoid # type: ignore + except ImportError: + # otherwise we use the implementation ported from numpy 1.26 + from numpy.core.numeric import asanyarray + from numpy.core.umath import add + y = asanyarray(y) + if x is None: + d = dx + else: + x = asanyarray(x) + if x.ndim == 1: + d = diff(x) + # reshape to correct shape + shape = [1]*y.ndim + shape[axis] = d.shape[0] + d = d.reshape(shape) + else: + d = diff(x, axis=axis) + nd = y.ndim + slice1 = [slice(None)]*nd + slice2 = [slice(None)]*nd + slice1[axis] = slice(1, None) + slice2[axis] = slice(None, -1) + try: + ret = (d * (y[tuple(slice1)] + y[tuple(slice2)]) / 2.0).sum(axis) + except ValueError: + # Operations didn't work, cast to ndarray + d = np.asarray(d) + y = np.asarray(y) + ret = add.reduce(d * (y[tuple(slice1)]+y[tuple(slice2)])/2.0, axis) + return ret + else: + return trapezoid(y, x=x, dx=dx, axis=axis) @with_doc(np.sin) def sin(x, out=None): From c5f254b46f05f359fd986186c508bd39b7913f5c Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 8 Dec 2023 17:22:31 +0100 Subject: [PATCH 177/212] Update CI test matrix --- .github/workflows/test.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 02a14a9..49b2c5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,18 +12,12 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "3.8", "3.9", "3.10", "3.11" ] - numpy-version: [ "1.19", "1.20", "1.21", "1.22", "1.23", "1.24" ] + python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] + numpy-version: [ "1.20", "1.21", "1.22", "1.23", "1.24", "1.25", "1.26" ] exclude: - - python-version: "3.10" - numpy-version: "1.19" - os: ubuntu-latest - python-version: "3.10" numpy-version: "1.20" os: ubuntu-latest - - python-version: "3.11" - numpy-version: "1.19" - os: ubuntu-latest - python-version: "3.11" numpy-version: "1.20" os: ubuntu-latest @@ -74,8 +68,8 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "3.8", "3.9", "3.10", "3.11" ] - numpy-version: [ "1.22", "1.23", "1.24" ] + python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] + numpy-version: [ "1.22", "1.23", "1.24", "1.25", "1.26" ] steps: - uses: actions/checkout@v2 From d0b6328dbc7065e446d40940d65481e70ee252d5 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 8 Dec 2023 17:34:28 +0100 Subject: [PATCH 178/212] update built matrix exclusions; install setuptools since distutils is gone from Python 3.12 --- .github/workflows/test.yml | 15 +++++++++++++++ pyproject.toml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 49b2c5b..a78365d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,12 @@ jobs: python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] numpy-version: [ "1.20", "1.21", "1.22", "1.23", "1.24", "1.25", "1.26" ] exclude: + - python-version: "3.8" + numpy-version: "1.25" + os: ubuntu-latest + - python-version: "3.8" + numpy-version: "1.26" + os: ubuntu-latest - python-version: "3.10" numpy-version: "1.20" os: ubuntu-latest @@ -49,6 +55,7 @@ jobs: - name: Install dependencies run: | python -m pip install -U pip + python -m pip install -U setuptools python -m pip install -U wheel python -m pip install -U pytest python -m pip install "numpy==${{ matrix.numpy-version }}" @@ -70,6 +77,13 @@ jobs: os: [ ubuntu-latest ] python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] numpy-version: [ "1.22", "1.23", "1.24", "1.25", "1.26" ] + exclude: + - python-version: "3.8" + numpy-version: "1.25" + os: ubuntu-latest + - python-version: "3.8" + numpy-version: "1.26" + os: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -95,6 +109,7 @@ jobs: - name: Install dependencies run: | python -m pip install -U pip + python -m pip install -U setuptools python -m pip install -U wheel python -m pip install "numpy==${{ matrix.numpy-version }}" python -m pip install -U mypy diff --git a/pyproject.toml b/pyproject.toml index 2c60291..55e9cb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ "Topic :: Scientific/Engineering" ] dependencies = [ - "numpy>=1.19" + "numpy>=1.20" ] dynamic = ["version"] From d77dd4325d9fb83883c42f87493bfbf3cff4f3fe Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 8 Dec 2023 17:43:42 +0100 Subject: [PATCH 179/212] try to fix some of the errors with Python 3.12 --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a78365d..b9ab2af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,6 +56,7 @@ jobs: run: | python -m pip install -U pip python -m pip install -U setuptools + python -m pip install -U six # needed by setuptools python -m pip install -U wheel python -m pip install -U pytest python -m pip install "numpy==${{ matrix.numpy-version }}" @@ -110,6 +111,7 @@ jobs: run: | python -m pip install -U pip python -m pip install -U setuptools + python -m pip install -U six # needed by setuptools python -m pip install -U wheel python -m pip install "numpy==${{ matrix.numpy-version }}" python -m pip install -U mypy From 9a140dd397156f8b0ce3410d8b71fd6c5bd582c4 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 8 Dec 2023 17:48:47 +0100 Subject: [PATCH 180/212] With Python 3.12, only numpy 1.26 seems to install cleanly --- .github/workflows/test.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9ab2af..ea49d2e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] + python-version: [ "3.8", "3.9", "3.10", "3.11" ] numpy-version: [ "1.20", "1.21", "1.22", "1.23", "1.24", "1.25", "1.26" ] exclude: - python-version: "3.8" @@ -30,6 +30,10 @@ jobs: - python-version: "3.11" numpy-version: "1.21" os: ubuntu-latest + include: + - python-version: "3.12" + numpy-version: "1.26" + os: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -56,7 +60,6 @@ jobs: run: | python -m pip install -U pip python -m pip install -U setuptools - python -m pip install -U six # needed by setuptools python -m pip install -U wheel python -m pip install -U pytest python -m pip install "numpy==${{ matrix.numpy-version }}" @@ -76,7 +79,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] + python-version: [ "3.8", "3.9", "3.10", "3.11" ] numpy-version: [ "1.22", "1.23", "1.24", "1.25", "1.26" ] exclude: - python-version: "3.8" @@ -85,6 +88,10 @@ jobs: - python-version: "3.8" numpy-version: "1.26" os: ubuntu-latest + include: + - python-version: "3.12" + numpy-version: "1.26" + os: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -111,7 +118,6 @@ jobs: run: | python -m pip install -U pip python -m pip install -U setuptools - python -m pip install -U six # needed by setuptools python -m pip install -U wheel python -m pip install "numpy==${{ matrix.numpy-version }}" python -m pip install -U mypy From a9d1322488f8e6e253a1121d9c7976bbe0669e4b Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 8 Dec 2023 19:01:43 +0100 Subject: [PATCH 181/212] updated changelog --- CHANGES.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index ea3b080..8a73d0d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,14 @@ CHANGES ======= +------ +0.15.0 +------ + +- Quantities now has type stubs for all classes and functions. Many thanks to Peter Konradi and Takumasa Nakamura for this major effort. +- Fixed a number of deprecations coming from NumPy (thanks to Zach McKenzie) +- Dropped support for NumPy 1.19, added testing for Numpy 1.25 and 1.26, and for Python 3.12 + ------ 0.14.1 ------ From b7bbb8eee18aee4d437b51a6150029e7cb6b5733 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Mon, 11 Dec 2023 10:01:15 +0100 Subject: [PATCH 182/212] update readthedocs configuration --- doc/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 0bed75e..d7dec1a 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -140,7 +140,7 @@ # Additional templates that should be rendered to pages, maps page names to # template names. -html_sidebars = {'index': 'indexsidebar.html'} +html_sidebars = {'index': ['indexsidebar.html']} # If false, no module index is generated. #html_use_modindex = True @@ -200,4 +200,4 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} \ No newline at end of file From b47419a6e41ec076da47909e6aea2f27189ff678 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Mon, 11 Dec 2023 10:28:54 +0100 Subject: [PATCH 183/212] update readthedocs configuration --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index d7dec1a..0149cd5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -140,7 +140,7 @@ # Additional templates that should be rendered to pages, maps page names to # template names. -html_sidebars = {'index': ['indexsidebar.html']} +html_sidebars = {'index': ['localtoc.html', 'relations.html', 'sourcelink.html', 'indexsidebar.html', 'searchbox.html']} # If false, no module index is generated. #html_use_modindex = True From ed430211844fc1b20994d48f95df6fc31a185508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Tue, 18 Jun 2024 10:21:48 +0200 Subject: [PATCH 184/212] Add support for NumPy 2.0 --- .github/workflows/test.yml | 5 ++++- quantities/quantity.py | 14 ++++++++------ quantities/tests/common.py | 7 ++++++- quantities/tests/test_arithmetic.py | 27 ++------------------------- quantities/uncertainquantity.py | 4 ++-- 5 files changed, 22 insertions(+), 35 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea49d2e..285f065 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,6 +34,9 @@ jobs: - python-version: "3.12" numpy-version: "1.26" os: ubuntu-latest + - python-version: "3.12" + numpy-version: "2.0" + os: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -128,4 +131,4 @@ jobs: - name: Check type information run: | - mypy quantities \ No newline at end of file + mypy quantities diff --git a/quantities/quantity.py b/quantities/quantity.py index 612992e..d9e7dae 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -120,9 +120,9 @@ def __new__(cls, data, units='', dtype=None, copy=True): data = data.rescale(units) if isinstance(data, unit_registry['UnitQuantity']): return 1*data - return np.array(data, dtype=dtype, copy=copy, subok=True).view(cls) + return np.asanyarray(data, dtype=dtype).view(cls) - ret = np.array(data, dtype=dtype, copy=copy).view(cls) + ret = np.asarray(data, dtype=dtype).view(cls) ret._dimensionality.update(validate_dimensionality(units)) return ret @@ -210,8 +210,8 @@ def rescale(self, units=None, dtype=None): dtype = self.dtype if self.dimensionality == to_dims: return self.astype(dtype) - to_u = Quantity(1.0, to_dims) - from_u = Quantity(1.0, self.dimensionality) + to_u = Quantity(1.0, to_dims, dtype=dtype) + from_u = Quantity(1.0, self.dimensionality, dtype=dtype) try: cf = get_conversion_factor(from_u, to_u) except AssertionError: @@ -219,6 +219,8 @@ def rescale(self, units=None, dtype=None): 'Unable to convert between units of "%s" and "%s"' %(from_u._dimensionality, to_u._dimensionality) ) + if np.dtype(dtype).kind in 'fc': + cf = np.array(cf, dtype=dtype) new_magnitude = cf*self.magnitude dtype = np.result_type(dtype, new_magnitude) return Quantity(new_magnitude, to_u, dtype=dtype) @@ -272,7 +274,7 @@ def __array_prepare__(self, obj, context=None): uf, objs, huh = context if uf.__name__.startswith('is'): return obj - #print self, obj, res, uf, objs + try: res._dimensionality = p_dict[uf](*objs) except KeyError: @@ -590,7 +592,7 @@ def nanargmax(self,axis=None, out=None): @with_doc(np.ndarray.ptp) def ptp(self, axis=None, out=None): - ret = self.magnitude.ptp(axis, None if out is None else out.magnitude) + ret = np.ptp(self.magnitude, axis, None if out is None else out.magnitude) dim = self.dimensionality if out is None: return Quantity(ret, dim, copy=False) diff --git a/quantities/tests/common.py b/quantities/tests/common.py index fb5d974..ed28c83 100644 --- a/quantities/tests/common.py +++ b/quantities/tests/common.py @@ -19,7 +19,11 @@ def assertQuantityEqual(self, q1, q2, msg=None, delta=None): Make sure q1 and q2 are the same quantities to within the given precision. """ - delta = 1e-5 if delta is None else delta + if delta is None: + # NumPy 2 introduced float16, so we base tolerance on machine epsilon + delta1 = np.finfo(q1.dtype).eps if isinstance(q1, np.ndarray) and q1.dtype.kind in 'fc' else 1e-15 + delta2 = np.finfo(q2.dtype).eps if isinstance(q2, np.ndarray) and q2.dtype.kind in 'fc' else 1e-15 + delta = max(delta1, delta2)**0.3 msg = '' if msg is None else ' (%s)' % msg q1 = Quantity(q1) @@ -28,6 +32,7 @@ def assertQuantityEqual(self, q1, q2, msg=None, delta=None): raise self.failureException( f"Shape mismatch ({q1.shape} vs {q2.shape}){msg}" ) + if not np.all(np.abs(q1.magnitude - q2.magnitude) < delta): raise self.failureException( "Magnitudes differ by more than %g (%s vs %s)%s" diff --git a/quantities/tests/test_arithmetic.py b/quantities/tests/test_arithmetic.py index 7f955b4..e7e5a05 100644 --- a/quantities/tests/test_arithmetic.py +++ b/quantities/tests/test_arithmetic.py @@ -48,32 +48,9 @@ def check(f, *args, **kwargs): return (new, ) -class iter_dtypes: - - def __init__(self): - self._i = 1 - self._typeDict = np.sctypeDict.copy() - self._typeDict[17] = int - self._typeDict[18] = long - self._typeDict[19] = float - self._typeDict[20] = complex - - def __iter__(self): - return self - - def __next__(self): - if self._i > 20: - raise StopIteration - - i = self._i - self._i += 1 - return self._typeDict[i] - - def next(self): - return self.__next__() - def get_dtypes(): - return list(iter_dtypes()) + numeric_dtypes = 'iufc' # https://numpy.org/doc/stable/reference/generated/numpy.dtype.kind.html + return [v for v in np.sctypeDict.values() if np.dtype(v).kind in numeric_dtypes] + [int, long, float, complex] class iter_types: diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index ad6d4c8..0f0949c 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -118,7 +118,7 @@ def __mul__(self, other): ru = (sru**2+oru**2)**0.5 u = res.view(Quantity) * ru except AttributeError: - other = np.array(other, copy=False, subok=True) + other = np.asanyarray(other) u = (self.uncertainty**2*other**2)**0.5 res._uncertainty = u @@ -140,7 +140,7 @@ def __truediv__(self, other): ru = (sru**2+oru**2)**0.5 u = res.view(Quantity) * ru except AttributeError: - other = np.array(other, copy=False, subok=True) + other = np.asanyarray(other) u = (self.uncertainty**2/other**2)**0.5 res._uncertainty = u From 75f7b0da47c677e9019ca9e5c91dd5d71f2be698 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Tue, 18 Jun 2024 10:48:13 +0200 Subject: [PATCH 185/212] Update CI matrix --- .github/workflows/test.yml | 39 ++++---------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea49d2e..f3a8895 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,28 +12,8 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "3.8", "3.9", "3.10", "3.11" ] - numpy-version: [ "1.20", "1.21", "1.22", "1.23", "1.24", "1.25", "1.26" ] - exclude: - - python-version: "3.8" - numpy-version: "1.25" - os: ubuntu-latest - - python-version: "3.8" - numpy-version: "1.26" - os: ubuntu-latest - - python-version: "3.10" - numpy-version: "1.20" - os: ubuntu-latest - - python-version: "3.11" - numpy-version: "1.20" - os: ubuntu-latest - - python-version: "3.11" - numpy-version: "1.21" - os: ubuntu-latest - include: - - python-version: "3.12" - numpy-version: "1.26" - os: ubuntu-latest + python-version: [ "3.9", "3.10", "3.11", "3.12" ] + numpy-version: [ "1.22", "1.23", "1.24", "1.25", "1.26", "2.0" ] steps: - uses: actions/checkout@v2 @@ -79,19 +59,8 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ "3.8", "3.9", "3.10", "3.11" ] - numpy-version: [ "1.22", "1.23", "1.24", "1.25", "1.26" ] - exclude: - - python-version: "3.8" - numpy-version: "1.25" - os: ubuntu-latest - - python-version: "3.8" - numpy-version: "1.26" - os: ubuntu-latest - include: - - python-version: "3.12" - numpy-version: "1.26" - os: ubuntu-latest + python-version: [ "3.9", "3.10", "3.11", "3.12" ] + numpy-version: [ "1.22", "1.23", "1.24", "1.25", "1.26", "2.0" ] steps: - uses: actions/checkout@v2 From 2ace61719404e64307064b1898cace5b7f69d330 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Tue, 18 Jun 2024 11:00:40 +0200 Subject: [PATCH 186/212] With Python 3.12, only NumPy 1.26 or later will install --- .github/workflows/test.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3a8895..4679bfd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,19 @@ jobs: os: [ ubuntu-latest ] python-version: [ "3.9", "3.10", "3.11", "3.12" ] numpy-version: [ "1.22", "1.23", "1.24", "1.25", "1.26", "2.0" ] + exclude: + - python-version: "3.12" + numpy-version: "1.22" + os: ubuntu-latest + - python-version: "3.12" + numpy-version: "1.23" + os: ubuntu-latest + - python-version: "3.12" + numpy-version: "1.24" + os: ubuntu-latest + - python-version: "3.12" + numpy-version: "1.25" + os: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -61,6 +74,19 @@ jobs: os: [ ubuntu-latest ] python-version: [ "3.9", "3.10", "3.11", "3.12" ] numpy-version: [ "1.22", "1.23", "1.24", "1.25", "1.26", "2.0" ] + exclude: + - python-version: "3.12" + numpy-version: "1.22" + os: ubuntu-latest + - python-version: "3.12" + numpy-version: "1.23" + os: ubuntu-latest + - python-version: "3.12" + numpy-version: "1.24" + os: ubuntu-latest + - python-version: "3.12" + numpy-version: "1.25" + os: ubuntu-latest steps: - uses: actions/checkout@v2 From 6d035c688e090bff382ed7e36f0e6faf9ea4cdcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Wed, 19 Jun 2024 11:06:46 +0200 Subject: [PATCH 187/212] Deprecate Quantities(... copy=) kwarg. Fix warnings. --- quantities/__init__.py | 2 ++ quantities/dimensionality.py | 9 +++-- quantities/quantity.py | 62 ++++++++++++++++---------------- quantities/tests/test_methods.py | 8 +++-- quantities/tests/test_umath.py | 10 +++++- quantities/uncertainquantity.py | 42 +++++++++++----------- 6 files changed, 76 insertions(+), 57 deletions(-) diff --git a/quantities/__init__.py b/quantities/__init__.py index d315fbb..d534d58 100644 --- a/quantities/__init__.py +++ b/quantities/__init__.py @@ -265,6 +265,8 @@ """ +class QuantitiesDeprecationWarning(DeprecationWarning): + pass from ._version import __version__ diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index c8d6002..22207de 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -9,6 +9,8 @@ from .registry import unit_registry from .decorators import memoize +_np_version = tuple(map(int, np.__version__.split('.'))) + def assert_isinstance(obj, types): try: assert isinstance(obj, types) @@ -329,10 +331,11 @@ def _d_copy(q1, out=None): def _d_clip(a1, a2, a3, q): return q.dimensionality -try: + +if _np_version < (2, 0, 0): p_dict[np.core.umath.clip] = _d_clip -except AttributeError: - pass # For compatibility with Numpy < 1.17 when clip wasn't a ufunc yet +else: + p_dict[np.clip] = _d_clip def _d_sqrt(q1, out=None): return q1._dimensionality**0.5 diff --git a/quantities/quantity.py b/quantities/quantity.py index d9e7dae..cce6fed 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -3,10 +3,11 @@ import copy from functools import wraps +import warnings import numpy as np -from . import markup +from . import markup, QuantitiesDeprecationWarning from .dimensionality import Dimensionality, p_dict from .registry import unit_registry from .decorators import with_doc @@ -114,7 +115,11 @@ class Quantity(np.ndarray): # TODO: what is an appropriate value? __array_priority__ = 21 - def __new__(cls, data, units='', dtype=None, copy=True): + def __new__(cls, data, units='', dtype=None, copy=None): + if copy is not None: + warnings.warn(("The 'copy' argument in Quantity is deprecated and will be removed in the future. " + "The argument has no effect since quantities-0.16.0 (to aid numpy-2.0 support)."), + QuantitiesDeprecationWarning, stacklevel=2) if isinstance(data, Quantity): if units: data = data.rescale(units) @@ -285,11 +290,12 @@ def __array_prepare__(self, obj, context=None): ) return res - def __array_wrap__(self, obj, context=None): + def __array_wrap__(self, obj, context=None, return_scalar=False): if not isinstance(obj, Quantity): # backwards compatibility with numpy-1.3 - obj = self.__array_prepare__(obj, context) - return obj + return self.__array_prepare__(obj, context) + else: + return super().__array_wrap__(obj, context, return_scalar) @with_doc(np.ndarray.__add__) @scale_other_units @@ -478,7 +484,7 @@ def sum(self, axis=None, dtype=None, out=None): ret = self.magnitude.sum(axis, dtype, None if out is None else out.magnitude) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -489,8 +495,7 @@ def nansum(self, axis=None, dtype=None, out=None): import numpy as np return Quantity( np.nansum(self.magnitude, axis, dtype, out), - self.dimensionality, - copy=False + self.dimensionality ) @with_doc(np.ndarray.fill) @@ -525,7 +530,7 @@ def argsort(self, axis=-1, kind='quick', order=None): @with_doc(np.ndarray.searchsorted) def searchsorted(self,values, side='left'): if not isinstance (values, Quantity): - values = Quantity(values, copy=False) + values = Quantity(values) if values._dimensionality != self._dimensionality: raise ValueError("values does not have the same units as self") @@ -541,7 +546,7 @@ def max(self, axis=None, out=None): ret = self.magnitude.max(axis, None if out is None else out.magnitude) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -555,8 +560,7 @@ def argmax(self, axis=None, out=None): def nanmax(self, axis=None, out=None): return Quantity( np.nanmax(self.magnitude), - self.dimensionality, - copy=False + self.dimensionality ) @with_doc(np.ndarray.min) @@ -564,7 +568,7 @@ def min(self, axis=None, out=None): ret = self.magnitude.min(axis, None if out is None else out.magnitude) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -574,8 +578,7 @@ def min(self, axis=None, out=None): def nanmin(self, axis=None, out=None): return Quantity( np.nanmin(self.magnitude), - self.dimensionality, - copy=False + self.dimensionality ) @with_doc(np.ndarray.argmin) @@ -595,7 +598,7 @@ def ptp(self, axis=None, out=None): ret = np.ptp(self.magnitude, axis, None if out is None else out.magnitude) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -622,7 +625,7 @@ def clip(self, min=None, max=None, out=None): ) dim = self.dimensionality if out is None: - return Quantity(clipped, dim, copy=False) + return Quantity(clipped, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -633,7 +636,7 @@ def round(self, decimals=0, out=None): ret = self.magnitude.round(decimals, None if out is None else out.magnitude) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -644,7 +647,7 @@ def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None): ret = self.magnitude.trace(offset, axis1, axis2, dtype, None if out is None else out.magnitude) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -654,8 +657,7 @@ def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None): def squeeze(self, axis=None): return Quantity( self.magnitude.squeeze(axis), - self.dimensionality, - copy=False + self.dimensionality ) @with_doc(np.ndarray.mean) @@ -663,7 +665,7 @@ def mean(self, axis=None, dtype=None, out=None): ret = self.magnitude.mean(axis, dtype, None if out is None else out.magnitude) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -674,15 +676,14 @@ def nanmean(self, axis=None, dtype=None, out=None): import numpy as np return Quantity( np.nanmean(self.magnitude, axis, dtype, out), - self.dimensionality, - copy=False) + self.dimensionality) @with_doc(np.ndarray.var) def var(self, axis=None, dtype=None, out=None, ddof=0): ret = self.magnitude.var(axis, dtype, out, ddof) dim = self._dimensionality**2 if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -693,7 +694,7 @@ def std(self, axis=None, dtype=None, out=None, ddof=0): ret = self.magnitude.std(axis, dtype, out, ddof) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -703,8 +704,7 @@ def std(self, axis=None, dtype=None, out=None, ddof=0): def nanstd(self, axis=None, dtype=None, out=None, ddof=0): return Quantity( np.nanstd(self.magnitude, axis, dtype, out, ddof), - self._dimensionality, - copy=False + self._dimensionality ) @with_doc(np.ndarray.prod) @@ -717,7 +717,7 @@ def prod(self, axis=None, dtype=None, out=None): ret = self.magnitude.prod(axis, dtype, None if out is None else out.magnitude) dim = self._dimensionality**power if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -728,7 +728,7 @@ def cumsum(self, axis=None, dtype=None, out=None): ret = self.magnitude.cumsum(axis, dtype, None if out is None else out.magnitude) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if not isinstance(out, Quantity): raise TypeError("out parameter must be a Quantity") out._dimensionality = dim @@ -745,7 +745,7 @@ def cumprod(self, axis=None, dtype=None, out=None): ret = self.magnitude.cumprod(axis, dtype, out) dim = self.dimensionality if out is None: - return Quantity(ret, dim, copy=False) + return Quantity(ret, dim) if isinstance(out, Quantity): out._dimensionality = dim return out diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index 8989074..ba184a3 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -1,4 +1,5 @@ -from .. import units as pq +import warnings +from .. import QuantitiesDeprecationWarning, units as pq from .common import TestCase import numpy as np @@ -119,7 +120,10 @@ def methodWithOut(self, name, result, q=None, *args, **kw): ) if isinstance(result, Quantity): # deliberately using an incompatible unit - out = Quantity(np.empty_like(result.magnitude), pq.s, copy=False) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=QuantitiesDeprecationWarning) + out = Quantity(np.empty_like(result.magnitude), pq.s, copy=False) + # we can drop 'copy=False' above once the deprecation of the arg has expired. else: out = np.empty_like(result) ret = getattr(q.copy(), name)(*args, out=out, **kw) diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index d9caa16..4c113d1 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -3,6 +3,8 @@ from .. import units as pq from .common import TestCase, unittest +_np_version = tuple(map(int, np.__version__.split('.'))) + class TestUmath(TestCase): @@ -54,7 +56,13 @@ def test_cross(self): self.assertQuantityEqual(np.cross(a,b), [-15,-2,39]*pq.kPa*pq.m**2) def test_trapz(self): - self.assertQuantityEqual(np.trapz(self.q, dx = 1*pq.m), 7.5 * pq.J*pq.m) + # np.trapz is deprecated, np.trapezoid is recommend + if _np_version < (2, 0, 0): + self.assertQuantityEqual(np.trapz(self.q, dx = 1*pq.m), 7.5 * pq.J*pq.m) + + def test_trapezoid(self): + if _np_version >= (2, 0, 0): + self.assertQuantityEqual(np.trapezoid(self.q, dx = 1*pq.m), 7.5 * pq.J*pq.m) def test_sinh(self): q = [1, 2, 3, 4, 6] * pq.radian diff --git a/quantities/uncertainquantity.py b/quantities/uncertainquantity.py index 0f0949c..319a6cc 100644 --- a/quantities/uncertainquantity.py +++ b/quantities/uncertainquantity.py @@ -2,8 +2,9 @@ """ import numpy as np +import warnings -from . import markup +from . import markup, QuantitiesDeprecationWarning from .quantity import Quantity, scale_other_units from .registry import unit_registry from .decorators import with_doc @@ -13,15 +14,20 @@ class UncertainQuantity(Quantity): # TODO: what is an appropriate value? __array_priority__ = 22 - def __new__(cls, data, units='', uncertainty=None, dtype='d', copy=True): - ret = Quantity.__new__(cls, data, units, dtype, copy) + def __new__(cls, data, units='', uncertainty=None, dtype='d', copy=None): + if copy is not None: + warnings.warn(("The 'copy' argument in UncertainQuantity is deprecated and will be removed in the future. " + "The argument has no effect since quantities-0.16.0 (to aid numpy-2.0 support)."), + QuantitiesDeprecationWarning, stacklevel=2) + ret = Quantity.__new__(cls, data, units, dtype) # _uncertainty initialized to be dimensionless by __array_finalize__: ret._uncertainty._dimensionality = ret._dimensionality if uncertainty is not None: ret.uncertainty = Quantity(uncertainty, ret._dimensionality) elif isinstance(data, UncertainQuantity): - if copy or ret._dimensionality != uncertainty._dimensionality: + is_copy = id(data) == id(ret) + if is_copy or ret._dimensionality != uncertainty._dimensionality: uncertainty = data.uncertainty.rescale(ret.units) ret.uncertainty = uncertainty @@ -50,7 +56,7 @@ def uncertainty(self): @uncertainty.setter def uncertainty(self, uncertainty): if not isinstance(uncertainty, Quantity): - uncertainty = Quantity(uncertainty, copy=False) + uncertainty = Quantity(uncertainty) try: assert self.shape == uncertainty.shape except AssertionError: @@ -78,7 +84,6 @@ def __array_finalize__(self, obj): Quantity( np.zeros(self.shape, self.dtype), self._dimensionality, - copy=False ) ) @@ -87,7 +92,7 @@ def __array_finalize__(self, obj): def __add__(self, other): res = super().__add__(other) u = (self.uncertainty**2+other.uncertainty**2)**0.5 - return UncertainQuantity(res, uncertainty=u, copy=False) + return UncertainQuantity(res, uncertainty=u) @with_doc(Quantity.__radd__, use_header=False) @scale_other_units @@ -99,13 +104,13 @@ def __radd__(self, other): def __sub__(self, other): res = super().__sub__(other) u = (self.uncertainty**2+other.uncertainty**2)**0.5 - return UncertainQuantity(res, uncertainty=u, copy=False) + return UncertainQuantity(res, uncertainty=u) @with_doc(Quantity.__rsub__, use_header=False) @scale_other_units def __rsub__(self, other): if not isinstance(other, UncertainQuantity): - other = UncertainQuantity(other, copy=False) + other = UncertainQuantity(other) return UncertainQuantity.__sub__(other, self) @@ -150,7 +155,7 @@ def __truediv__(self, other): def __rtruediv__(self, other): temp = UncertainQuantity( 1/self.magnitude, self.dimensionality**-1, - self.relative_uncertainty/self.magnitude, copy=False + self.relative_uncertainty/self.magnitude ) return other * temp @@ -165,8 +170,7 @@ def __getitem__(self, key): return UncertainQuantity( self.magnitude[key], self._dimensionality, - self.uncertainty[key], - copy=False + self.uncertainty[key] ) @with_doc(Quantity.__repr__, use_header=False) @@ -198,8 +202,7 @@ def sum(self, axis=None, dtype=None, out=None): return UncertainQuantity( self.magnitude.sum(axis, dtype, out), self.dimensionality, - (np.sum(self.uncertainty.magnitude**2, axis))**0.5, - copy=False + (np.sum(self.uncertainty.magnitude**2, axis))**0.5 ) @with_doc(np.nansum) @@ -207,8 +210,7 @@ def nansum(self, axis=None, dtype=None, out=None): return UncertainQuantity( np.nansum(self.magnitude, axis, dtype, out), self.dimensionality, - (np.nansum(self.uncertainty.magnitude**2, axis))**0.5, - copy=False + (np.nansum(self.uncertainty.magnitude**2, axis))**0.5 ) @with_doc(np.ndarray.mean) @@ -216,8 +218,8 @@ def mean(self, axis=None, dtype=None, out=None): return UncertainQuantity( self.magnitude.mean(axis, dtype, out), self.dimensionality, - ((1.0/np.size(self,axis))**2 * np.sum(self.uncertainty.magnitude**2, axis))**0.5, - copy=False) + ((1.0/np.size(self,axis))**2 * np.sum(self.uncertainty.magnitude**2, axis))**0.5 + ) @with_doc(np.nanmean) def nanmean(self, axis=None, dtype=None, out=None): @@ -225,8 +227,8 @@ def nanmean(self, axis=None, dtype=None, out=None): return UncertainQuantity( np.nanmean(self.magnitude, axis, dtype, out), self.dimensionality, - ((1.0/size)**2 * np.nansum(np.nan_to_num(self.uncertainty.magnitude)**2, axis))**0.5, - copy=False) + ((1.0/size)**2 * np.nansum(np.nan_to_num(self.uncertainty.magnitude)**2, axis))**0.5 + ) @with_doc(np.sqrt) def sqrt(self, out=None): From 1713ff929af3d57aabd485196520d5d74e50601e Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:03:44 -0400 Subject: [PATCH 188/212] add old return quantity obj --- quantities/quantity.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index cce6fed..63a2b36 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -292,10 +292,13 @@ def __array_prepare__(self, obj, context=None): def __array_wrap__(self, obj, context=None, return_scalar=False): if not isinstance(obj, Quantity): + if tuple(map(int, np.__version__.split('.'))) < (2, 0, 0): # backwards compatibility with numpy-1.3 - return self.__array_prepare__(obj, context) + return self.__array_prepare__(obj, context) + else: + return super().__array_wrap__(obj, context, return_scalar) else: - return super().__array_wrap__(obj, context, return_scalar) + return obj @with_doc(np.ndarray.__add__) @scale_other_units From b367e873f21e34651e8b0119cc48dc5d847262fb Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:16:42 -0400 Subject: [PATCH 189/212] fix for numpy 2.0 wrapping --- quantities/quantity.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index 63a2b36..ee2194c 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -291,14 +291,15 @@ def __array_prepare__(self, obj, context=None): return res def __array_wrap__(self, obj, context=None, return_scalar=False): - if not isinstance(obj, Quantity): - if tuple(map(int, np.__version__.split('.'))) < (2, 0, 0): - # backwards compatibility with numpy-1.3 + _np_version = tuple(map(int, np.__version__.split('.'))) + if _np_version < (2, 0, 0): + if not isinstance(obj, Quantity): return self.__array_prepare__(obj, context) else: - return super().__array_wrap__(obj, context, return_scalar) + return obj else: - return obj + return super().__array_wrap__(obj, context, return_scalar) + @with_doc(np.ndarray.__add__) @scale_other_units From b6efa33bb86d2c4a1dcfa4ca81927564a4b2f055 Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:24:21 -0400 Subject: [PATCH 190/212] full separate numpy > 2.0 array wrap --- quantities/quantity.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index ee2194c..29b6df3 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -292,13 +292,18 @@ def __array_prepare__(self, obj, context=None): def __array_wrap__(self, obj, context=None, return_scalar=False): _np_version = tuple(map(int, np.__version__.split('.'))) + # For NumPy < 2.0 we do old behavior if _np_version < (2, 0, 0): if not isinstance(obj, Quantity): return self.__array_prepare__(obj, context) else: return obj + # For NumPy > 2.0 we either do the prepare or the wrap else: - return super().__array_wrap__(obj, context, return_scalar) + if not isinstance(obj, Quantity): + return self.__array_prepare__(obj, context) + else: + return super().__array_wrap__(obj, context, return_scalar) @with_doc(np.ndarray.__add__) From 2a18ba9b9640222912386a4ecb7f8f17951d0b79 Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:21:34 -0400 Subject: [PATCH 191/212] prevent arbitrary code eval --- quantities/registry.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/quantities/registry.py b/quantities/registry.py index ec520e8..8fcdc5d 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -1,8 +1,8 @@ """ """ -import copy import re +import builtins class UnitRegistry: @@ -16,6 +16,13 @@ def __init__(self): self.__context = {} def __getitem__(self, string): + + # easy hack to prevent arbitrary evaluation of code + all_builtins = dir(builtins) + for builtin in all_builtins: + if builtin in string: + raise RuntimeError(f"String parsing error for {string}. Enter a string accepted by quantities") + try: return eval(string, self.__context) except NameError: From 298c01f097a7794e51af8d8eaeb9983ca745f469 Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:27:10 -0400 Subject: [PATCH 192/212] fix for bytes --- quantities/registry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quantities/registry.py b/quantities/registry.py index 8fcdc5d..10329bd 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -19,6 +19,8 @@ def __getitem__(self, string): # easy hack to prevent arbitrary evaluation of code all_builtins = dir(builtins) + # because we have kilobytes, other bytes we have to remove bytes + all_builtins = all_builtins.remove("bytes") for builtin in all_builtins: if builtin in string: raise RuntimeError(f"String parsing error for {string}. Enter a string accepted by quantities") From bb651935bd1b727cf3f3e169b1871b75c7ea8e9d Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:29:16 -0400 Subject: [PATCH 193/212] remove is in place --- quantities/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantities/registry.py b/quantities/registry.py index 10329bd..b48ac91 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -20,7 +20,7 @@ def __getitem__(self, string): # easy hack to prevent arbitrary evaluation of code all_builtins = dir(builtins) # because we have kilobytes, other bytes we have to remove bytes - all_builtins = all_builtins.remove("bytes") + all_builtins.remove("bytes") for builtin in all_builtins: if builtin in string: raise RuntimeError(f"String parsing error for {string}. Enter a string accepted by quantities") From 3430babf5300692c2d56d25a39ee10dcd8d0320c Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:31:38 -0400 Subject: [PATCH 194/212] fix for octet as well --- quantities/registry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quantities/registry.py b/quantities/registry.py index b48ac91..4d6a081 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -21,6 +21,8 @@ def __getitem__(self, string): all_builtins = dir(builtins) # because we have kilobytes, other bytes we have to remove bytes all_builtins.remove("bytes") + # have to deal with octet as well + all_builtins.remove("oct") for builtin in all_builtins: if builtin in string: raise RuntimeError(f"String parsing error for {string}. Enter a string accepted by quantities") From 972006634c18a0a9e6fd68096e63db33b56d0090 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Tue, 27 Aug 2024 11:57:50 +0200 Subject: [PATCH 195/212] updated changelog --- CHANGES.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 8a73d0d..832b728 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,14 @@ CHANGES ======= +------ +0.16.0 +------ + +- Added support for NumPy 2.0, while maintaining support for older versions back to 1.22 [`PR#235 `_]. Many thanks to Björn Dahlgren and Zach McKenzie for this. +- Fixed a potential security hole [`PR#236 `_] +- Dropped support for Python 3.8 + ------ 0.15.0 ------ From 275618e38ae627d7038997916597dd13b16d21d5 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Tue, 27 Aug 2024 14:33:22 +0200 Subject: [PATCH 196/212] fix version string parsing --- quantities/dimensionality.py | 3 ++- quantities/quantity.py | 2 +- quantities/tests/test_umath.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index 22207de..d38038e 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -9,7 +9,8 @@ from .registry import unit_registry from .decorators import memoize -_np_version = tuple(map(int, np.__version__.split('.'))) +_np_version = tuple(map(int, np.__version__.split(".dev")[0].split("."))) + def assert_isinstance(obj, types): try: diff --git a/quantities/quantity.py b/quantities/quantity.py index 29b6df3..e6046fd 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -291,7 +291,7 @@ def __array_prepare__(self, obj, context=None): return res def __array_wrap__(self, obj, context=None, return_scalar=False): - _np_version = tuple(map(int, np.__version__.split('.'))) + _np_version = tuple(map(int, np.__version__.split(".dev")[0].split("."))) # For NumPy < 2.0 we do old behavior if _np_version < (2, 0, 0): if not isinstance(obj, Quantity): diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 4c113d1..6df6490 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -3,7 +3,7 @@ from .. import units as pq from .common import TestCase, unittest -_np_version = tuple(map(int, np.__version__.split('.'))) +_np_version = tuple(map(int, np.__version__.split(".dev")[0].split("."))) class TestUmath(TestCase): From 8f99a26bed5ec93984c57c655546567e9ef4be88 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Tue, 27 Aug 2024 14:38:02 +0200 Subject: [PATCH 197/212] add typical .venv to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b4d65eb..a5c99dc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ dist .idea .*cache/ _version.py -MANIFEST \ No newline at end of file +MANIFEST +.venv From afa2fa9088091ec5d8bc7f9a075ea7b3a710b525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=BCtz?= Date: Thu, 5 Sep 2024 14:34:39 -0700 Subject: [PATCH 198/212] declare compatibility with Python >= 3.9 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 55e9cb8..ca1193e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "quantities" description = "Support for physical quantities with units, based on numpy" readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" license = {file = "doc/user/license.rst"} authors = [ {name = "Darren Dale", email = "dsdale24@gmail.com"} @@ -49,4 +49,4 @@ requires = ["setuptools", "setuptools_scm[toml]"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] -write_to = "quantities/_version.py" \ No newline at end of file +write_to = "quantities/_version.py" From bfc6efb8e6509235ee301301fa0df4aee5fe5176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ingvar=20Dahlgren?= Date: Sun, 15 Sep 2024 13:21:57 +0200 Subject: [PATCH 199/212] Add umath funcs: maximum & minimum --- quantities/dimensionality.py | 2 ++ quantities/tests/test_umath.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/quantities/dimensionality.py b/quantities/dimensionality.py index d38038e..c00190b 100644 --- a/quantities/dimensionality.py +++ b/quantities/dimensionality.py @@ -284,6 +284,8 @@ def _d_check_uniform(q1, q2, out=None): p_dict[np.less_equal] = _d_check_uniform p_dict[np.greater] = _d_check_uniform p_dict[np.greater_equal] = _d_check_uniform +p_dict[np.maximum] = _d_check_uniform +p_dict[np.minimum] = _d_check_uniform def _d_arctan2(q1, q2, out=None): try: diff --git a/quantities/tests/test_umath.py b/quantities/tests/test_umath.py index 6df6490..a29231d 100644 --- a/quantities/tests/test_umath.py +++ b/quantities/tests/test_umath.py @@ -293,3 +293,13 @@ def test_greater_equal(self): arr2 = (1.0, 2.0) * pq.m self.assertTrue(np.all(np.greater_equal(arr2, arr1))) self.assertFalse(np.all(np.greater_equal(arr2*0.99, arr1))) + + def test_maximum(self): + arr1 = (998, 999) * pq.m + arr2 = (1e3, 5e2) * pq.m + self.assertQuantityEqual(np.maximum(arr1, arr2) - [1000, 999]*pq.m, [0, 0]*pq.m) + + def test_minimum(self): + arr1 = (998, 999) * pq.m + arr2 = (1e3, 5e2) * pq.m + self.assertQuantityEqual(np.minimum(arr1, arr2) - [998, 500]*pq.m, [0, 0]*pq.m) From 15b4d246222886f8e2e4424271ff515aa14acae2 Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:14:04 -0400 Subject: [PATCH 200/212] fix bug discover in neo ci --- quantities/registry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quantities/registry.py b/quantities/registry.py index 4d6a081..5c8fc50 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -23,6 +23,8 @@ def __getitem__(self, string): all_builtins.remove("bytes") # have to deal with octet as well all_builtins.remove("oct") + # have to remove min which is short for minute + all_builtins.remove("min") for builtin in all_builtins: if builtin in string: raise RuntimeError(f"String parsing error for {string}. Enter a string accepted by quantities") From efc67d99ca88330e6a6ba069c58f1c6648491d2c Mon Sep 17 00:00:00 2001 From: zm711 <92116279+zm711@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:38:00 -0400 Subject: [PATCH 201/212] add test for `min` problem and add comments --- quantities/registry.py | 2 +- quantities/tests/test_arithmetic.py | 6 ++++++ quantities/tests/test_conversion.py | 4 ++++ quantities/units/time.py | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/quantities/registry.py b/quantities/registry.py index 5c8fc50..4f9f3c7 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -27,7 +27,7 @@ def __getitem__(self, string): all_builtins.remove("min") for builtin in all_builtins: if builtin in string: - raise RuntimeError(f"String parsing error for {string}. Enter a string accepted by quantities") + raise RuntimeError(f"String parsing error for `{string}`. Enter a string accepted by quantities") try: return eval(string, self.__context) diff --git a/quantities/tests/test_arithmetic.py b/quantities/tests/test_arithmetic.py index e7e5a05..1b6aeeb 100644 --- a/quantities/tests/test_arithmetic.py +++ b/quantities/tests/test_arithmetic.py @@ -226,6 +226,12 @@ def test_addition(self): [1, 2, 3]*pq.hp + [1, 2, 3]*pq.hp, [2, 4, 6]*pq.hp ) + # add in test with 'min' since this caused issues + # see https://github.com/python-quantities/python-quantities/issues/243 + self.assertQuantityEqual( + Quantity(1, 'min') + Quantity(1, 'min'), + 2*pq.min + ) self.assertRaises(ValueError, op.add, pq.kPa, pq.lb) self.assertRaises(ValueError, op.add, pq.kPa, 10) diff --git a/quantities/tests/test_conversion.py b/quantities/tests/test_conversion.py index b0c848c..7ae0384 100644 --- a/quantities/tests/test_conversion.py +++ b/quantities/tests/test_conversion.py @@ -97,6 +97,10 @@ def test_default_system(self): self.assertQuantityEqual(pq.kg.simplified, 1000*pq.g) self.assertQuantityEqual(pq.m.simplified, 1000*pq.mm) + # test a time default as well as mass and weight + pq.set_default_units('SI') + self.assertQuantityEqual(pq.min.simplified, 60*pq.sec) + class TestUnitInformation(TestCase): def test_si(self): diff --git a/quantities/units/time.py b/quantities/units/time.py index f331545..9fa3505 100644 --- a/quantities/units/time.py +++ b/quantities/units/time.py @@ -64,7 +64,7 @@ 60*s, symbol='min', aliases=['minutes'] -) +) # min is function in python h = hr = hour = UnitTime( 'hour', 60*min, From 4e6679d12ab8aec30928aa10b137795eedbca461 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 16 Oct 2024 16:57:23 +0200 Subject: [PATCH 202/212] updated changelog --- CHANGES.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 832b728..c3fb6f3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,13 @@ CHANGES ======= +------ +0.16.1 +------ + +- Fixed a couple of small bugs ([`PR#238 `_] and [`PR#242 `_]) +- Added umath funcs: `maximum` & `minimum` + ------ 0.16.0 ------ From f803620be9b4bce2f8b8e1d6067d3c36b2437e31 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wagenaar" Date: Fri, 15 Nov 2024 17:45:10 -0800 Subject: [PATCH 203/212] Added "plain" property to quantity The new "plain" property returns a copy of the quantity as a plain number provided that the quantity is dimensionless. For example: import quantities as pq t = 2 * pq.ms f = 3 * pq.MHz n = (t*f).plain # n will be 6000 If the quantity is not dimensionless, a conversion error is raised. --- quantities/quantity.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/quantities/quantity.py b/quantities/quantity.py index e6046fd..bf8597b 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -250,6 +250,21 @@ def rescale_preferred(self): raise Exception("Preferred units for '%s' (or equivalent) not specified in " "quantites.quantity.PREFERRED." % self.dimensionality) + @property + def plain(self): + """ + Return a copy of the quantity as a plain number provided that the quantity + is dimensionless. For example: + ``` + import quantities as pq + t = 2 * pq.ms + f = 3 * pq.MHz + n = (t*f).plain # n will be 6000 + ``` + If the quantity is not dimensionless, a conversion error is raised. + """ + return self.rescale(unit_registry['dimensionless']).magnitude + @with_doc(np.ndarray.astype) def astype(self, dtype=None, **kwargs): '''Scalars are returned as scalar Quantity arrays.''' From 633cf9e409fbf7ed4cbbaf5ca4fb2a0c829c5cd0 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wagenaar" Date: Tue, 19 Nov 2024 15:10:06 -0800 Subject: [PATCH 204/212] =?UTF-8?q?Renamed=20the=20=E2=80=9Cplain=E2=80=9D?= =?UTF-8?q?=20property?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed the originally proposed “plain” property into “dimensionless_magnitude” as suggested. Improved documentation of the property. Added documentation to the “magnitude” property to clarify the distinction between the two. --- quantities/quantity.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/quantities/quantity.py b/quantities/quantity.py index bf8597b..74a425f 100644 --- a/quantities/quantity.py +++ b/quantities/quantity.py @@ -145,6 +145,16 @@ def _reference(self): @property def magnitude(self): + """ + Returns a view onto the numerical value of the quantity, stripping + away the associated units. For example: + ``` + import quantities as pq + t = 2 * pq.millisecond + n = t.magnitude # n will be 2 (not 0.002) + ``` + See also: dimensionless_magnitude. + """ return self.view(type=np.ndarray) @property @@ -251,17 +261,19 @@ def rescale_preferred(self): "quantites.quantity.PREFERRED." % self.dimensionality) @property - def plain(self): + def dimensionless_magnitude(self): """ - Return a copy of the quantity as a plain number provided that the quantity - is dimensionless. For example: + Returns the numerical value of a dimensionless quantity in the form of + a numpy array. Any decimal prefixes are normalized away first. + For example: ``` import quantities as pq t = 2 * pq.ms f = 3 * pq.MHz - n = (t*f).plain # n will be 6000 + n = (t*f).dimensionless_magnitude # n will be 6000 (not 6) ``` If the quantity is not dimensionless, a conversion error is raised. + See also: magnitude. """ return self.rescale(unit_registry['dimensionless']).magnitude From 856db64207e002d7c816c484bf396e9f6da6d746 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wagenaar" Date: Wed, 20 Nov 2024 12:17:14 -0800 Subject: [PATCH 205/212] Added test coverage for dimensionless_magnitude --- quantities/tests/test_methods.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index ba184a3..cf548ec 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -356,3 +356,9 @@ def test_rescale_integer_argument(self): Quantity(10, pq.deg).rescale(pq.rad), np.pi/18*pq.rad ) + + def test_dimensionless_magnitude(self): + self.assertQuantityEqual((self.q / pq.cm).dimensionless_magnitude, + 100 * self.q.magnitude) + self.assertRaises(ValueError, lambda x: x.dimensionless_magnitude, + self.q) From 966deea29e3bc17cb234cb010d23fbe39282065a Mon Sep 17 00:00:00 2001 From: "Daniel A. Wagenaar" Date: Wed, 20 Nov 2024 12:19:25 -0800 Subject: [PATCH 206/212] Added to test coverage for dimensionless_magnitude --- quantities/tests/test_methods.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quantities/tests/test_methods.py b/quantities/tests/test_methods.py index cf548ec..ff84210 100644 --- a/quantities/tests/test_methods.py +++ b/quantities/tests/test_methods.py @@ -358,6 +358,7 @@ def test_rescale_integer_argument(self): ) def test_dimensionless_magnitude(self): + self.assertEqual((pq.kg/pq.g).dimensionless_magnitude, 1000) self.assertQuantityEqual((self.q / pq.cm).dimensionless_magnitude, 100 * self.q.magnitude) self.assertRaises(ValueError, lambda x: x.dimensionless_magnitude, From b60a883bb594b71569c4dfdc598f5602bf5015b5 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Tue, 3 Dec 2024 13:59:13 +0100 Subject: [PATCH 207/212] An alternative approach to avoiding arbitrary evaluation of code when parsing strings as units. Based on https://stackoverflow.com/a/11952618 by https://stackoverflow.com/users/567292/ecatmur Fixes #250 Also added test for the original issue: #221 --- .github/workflows/test.yml | 1 + quantities/registry.py | 48 ++++++++++++++++++++-------------- quantities/tests/test_units.py | 8 ++++++ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a445ed1..62a451a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -118,6 +118,7 @@ jobs: python -m pip install -U setuptools python -m pip install -U wheel python -m pip install "numpy==${{ matrix.numpy-version }}" + python -m pip install -U pytest python -m pip install -U mypy - name: Install diff --git a/quantities/registry.py b/quantities/registry.py index 4f9f3c7..d03433e 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -1,41 +1,49 @@ """ """ +import ast import re -import builtins class UnitRegistry: + # Note that this structure ensures that UnitRegistry behaves as a singleton class __Registry: __shared_state = {} + whitelist = ( + ast.Expression, + ast.Constant, + ast.Name, + ast.Load, + ast.BinOp, + ast.UnaryOp, + ast.operator, + ast.unaryop, + ast.Num, + ) def __init__(self): self.__dict__ = self.__shared_state self.__context = {} def __getitem__(self, string): - - # easy hack to prevent arbitrary evaluation of code - all_builtins = dir(builtins) - # because we have kilobytes, other bytes we have to remove bytes - all_builtins.remove("bytes") - # have to deal with octet as well - all_builtins.remove("oct") - # have to remove min which is short for minute - all_builtins.remove("min") - for builtin in all_builtins: - if builtin in string: - raise RuntimeError(f"String parsing error for `{string}`. Enter a string accepted by quantities") - - try: - return eval(string, self.__context) - except NameError: + tree = ast.parse(string, mode="eval") + valid = all(isinstance(node, self.whitelist) for node in ast.walk(tree)) + if valid: + try: + item = eval( + compile(tree, filename="", mode="eval"), + {"__builtins__": {}}, + self.__context, + ) + except NameError: + raise LookupError('Unable to parse units: "%s"' % string) + else: + return item + else: # could return self['UnitQuantity'](string) - raise LookupError( - 'Unable to parse units: "%s"'%string - ) + raise LookupError('Unable to parse units: "%s"' % string) def __setitem__(self, string, val): assert isinstance(string, str) diff --git a/quantities/tests/test_units.py b/quantities/tests/test_units.py index 2bbbf93..1912a3a 100644 --- a/quantities/tests/test_units.py +++ b/quantities/tests/test_units.py @@ -1,6 +1,9 @@ +import pytest + from .. import units as pq from .common import TestCase + class TestUnits(TestCase): def test_compound_units(self): @@ -30,3 +33,8 @@ def test_units_copy(self): self.assertQuantityEqual(pq.m.copy(), pq.m) pc_per_cc = pq.CompoundUnit("pc/cm**3") self.assertQuantityEqual(pc_per_cc.copy(), pc_per_cc) + + def test_code_injection(self): + with pytest.raises(LookupError) as exc_info: + pq.CompoundUnit("exec(\"print('Hello there.')\\nprint('General Wasabi!')\")") + assert "Wasabi" in str(exc_info.value) From 45d8a2ed1d6b448d03a599c6d576f03f96fda0a1 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 5 Feb 2025 17:44:14 +0100 Subject: [PATCH 208/212] remove ast.Num from whitelist as it is deprecated and will be removed in Python 3.14 Co-authored-by: Zach McKenzie <92116279+zm711@users.noreply.github.com> --- quantities/registry.py | 1 - 1 file changed, 1 deletion(-) diff --git a/quantities/registry.py b/quantities/registry.py index d03433e..0cb5082 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -20,7 +20,6 @@ class __Registry: ast.UnaryOp, ast.operator, ast.unaryop, - ast.Num, ) def __init__(self): From 9f212e9f1939b627ef5caf68bac07ddc9c7d14bb Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 5 Feb 2025 17:47:06 +0100 Subject: [PATCH 209/212] Add reference to Stack Overflow post --- quantities/registry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quantities/registry.py b/quantities/registry.py index 0cb5082..7603965 100644 --- a/quantities/registry.py +++ b/quantities/registry.py @@ -27,6 +27,8 @@ def __init__(self): self.__context = {} def __getitem__(self, string): + # This approach to avoiding arbitrary evaluation of code is based on https://stackoverflow.com/a/11952618 + # by https://stackoverflow.com/users/567292/ecatmur tree = ast.parse(string, mode="eval") valid = all(isinstance(node, self.whitelist) for node in ast.walk(tree)) if valid: From 6ad9e1600c09cb7908e48823a708fc8e99fc2037 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Wed, 5 Feb 2025 18:20:35 +0100 Subject: [PATCH 210/212] Add kilonewton cf #252 --- quantities/units/force.py | 6 ++++++ quantities/units/force.pyi | 1 + 2 files changed, 7 insertions(+) diff --git a/quantities/units/force.py b/quantities/units/force.py index 115080f..c903969 100644 --- a/quantities/units/force.py +++ b/quantities/units/force.py @@ -14,6 +14,12 @@ symbol='N', aliases=['newtons'] ) +kN = kilonewton = UnitQuantity( + 'kilonewton', + 1000*N, + symbol='kN', + aliases=['kilonewtons'] +) dyne = UnitQuantity( 'dyne', gram*cm/s**2, diff --git a/quantities/units/force.pyi b/quantities/units/force.pyi index 46062b7..43f52c6 100644 --- a/quantities/units/force.pyi +++ b/quantities/units/force.pyi @@ -2,6 +2,7 @@ from ..unitquantity import UnitQuantity N: UnitQuantity newton: UnitQuantity +kilonewton: UnitQuantity dyne: UnitQuantity pond: UnitQuantity kgf: UnitQuantity From d6bdce681b4bc33807ca73a3247b0295ec022c44 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 11 Apr 2025 16:02:51 +0200 Subject: [PATCH 211/212] Update workflow config to use more recent versions of actions --- .github/workflows/test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 62a451a..573dfa4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,10 +31,10 @@ jobs: numpy-version: "2.0" os: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -44,7 +44,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: @@ -91,10 +91,10 @@ jobs: numpy-version: "1.25" os: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -104,7 +104,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: From 5015b5dd2b372e7f1914b67d9e4107da736db851 Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Fri, 11 Apr 2025 16:13:17 +0200 Subject: [PATCH 212/212] updated changelog --- CHANGES.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index c3fb6f3..3e4f48d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,14 @@ CHANGES ======= +------ +0.16.2 +------ + +- Added a property `dimensionless_magnitude` to the Quantity class ([`PR#248 `_]) +- Implemented an alternative approach to avoiding arbitrary evaluation of code when parsing strings as units, which fixes some bugs introduced in v0.16.1 ([`PR#251 `_]) +- Added the kilonewton + ------ 0.16.1 ------