copulas 0.11.1__tar.gz → 0.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of copulas might be problematic. Click here for more details.

Files changed (40) hide show
  1. {copulas-0.11.1 → copulas-0.12.0}/PKG-INFO +1 -1
  2. copulas-0.12.0/copulas/__init__.py +91 -0
  3. {copulas-0.11.1 → copulas-0.12.0}/copulas/bivariate/__init__.py +1 -1
  4. {copulas-0.11.1 → copulas-0.12.0}/copulas/bivariate/base.py +2 -1
  5. {copulas-0.11.1 → copulas-0.12.0}/copulas/bivariate/frank.py +1 -1
  6. {copulas-0.11.1 → copulas-0.12.0}/copulas/datasets.py +1 -1
  7. copulas-0.12.0/copulas/errors.py +5 -0
  8. {copulas-0.11.1 → copulas-0.12.0}/copulas/multivariate/base.py +2 -1
  9. {copulas-0.11.1 → copulas-0.12.0}/copulas/multivariate/gaussian.py +72 -46
  10. {copulas-0.11.1 → copulas-0.12.0}/copulas/multivariate/tree.py +1 -1
  11. {copulas-0.11.1 → copulas-0.12.0}/copulas/multivariate/vine.py +6 -7
  12. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/base.py +3 -3
  13. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/gaussian_kde.py +1 -1
  14. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/selection.py +1 -1
  15. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/truncated_gaussian.py +1 -1
  16. copulas-0.11.1/copulas/__init__.py → copulas-0.12.0/copulas/utils.py +7 -96
  17. {copulas-0.11.1 → copulas-0.12.0}/copulas.egg-info/PKG-INFO +1 -1
  18. {copulas-0.11.1 → copulas-0.12.0}/copulas.egg-info/SOURCES.txt +2 -0
  19. {copulas-0.11.1 → copulas-0.12.0}/pyproject.toml +21 -7
  20. {copulas-0.11.1 → copulas-0.12.0}/LICENSE +0 -0
  21. {copulas-0.11.1 → copulas-0.12.0}/README.md +0 -0
  22. {copulas-0.11.1 → copulas-0.12.0}/copulas/bivariate/clayton.py +0 -0
  23. {copulas-0.11.1 → copulas-0.12.0}/copulas/bivariate/gumbel.py +0 -0
  24. {copulas-0.11.1 → copulas-0.12.0}/copulas/bivariate/independence.py +0 -0
  25. {copulas-0.11.1 → copulas-0.12.0}/copulas/bivariate/utils.py +0 -0
  26. {copulas-0.11.1 → copulas-0.12.0}/copulas/multivariate/__init__.py +0 -0
  27. {copulas-0.11.1 → copulas-0.12.0}/copulas/optimize/__init__.py +0 -0
  28. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/__init__.py +0 -0
  29. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/beta.py +0 -0
  30. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/gamma.py +0 -0
  31. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/gaussian.py +0 -0
  32. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/log_laplace.py +0 -0
  33. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/student_t.py +0 -0
  34. {copulas-0.11.1 → copulas-0.12.0}/copulas/univariate/uniform.py +0 -0
  35. {copulas-0.11.1 → copulas-0.12.0}/copulas/visualization.py +0 -0
  36. {copulas-0.11.1 → copulas-0.12.0}/copulas.egg-info/dependency_links.txt +0 -0
  37. {copulas-0.11.1 → copulas-0.12.0}/copulas.egg-info/requires.txt +0 -0
  38. {copulas-0.11.1 → copulas-0.12.0}/copulas.egg-info/top_level.txt +0 -0
  39. {copulas-0.11.1 → copulas-0.12.0}/setup.cfg +0 -0
  40. {copulas-0.11.1 → copulas-0.12.0}/tests/test_tasks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copulas
3
- Version: 0.11.1
3
+ Version: 0.12.0
4
4
  Summary: Create tabular synthetic data using copulas-based modeling.
5
5
  Author-email: "DataCebo, Inc." <info@sdv.dev>
6
6
  License: BSL-1.1
@@ -0,0 +1,91 @@
1
+ """Top-level package for Copulas."""
2
+
3
+ __author__ = 'DataCebo, Inc.'
4
+ __email__ = 'info@sdv.dev'
5
+ __version__ = '0.12.0'
6
+
7
+ import sys
8
+ import warnings
9
+ from copy import deepcopy
10
+ from importlib.metadata import entry_points
11
+ from operator import attrgetter
12
+ from types import ModuleType
13
+
14
+
15
+ def _get_addon_target(addon_path_name):
16
+ """Find the target object for the add-on.
17
+
18
+ Args:
19
+ addon_path_name (str):
20
+ The add-on's name. The add-on's name should be the full path of valid Python
21
+ identifiers (i.e. importable.module:object.attr).
22
+
23
+ Returns:
24
+ tuple:
25
+ * object:
26
+ The base module or object the add-on should be added to.
27
+ * str:
28
+ The name the add-on should be added to under the module or object.
29
+ """
30
+ module_path, _, object_path = addon_path_name.partition(':')
31
+ module_path = module_path.split('.')
32
+
33
+ if module_path[0] != __name__:
34
+ msg = f"expected base module to be '{__name__}', found '{module_path[0]}'"
35
+ raise AttributeError(msg)
36
+
37
+ target_base = sys.modules[__name__]
38
+ for submodule in module_path[1:-1]:
39
+ target_base = getattr(target_base, submodule)
40
+
41
+ addon_name = module_path[-1]
42
+ if object_path:
43
+ if len(module_path) > 1 and not hasattr(target_base, module_path[-1]):
44
+ msg = f"cannot add '{object_path}' to unknown submodule '{'.'.join(module_path)}'"
45
+ raise AttributeError(msg)
46
+
47
+ if len(module_path) > 1:
48
+ target_base = getattr(target_base, module_path[-1])
49
+
50
+ split_object = object_path.split('.')
51
+ addon_name = split_object[-1]
52
+
53
+ if len(split_object) > 1:
54
+ target_base = attrgetter('.'.join(split_object[:-1]))(target_base)
55
+
56
+ return target_base, addon_name
57
+
58
+
59
+ def _find_addons():
60
+ """Find and load all copulas add-ons."""
61
+ group = 'copulas_modules'
62
+ try:
63
+ eps = entry_points(group=group)
64
+ except TypeError:
65
+ # Load-time selection requires Python >= 3.10 or importlib_metadata >= 3.6
66
+ eps = entry_points().get(group, [])
67
+
68
+ for entry_point in eps:
69
+ try:
70
+ addon = entry_point.load()
71
+ except Exception as e: # pylint: disable=broad-exception-caught
72
+ msg = f'Failed to load "{entry_point.name}" from "{entry_point.value}" with error:\n{e}'
73
+ warnings.warn(msg)
74
+ continue
75
+
76
+ try:
77
+ addon_target, addon_name = _get_addon_target(entry_point.name)
78
+ except AttributeError as error:
79
+ msg = f"Failed to set '{entry_point.name}': {error}."
80
+ warnings.warn(msg)
81
+ continue
82
+
83
+ if isinstance(addon, ModuleType):
84
+ addon_module_name = f'{addon_target.__name__}.{addon_name}'
85
+ if addon_module_name not in sys.modules:
86
+ sys.modules[addon_module_name] = addon
87
+
88
+ setattr(addon_target, addon_name, addon)
89
+
90
+
91
+ _find_addons()
@@ -3,7 +3,7 @@
3
3
  import numpy as np
4
4
  import pandas as pd
5
5
 
6
- from copulas import EPSILON
6
+ from copulas.utils import EPSILON
7
7
  from copulas.bivariate.base import Bivariate, CopulaTypes
8
8
  from copulas.bivariate.clayton import Clayton
9
9
  from copulas.bivariate.frank import Frank
@@ -8,8 +8,9 @@ import numpy as np
8
8
  from scipy import stats
9
9
  from scipy.optimize import brentq
10
10
 
11
- from copulas import EPSILON, NotFittedError, random_state, validate_random_state
12
11
  from copulas.bivariate.utils import split_matrix
12
+ from copulas.errors import NotFittedError
13
+ from copulas.utils import EPSILON, random_state, validate_random_state
13
14
 
14
15
 
15
16
  class CopulaTypes(Enum):
@@ -6,9 +6,9 @@ import numpy as np
6
6
  import scipy.integrate as integrate
7
7
  from scipy.optimize import least_squares
8
8
 
9
- from copulas import EPSILON
10
9
  from copulas.bivariate.base import Bivariate, CopulaTypes
11
10
  from copulas.bivariate.utils import split_matrix
11
+ from copulas.utils import EPSILON
12
12
 
13
13
  MIN_FLOAT_LOG = np.log(sys.float_info.min)
14
14
  MAX_FLOAT_LOG = np.log(sys.float_info.max)
@@ -4,7 +4,7 @@ import numpy as np
4
4
  import pandas as pd
5
5
  from scipy import stats
6
6
 
7
- from copulas import set_random_state, validate_random_state
7
+ from copulas.utils import set_random_state, validate_random_state
8
8
 
9
9
 
10
10
  def _dummy_fn(state):
@@ -0,0 +1,5 @@
1
+ """Copulas Exceptions."""
2
+
3
+
4
+ class NotFittedError(Exception):
5
+ """NotFittedError class."""
@@ -4,7 +4,8 @@ import pickle
4
4
 
5
5
  import numpy as np
6
6
 
7
- from copulas import NotFittedError, get_instance, validate_random_state
7
+ from copulas.errors import NotFittedError
8
+ from copulas.utils import get_instance, validate_random_state
8
9
 
9
10
 
10
11
  class Multivariate(object):
@@ -7,7 +7,9 @@ import numpy as np
7
7
  import pandas as pd
8
8
  from scipy import stats
9
9
 
10
- from copulas import (
10
+ from copulas.multivariate.base import Multivariate
11
+ from copulas.univariate import GaussianUnivariate, Univariate
12
+ from copulas.utils import (
11
13
  EPSILON,
12
14
  check_valid_values,
13
15
  get_instance,
@@ -16,8 +18,6 @@ from copulas import (
16
18
  store_args,
17
19
  validate_random_state,
18
20
  )
19
- from copulas.multivariate.base import Multivariate
20
- from copulas.univariate import GaussianUnivariate, Univariate
21
21
 
22
22
  LOGGER = logging.getLogger(__name__)
23
23
  DEFAULT_DISTRIBUTION = Univariate
@@ -70,26 +70,6 @@ class GaussianMultivariate(Multivariate):
70
70
 
71
71
  return stats.norm.ppf(np.column_stack(U))
72
72
 
73
- def _get_correlation(self, X):
74
- """Compute correlation matrix with transformed data.
75
-
76
- Args:
77
- X (numpy.ndarray):
78
- Data for which the correlation needs to be computed.
79
-
80
- Returns:
81
- numpy.ndarray:
82
- computed correlation matrix.
83
- """
84
- result = self._transform_to_normal(X)
85
- correlation = pd.DataFrame(data=result).corr().to_numpy()
86
- correlation = np.nan_to_num(correlation, nan=0.0)
87
- # If singular, add some noise to the diagonal
88
- if np.linalg.cond(correlation) > 1.0 / sys.float_info.epsilon:
89
- correlation = correlation + np.identity(correlation.shape[0]) * EPSILON
90
-
91
- return pd.DataFrame(correlation, index=self.columns, columns=self.columns)
92
-
93
73
  @check_valid_values
94
74
  def fit(self, X):
95
75
  """Compute the distribution for each variable and then its correlation matrix.
@@ -100,42 +80,88 @@ class GaussianMultivariate(Multivariate):
100
80
  """
101
81
  LOGGER.info('Fitting %s', self)
102
82
 
83
+ # Validate the input data
84
+ X = self._validate_input(X)
85
+ columns, univariates = self._fit_columns(X)
86
+
87
+ self.columns = columns
88
+ self.univariates = univariates
89
+
90
+ LOGGER.debug('Computing correlation.')
91
+ self.correlation = self._get_correlation(X)
92
+ self.fitted = True
93
+ LOGGER.debug('GaussianMultivariate fitted successfully')
94
+
95
+ def _validate_input(self, X):
96
+ """Validate the input data."""
103
97
  if not isinstance(X, pd.DataFrame):
104
98
  X = pd.DataFrame(X)
105
99
 
100
+ return X
101
+
102
+ def _fit_columns(self, X):
103
+ """Fit each column to its distribution."""
106
104
  columns = []
107
105
  univariates = []
108
106
  for column_name, column in X.items():
109
- if isinstance(self.distribution, dict):
110
- distribution = self.distribution.get(column_name, DEFAULT_DISTRIBUTION)
111
- else:
112
- distribution = self.distribution
113
-
107
+ distribution = self._get_distribution_for_column(column_name)
114
108
  LOGGER.debug('Fitting column %s to %s', column_name, distribution)
115
109
 
116
- univariate = get_instance(distribution)
117
- try:
118
- univariate.fit(column)
119
- except BaseException:
120
- log_message = (
121
- f'Unable to fit to a {distribution} distribution for column {column_name}. '
122
- 'Using a Gaussian distribution instead.'
123
- )
124
- LOGGER.info(log_message)
125
- univariate = GaussianUnivariate()
126
- univariate.fit(column)
127
-
110
+ univariate = self._fit_column(column, distribution, column_name)
128
111
  columns.append(column_name)
129
112
  univariates.append(univariate)
130
113
 
131
- self.columns = columns
132
- self.univariates = univariates
114
+ return columns, univariates
115
+
116
+ def _get_distribution_for_column(self, column_name):
117
+ """Retrieve the distribution for a given column name."""
118
+ if isinstance(self.distribution, dict):
119
+ return self.distribution.get(column_name, DEFAULT_DISTRIBUTION)
120
+
121
+ return self.distribution
122
+
123
+ def _fit_column(self, column, distribution, column_name):
124
+ """Fit a single column to its distribution with exception handling."""
125
+ univariate = get_instance(distribution)
126
+ try:
127
+ univariate.fit(column)
128
+ except Exception as error:
129
+ univariate = self._fit_with_fallback_distribution(
130
+ column, distribution, column_name, error
131
+ )
132
+
133
+ return univariate
134
+
135
+ def _fit_with_fallback_distribution(self, column, distribution, column_name, error):
136
+ """Fall back to fitting a Gaussian distribution and log the error."""
137
+ log_message = (
138
+ f'Unable to fit to a {distribution} distribution for column {column_name}. '
139
+ 'Using a Gaussian distribution instead.'
140
+ )
141
+ LOGGER.info(log_message)
142
+ univariate = GaussianUnivariate()
143
+ univariate.fit(column)
144
+ return univariate
133
145
 
134
- LOGGER.debug('Computing correlation')
135
- self.correlation = self._get_correlation(X)
136
- self.fitted = True
146
+ def _get_correlation(self, X):
147
+ """Compute correlation matrix with transformed data.
137
148
 
138
- LOGGER.debug('GaussianMultivariate fitted successfully')
149
+ Args:
150
+ X (numpy.ndarray):
151
+ Data for which the correlation needs to be computed.
152
+
153
+ Returns:
154
+ numpy.ndarray:
155
+ computed correlation matrix.
156
+ """
157
+ result = self._transform_to_normal(X)
158
+ correlation = pd.DataFrame(data=result).corr().to_numpy()
159
+ correlation = np.nan_to_num(correlation, nan=0.0)
160
+ # If singular, add some noise to the diagonal
161
+ if np.linalg.cond(correlation) > 1.0 / sys.float_info.epsilon:
162
+ correlation = correlation + np.identity(correlation.shape[0]) * EPSILON
163
+
164
+ return pd.DataFrame(correlation, index=self.columns, columns=self.columns)
139
165
 
140
166
  def probability_density(self, X):
141
167
  """Compute the probability density for each point in X.
@@ -6,9 +6,9 @@ from enum import Enum
6
6
  import numpy as np
7
7
  import scipy
8
8
 
9
- from copulas import EPSILON, get_qualified_name
10
9
  from copulas.bivariate.base import Bivariate
11
10
  from copulas.multivariate.base import Multivariate
11
+ from copulas.utils import EPSILON, get_qualified_name
12
12
 
13
13
  LOGGER = logging.getLogger(__name__)
14
14
 
@@ -7,7 +7,11 @@ import warnings
7
7
  import numpy as np
8
8
  import pandas as pd
9
9
 
10
- from copulas import (
10
+ from copulas.bivariate.base import Bivariate, CopulaTypes
11
+ from copulas.multivariate.base import Multivariate
12
+ from copulas.multivariate.tree import Tree, get_tree
13
+ from copulas.univariate.gaussian_kde import GaussianKDE
14
+ from copulas.utils import (
11
15
  EPSILON,
12
16
  check_valid_values,
13
17
  get_qualified_name,
@@ -15,10 +19,6 @@ from copulas import (
15
19
  store_args,
16
20
  validate_random_state,
17
21
  )
18
- from copulas.bivariate.base import Bivariate, CopulaTypes
19
- from copulas.multivariate.base import Multivariate
20
- from copulas.multivariate.tree import Tree, get_tree
21
- from copulas.univariate.gaussian_kde import GaussianKDE
22
22
 
23
23
  LOGGER = logging.getLogger(__name__)
24
24
 
@@ -76,8 +76,7 @@ class VineCopula(Multivariate):
76
76
  def __init__(self, vine_type, random_state=None):
77
77
  if sys.version_info > (3, 8):
78
78
  warnings.warn(
79
- 'Vines have not been fully tested on Python >= 3.8 and might '
80
- 'produce wrong results.'
79
+ 'Vines have not been fully tested on Python >= 3.8 and might produce wrong results.'
81
80
  )
82
81
 
83
82
  self.random_state = validate_random_state(random_state)
@@ -6,15 +6,15 @@ from enum import Enum
6
6
 
7
7
  import numpy as np
8
8
 
9
- from copulas import (
10
- NotFittedError,
9
+ from copulas.errors import NotFittedError
10
+ from copulas.univariate.selection import select_univariate
11
+ from copulas.utils import (
11
12
  get_instance,
12
13
  get_qualified_name,
13
14
  random_state,
14
15
  store_args,
15
16
  validate_random_state,
16
17
  )
17
- from copulas.univariate.selection import select_univariate
18
18
 
19
19
 
20
20
  class ParametricType(Enum):
@@ -4,9 +4,9 @@ import numpy as np
4
4
  from scipy.special import ndtr
5
5
  from scipy.stats import gaussian_kde
6
6
 
7
- from copulas import EPSILON, random_state, store_args, validate_random_state
8
7
  from copulas.optimize import bisect, chandrupatla
9
8
  from copulas.univariate.base import BoundedType, ParametricType, ScipyModel
9
+ from copulas.utils import EPSILON, random_state, store_args, validate_random_state
10
10
 
11
11
 
12
12
  class GaussianKDE(ScipyModel):
@@ -3,7 +3,7 @@
3
3
  import numpy as np
4
4
  from scipy.stats import kstest
5
5
 
6
- from copulas import get_instance
6
+ from copulas.utils import get_instance
7
7
 
8
8
 
9
9
  def select_univariate(X, candidates):
@@ -6,8 +6,8 @@ import numpy as np
6
6
  from scipy.optimize import fmin_slsqp
7
7
  from scipy.stats import truncnorm
8
8
 
9
- from copulas import EPSILON, store_args, validate_random_state
10
9
  from copulas.univariate.base import BoundedType, ParametricType, ScipyModel
10
+ from copulas.utils import EPSILON, store_args, validate_random_state
11
11
 
12
12
 
13
13
  class TruncatedGaussian(ScipyModel):
@@ -1,18 +1,9 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- """Top-level package for Copulas."""
4
-
5
- __author__ = 'DataCebo, Inc.'
6
- __email__ = 'info@sdv.dev'
7
- __version__ = '0.11.1'
1
+ """Utils module."""
8
2
 
9
3
  import contextlib
10
4
  import importlib
11
- import sys
12
- import warnings
13
5
  from copy import deepcopy
14
- from importlib.metadata import entry_points
15
- from operator import attrgetter
6
+ from functools import wraps
16
7
 
17
8
  import numpy as np
18
9
  import pandas as pd
@@ -20,10 +11,6 @@ import pandas as pd
20
11
  EPSILON = np.finfo(np.float32).eps
21
12
 
22
13
 
23
- class NotFittedError(Exception):
24
- """NotFittedError class."""
25
-
26
-
27
14
  @contextlib.contextmanager
28
15
  def set_random_state(random_state, set_model_random_state):
29
16
  """Context manager for managing the random state.
@@ -35,7 +22,6 @@ def set_random_state(random_state, set_model_random_state):
35
22
  Function to set the random state on the model.
36
23
  """
37
24
  original_state = np.random.get_state()
38
-
39
25
  np.random.set_state(random_state.get_state())
40
26
 
41
27
  try:
@@ -55,10 +41,10 @@ def random_state(function):
55
41
  The function to wrap around.
56
42
  """
57
43
 
44
+ @wraps(function)
58
45
  def wrapper(self, *args, **kwargs):
59
46
  if self.random_state is None:
60
47
  return function(self, *args, **kwargs)
61
-
62
48
  else:
63
49
  with set_random_state(self.random_state, self.set_random_state):
64
50
  return function(self, *args, **kwargs)
@@ -123,6 +109,7 @@ def store_args(__init__):
123
109
  callable: Decorated ``__init__`` function.
124
110
  """
125
111
 
112
+ @wraps(__init__)
126
113
  def new__init__(self, *args, **kwargs):
127
114
  args_copy = deepcopy(args)
128
115
  kwargs_copy = deepcopy(kwargs)
@@ -138,7 +125,6 @@ def get_qualified_name(_object):
138
125
  module = _object.__module__
139
126
  if hasattr(_object, '__name__'):
140
127
  _class = _object.__name__
141
-
142
128
  else:
143
129
  _class = _object.__class__.__name__
144
130
 
@@ -184,6 +170,7 @@ def vectorize(function):
184
170
 
185
171
  """
186
172
 
173
+ @wraps(function)
187
174
  def decorated(self, X, *args, **kwargs):
188
175
  if not isinstance(X, np.ndarray):
189
176
  return function(self, X, *args, **kwargs)
@@ -195,11 +182,9 @@ def vectorize(function):
195
182
  return np.fromiter(
196
183
  (function(self, *x, *args, **kwargs) for x in X), np.dtype('float64')
197
184
  )
198
-
199
185
  else:
200
186
  raise ValueError('Arrays of dimensionality higher than 2 are not supported.')
201
187
 
202
- decorated.__doc__ = function.__doc__
203
188
  return decorated
204
189
 
205
190
 
@@ -213,6 +198,7 @@ def scalarize(function):
213
198
  callable: Decorated function that accepts and returns scalars.
214
199
  """
215
200
 
201
+ @wraps(function)
216
202
  def decorated(self, X, *args, **kwargs):
217
203
  scalar = not isinstance(X, np.ndarray)
218
204
 
@@ -225,7 +211,6 @@ def scalarize(function):
225
211
 
226
212
  return result
227
213
 
228
- decorated.__doc__ = function.__doc__
229
214
  return decorated
230
215
 
231
216
 
@@ -242,10 +227,10 @@ def check_valid_values(function):
242
227
  ValueError: If there are missing or invalid values or if the dataset is empty.
243
228
  """
244
229
 
230
+ @wraps(function)
245
231
  def decorated(self, X, *args, **kwargs):
246
232
  if isinstance(X, pd.DataFrame):
247
233
  W = X.to_numpy()
248
-
249
234
  else:
250
235
  W = X
251
236
 
@@ -261,77 +246,3 @@ def check_valid_values(function):
261
246
  return function(self, X, *args, **kwargs)
262
247
 
263
248
  return decorated
264
-
265
-
266
- def _get_addon_target(addon_path_name):
267
- """Find the target object for the add-on.
268
-
269
- Args:
270
- addon_path_name (str):
271
- The add-on's name. The add-on's name should be the full path of valid Python
272
- identifiers (i.e. importable.module:object.attr).
273
-
274
- Returns:
275
- tuple:
276
- * object:
277
- The base module or object the add-on should be added to.
278
- * str:
279
- The name the add-on should be added to under the module or object.
280
- """
281
- module_path, _, object_path = addon_path_name.partition(':')
282
- module_path = module_path.split('.')
283
-
284
- if module_path[0] != __name__:
285
- msg = f"expected base module to be '{__name__}', found '{module_path[0]}'"
286
- raise AttributeError(msg)
287
-
288
- target_base = sys.modules[__name__]
289
- for submodule in module_path[1:-1]:
290
- target_base = getattr(target_base, submodule)
291
-
292
- addon_name = module_path[-1]
293
- if object_path:
294
- if len(module_path) > 1 and not hasattr(target_base, module_path[-1]):
295
- msg = f"cannot add '{object_path}' to unknown submodule '{'.'.join(module_path)}'"
296
- raise AttributeError(msg)
297
-
298
- if len(module_path) > 1:
299
- target_base = getattr(target_base, module_path[-1])
300
-
301
- split_object = object_path.split('.')
302
- addon_name = split_object[-1]
303
-
304
- if len(split_object) > 1:
305
- target_base = attrgetter('.'.join(split_object[:-1]))(target_base)
306
-
307
- return target_base, addon_name
308
-
309
-
310
- def _find_addons():
311
- """Find and load all copulas add-ons."""
312
- group = 'copulas_modules'
313
- try:
314
- eps = entry_points(group=group)
315
- except TypeError:
316
- # Load-time selection requires Python >= 3.10 or importlib_metadata >= 3.6
317
- eps = entry_points().get(group, [])
318
-
319
- for entry_point in eps:
320
- try:
321
- addon = entry_point.load()
322
- except Exception: # pylint: disable=broad-exception-caught
323
- msg = f'Failed to load "{entry_point.name}" from "{entry_point.value}".'
324
- warnings.warn(msg)
325
- continue
326
-
327
- try:
328
- addon_target, addon_name = _get_addon_target(entry_point.name)
329
- except AttributeError as error:
330
- msg = f"Failed to set '{entry_point.name}': {error}."
331
- warnings.warn(msg)
332
- continue
333
-
334
- setattr(addon_target, addon_name, addon)
335
-
336
-
337
- _find_addons()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copulas
3
- Version: 0.11.1
3
+ Version: 0.12.0
4
4
  Summary: Create tabular synthetic data using copulas-based modeling.
5
5
  Author-email: "DataCebo, Inc." <info@sdv.dev>
6
6
  License: BSL-1.1
@@ -3,6 +3,8 @@ README.md
3
3
  pyproject.toml
4
4
  copulas/__init__.py
5
5
  copulas/datasets.py
6
+ copulas/errors.py
7
+ copulas/utils.py
6
8
  copulas/visualization.py
7
9
  copulas.egg-info/PKG-INFO
8
10
  copulas.egg-info/SOURCES.txt
@@ -149,7 +149,7 @@ namespaces = false
149
149
  ]
150
150
 
151
151
  [tool.bumpversion]
152
- current_version = "0.11.1"
152
+ current_version = "0.12.0"
153
153
  commit = true
154
154
  tag = true
155
155
  parse = '(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<candidate>\d+))?'
@@ -182,7 +182,8 @@ exclude = [
182
182
  ".git",
183
183
  "__pycache__",
184
184
  ".ipynb_checkpoints",
185
- "*.ipynb"
185
+ "*.ipynb",
186
+ "tasks.py",
186
187
  ]
187
188
 
188
189
  [tool.ruff.lint]
@@ -192,14 +193,23 @@ select = [
192
193
  # Pycodestyle
193
194
  "E",
194
195
  "W",
195
- "D200",
196
+ # pydocstyle
197
+ "D",
196
198
  # isort
197
199
  "I001",
200
+ # print statements
201
+ "T201",
202
+ # pandas-vet
203
+ "PD",
204
+ # numpy 2.0
205
+ "NPY201"
198
206
  ]
199
207
  ignore = [
200
- "E501",
208
+ # pydocstyle
201
209
  "D107", # Missing docstring in __init__
202
210
  "D417", # Missing argument descriptions in the docstring, this is a bug from pydocstyle: https://github.com/PyCQA/pydocstyle/issues/449
211
+ "PD901",
212
+ "PD101",
203
213
  ]
204
214
 
205
215
  [tool.ruff.format]
@@ -209,14 +219,18 @@ preview = true
209
219
  docstring-code-format = true
210
220
  docstring-code-line-length = "dynamic"
211
221
 
212
- [tool.ruff.lint.pep8-naming]
213
- extend-ignore-names = ["X", "C", "X_padded", "Y", "Y_padded"]
214
-
215
222
  [tool.ruff.lint.isort]
216
223
  known-first-party = ["copulas"]
224
+ lines-between-types = 0
217
225
 
218
226
  [tool.ruff.lint.per-file-ignores]
219
227
  "__init__.py" = ["F401", "E402", "F403", "F405", "E501", "I001"]
228
+ "errors.py" = ["D105"]
229
+ "tests/**.py" = ["D"]
220
230
 
221
231
  [tool.ruff.lint.pydocstyle]
222
232
  convention = "google"
233
+
234
+ [tool.ruff.lint.pycodestyle]
235
+ max-doc-length = 100
236
+ max-line-length = 100
File without changes
File without changes
File without changes
File without changes