wbportfolio 1.55.9__py2.py3-none-any.whl → 1.55.10rc0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. wbportfolio/api_clients/ubs.py +11 -9
  2. wbportfolio/contrib/company_portfolio/configs/display.py +4 -4
  3. wbportfolio/contrib/company_portfolio/configs/previews.py +3 -3
  4. wbportfolio/contrib/company_portfolio/filters.py +10 -10
  5. wbportfolio/contrib/company_portfolio/scripts.py +7 -2
  6. wbportfolio/factories/product_groups.py +3 -3
  7. wbportfolio/factories/products.py +3 -3
  8. wbportfolio/import_export/handlers/asset_position.py +3 -3
  9. wbportfolio/import_export/handlers/dividend.py +1 -1
  10. wbportfolio/import_export/handlers/fees.py +2 -2
  11. wbportfolio/import_export/handlers/trade.py +3 -3
  12. wbportfolio/import_export/parsers/default_mapping.py +1 -1
  13. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +2 -2
  14. wbportfolio/import_export/parsers/jpmorgan/fees.py +2 -2
  15. wbportfolio/import_export/parsers/jpmorgan/strategy.py +2 -2
  16. wbportfolio/import_export/parsers/jpmorgan/valuation.py +2 -2
  17. wbportfolio/import_export/parsers/leonteq/trade.py +2 -1
  18. wbportfolio/import_export/parsers/natixis/utils.py +6 -2
  19. wbportfolio/import_export/parsers/sg_lux/equity.py +4 -3
  20. wbportfolio/import_export/parsers/sg_lux/sylk.py +12 -11
  21. wbportfolio/import_export/parsers/sg_lux/valuation.py +4 -2
  22. wbportfolio/import_export/parsers/societe_generale/strategy.py +3 -3
  23. wbportfolio/import_export/parsers/tellco/customer_trade.py +2 -1
  24. wbportfolio/import_export/parsers/tellco/valuation.py +4 -3
  25. wbportfolio/import_export/parsers/ubs/equity.py +2 -1
  26. wbportfolio/import_export/parsers/ubs/valuation.py +2 -1
  27. wbportfolio/import_export/utils.py +3 -1
  28. wbportfolio/metric/backends/base.py +2 -2
  29. wbportfolio/models/asset.py +2 -1
  30. wbportfolio/models/custodians.py +3 -3
  31. wbportfolio/models/exceptions.py +1 -1
  32. wbportfolio/models/graphs/utils.py +11 -11
  33. wbportfolio/models/orders/order_proposals.py +2 -2
  34. wbportfolio/models/portfolio.py +7 -7
  35. wbportfolio/models/portfolio_relationship.py +6 -0
  36. wbportfolio/models/products.py +3 -0
  37. wbportfolio/models/rebalancing.py +3 -0
  38. wbportfolio/models/roles.py +4 -10
  39. wbportfolio/models/transactions/claim.py +6 -5
  40. wbportfolio/pms/typing.py +1 -1
  41. wbportfolio/rebalancing/models/market_capitalization_weighted.py +1 -5
  42. wbportfolio/risk_management/backends/controversy_portfolio.py +1 -1
  43. wbportfolio/risk_management/backends/instrument_list_portfolio.py +1 -1
  44. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +2 -2
  45. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -1
  46. wbportfolio/serializers/transactions/claim.py +2 -2
  47. wbportfolio/tests/models/test_portfolios.py +5 -5
  48. wbportfolio/tests/models/test_splits.py +1 -6
  49. wbportfolio/tests/viewsets/test_products.py +1 -0
  50. wbportfolio/viewsets/charts/assets.py +6 -4
  51. wbportfolio/viewsets/configs/buttons/mixins.py +2 -2
  52. wbportfolio/viewsets/configs/display/reconciliations.py +4 -4
  53. {wbportfolio-1.55.9.dist-info → wbportfolio-1.55.10rc0.dist-info}/METADATA +1 -1
  54. {wbportfolio-1.55.9.dist-info → wbportfolio-1.55.10rc0.dist-info}/RECORD +56 -56
  55. {wbportfolio-1.55.9.dist-info → wbportfolio-1.55.10rc0.dist-info}/WHEEL +0 -0
  56. {wbportfolio-1.55.9.dist-info → wbportfolio-1.55.10rc0.dist-info}/licenses/LICENSE +0 -0
@@ -62,21 +62,21 @@ class UBSNeoAPIClient:
62
62
  def _raise_for_status(self, response):
63
63
  try:
64
64
  response.raise_for_status()
65
- except HTTPError:
65
+ except HTTPError as e:
66
66
  json_response = response.json()
67
- raise HTTPError(json_response.get("errors", json_response.get("message")))
67
+ raise HTTPError(json_response.get("errors", json_response.get("message"))) from e
68
68
 
69
69
  def get_rebalance_service_status(self) -> dict:
70
70
  """Check API connection status."""
71
71
  url = f"{self.BASE_URL}/ged-amc/external/rebalance/v1/status"
72
- response = requests.get(url, headers=self._get_headers())
72
+ response = requests.get(url, headers=self._get_headers(), timeout=10)
73
73
  self._raise_for_status(response)
74
74
  return self._get_json(response)
75
75
 
76
76
  def get_rebalance_status_for_isin(self, isin: str) -> dict:
77
77
  """Check certificate accessibility and workflow status for given ISIN."""
78
78
  url = f"{self.BASE_URL}/ged-amc/external/rebalance/v1/status/{isin}"
79
- response = requests.get(url, headers=self._get_headers())
79
+ response = requests.get(url, headers=self._get_headers(), timeout=10)
80
80
  self._raise_for_status(response)
81
81
  return self._get_json(response)
82
82
 
@@ -90,7 +90,7 @@ class UBSNeoAPIClient:
90
90
  isin = self._validate_isin(isin, test=test)
91
91
  url = f"{self.BASE_URL}/ged-amc/external/rebalance/v1/submit/{isin}"
92
92
  payload = {"items": items}
93
- response = requests.post(url, json=payload, headers=self._get_headers())
93
+ response = requests.post(url, json=payload, headers=self._get_headers(), timeout=10)
94
94
  self._raise_for_status(response)
95
95
  return self._get_json(response)
96
96
 
@@ -99,7 +99,7 @@ class UBSNeoAPIClient:
99
99
  isin = self._validate_isin(isin)
100
100
  url = f"{self.BASE_URL}/ged-amc/external/rebalance/v1/savedraft/{isin}"
101
101
  payload = {"items": items}
102
- response = requests.post(url, json=payload, headers=self._get_headers())
102
+ response = requests.post(url, json=payload, headers=self._get_headers(), timeout=10)
103
103
  self._raise_for_status(response)
104
104
  return self._get_json(response)
105
105
 
@@ -107,20 +107,20 @@ class UBSNeoAPIClient:
107
107
  """Cancel a rebalance request."""
108
108
  isin = self._validate_isin(isin)
109
109
  url = f"{self.BASE_URL}/ged-amc/external/rebalance/v1/cancel/{isin}"
110
- response = requests.delete(url, headers=self._get_headers())
110
+ response = requests.delete(url, headers=self._get_headers(), timeout=10)
111
111
  self._raise_for_status(response)
112
112
  return self._get_json(response)
113
113
 
114
114
  def get_current_rebalance_request(self, isin: str) -> dict:
115
115
  """Fetch the current rebalance request for a certificate."""
116
116
  url = f"{self.BASE_URL}/ged-amc/external/rebalance/v1/currentRebalanceRequest/{isin}"
117
- response = requests.get(url, headers=self._get_headers())
117
+ response = requests.get(url, headers=self._get_headers(), timeout=10)
118
118
  self._raise_for_status(response)
119
119
  return self._get_json(response)
120
120
 
121
121
  def get_portfolio_at_date(self, isin: str, val_date: date) -> dict:
122
122
  url = f"https://neo.ubs.com/api/ged-amc/external/report/v1/valuation/{isin}/{val_date:%Y-%m-%d}"
123
- response = requests.get(url, headers=self._get_headers())
123
+ response = requests.get(url, headers=self._get_headers(), timeout=10)
124
124
  self._raise_for_status(response)
125
125
  return self._get_json(response)
126
126
 
@@ -130,6 +130,7 @@ class UBSNeoAPIClient:
130
130
  url,
131
131
  headers=self._get_headers(),
132
132
  params={"fromDate": from_date.strftime("%Y-%m-%d"), "toDate": to_date.strftime("%Y-%m-%d")},
133
+ timeout=10,
133
134
  )
134
135
  self._raise_for_status(response)
135
136
  return self._get_json(response)
@@ -140,6 +141,7 @@ class UBSNeoAPIClient:
140
141
  url,
141
142
  headers=self._get_headers(),
142
143
  params={"fromDate": from_date.strftime("%Y-%m-%d"), "toDate": to_date.strftime("%Y-%m-%d")},
144
+ timeout=10,
143
145
  )
144
146
  self._raise_for_status(response)
145
147
  return self._get_json(response)
@@ -1,8 +1,8 @@
1
1
  from typing import Optional
2
2
 
3
3
  from django.utils.translation import gettext as _
4
- from wbcore.contrib.directory.viewsets.display.entries import CompanyModelDisplay as CMD
5
- from wbcore.contrib.directory.viewsets.display.entries import PersonModelDisplay as PMD
4
+ from wbcore.contrib.directory.viewsets.display.entries import CompanyModelDisplay as BaseCompanyModelDisplay
5
+ from wbcore.contrib.directory.viewsets.display.entries import PersonModelDisplay as BasePersonModelDisplay
6
6
  from wbcore.metadata.configs import display as dp
7
7
  from wbcore.metadata.configs.display.instance_display import (
8
8
  Inline,
@@ -62,7 +62,7 @@ AUM_FIELDS = Section(
62
62
  )
63
63
 
64
64
 
65
- class CompanyModelDisplay(CMD):
65
+ class CompanyModelDisplay(BaseCompanyModelDisplay):
66
66
  def get_list_display(self) -> Optional[dp.ListDisplay]:
67
67
  list_display = super().get_list_display()
68
68
  list_display.fields = (
@@ -134,7 +134,7 @@ class CompanyModelDisplay(CMD):
134
134
  return instance_display
135
135
 
136
136
 
137
- class PersonModelDisplay(PMD):
137
+ class PersonModelDisplay(BasePersonModelDisplay):
138
138
  def get_list_display(self) -> Optional[dp.ListDisplay]:
139
139
  list_display = super().get_list_display()
140
140
 
@@ -1,3 +1,5 @@
1
+ from contextlib import suppress
2
+
1
3
  from wbcore.contrib.directory.viewsets.previews import EntryPreviewConfig
2
4
  from wbcore.contrib.icons import WBIcon
3
5
  from wbcore.metadata.configs import buttons as bt
@@ -17,12 +19,10 @@ class CompanyPreviewConfig(EntryPreviewConfig):
17
19
  ["asset_under_management", "invested_assets_under_management_usd"],
18
20
  [repeat_field(2, "potential")],
19
21
  ]
20
- try:
22
+ with suppress(Exception):
21
23
  entry = self.view.get_object()
22
24
  if entry.profile_image:
23
25
  fields.insert(0, [repeat_field(2, "profile_image")])
24
- except Exception:
25
- pass
26
26
 
27
27
  return create_simple_display(fields)
28
28
 
@@ -1,6 +1,6 @@
1
1
  from wbcore import filters
2
- from wbcore.contrib.directory.filters import CompanyFilter as CF
3
- from wbcore.contrib.directory.filters import PersonFilter as PF
2
+ from wbcore.contrib.directory.filters import CompanyFilter as BaseCompanyFilter
3
+ from wbcore.contrib.directory.filters import PersonFilter as BasePersonFilter
4
4
  from wbcore.contrib.directory.models import Company, Person
5
5
 
6
6
 
@@ -99,29 +99,29 @@ class EntryPortfolioFilter(filters.FilterSet):
99
99
  return queryset
100
100
 
101
101
 
102
- class CompanyFilter(CF, EntryPortfolioFilter):
102
+ class CompanyFilter(BaseCompanyFilter, EntryPortfolioFilter):
103
103
  @classmethod
104
104
  def get_filter_class_for_remote_filter(cls):
105
105
  """
106
106
  Define which filterset class sender to user for remote filter registration
107
107
  """
108
- return CF
108
+ return BaseCompanyFilter
109
109
 
110
- class Meta(CF.Meta):
110
+ class Meta(BaseCompanyFilter.Meta):
111
111
  fields = {
112
- **CF.Meta.fields,
112
+ **BaseCompanyFilter.Meta.fields,
113
113
  }
114
114
 
115
115
 
116
- class PersonFilter(PF, EntryPortfolioFilter):
116
+ class PersonFilter(BasePersonFilter, EntryPortfolioFilter):
117
117
  @classmethod
118
118
  def get_filter_class_for_remote_filter(cls):
119
119
  """
120
120
  Define which filterset class sender to user for remote filter registration
121
121
  """
122
- return PF
122
+ return BasePersonFilter
123
123
 
124
- class Meta(PF.Meta):
124
+ class Meta(BasePersonFilter.Meta):
125
125
  fields = {
126
- **PF.Meta.fields,
126
+ **BasePersonFilter.Meta.fields,
127
127
  }
@@ -1,8 +1,11 @@
1
+ import logging
1
2
  import re
2
3
 
3
4
  from wbcore.contrib.currency.models import Currency
4
5
  from wbcore.contrib.directory.models import Company
5
6
 
7
+ logger = logging.getLogger("pms")
8
+
6
9
 
7
10
  def get_currency_and_assets_under_management(
8
11
  aum_string, currency_mapping, default_currency, multiplier_mapping, default_multiplier
@@ -72,5 +75,7 @@ def assign_aum():
72
75
  portfolio_data.assets_under_management_currency = currency
73
76
  try:
74
77
  portfolio_data.save()
75
- except Exception:
76
- pass
78
+ except Exception as e:
79
+ logger.error(
80
+ f"while we try to save the customer portfolio aum data for {company}, we encounter the error: {e}"
81
+ )
@@ -21,10 +21,10 @@ class ProductGroupFactory(InstrumentFactory):
21
21
  paying_agent = factory.SubFactory("wbcore.contrib.directory.factories.entries.CompanyFactory")
22
22
 
23
23
  @factory.post_generation
24
- def create_initial_portfolio(product_group, *args, **kwargs):
25
- if product_group.id and not product_group.portfolios.exists():
24
+ def create_initial_portfolio(self, *args, **kwargs):
25
+ if self.id and not self.portfolios.exists():
26
26
  portfolio = PortfolioFactory.create()
27
- InstrumentPortfolioThroughModel.objects.create(instrument=product_group, portfolio=portfolio)
27
+ InstrumentPortfolioThroughModel.objects.create(instrument=self, portfolio=portfolio)
28
28
 
29
29
 
30
30
  class ProductGroupRepresentantFactory(factory.django.DjangoModelFactory):
@@ -40,10 +40,10 @@ class ProductFactory(InstrumentFactory):
40
40
  instrument_type = factory.LazyAttribute(lambda o: InstrumentTypeFactory.create(name="Product", key="product"))
41
41
 
42
42
  @factory.post_generation
43
- def create_initial_portfolio(product, *args, **kwargs):
44
- if product.id and not product.portfolios.exists():
43
+ def create_initial_portfolio(self, *args, **kwargs):
44
+ if self.id and not self.portfolios.exists():
45
45
  portfolio = PortfolioFactory.create()
46
- InstrumentPortfolioThroughModel.objects.create(instrument=product, portfolio=portfolio)
46
+ InstrumentPortfolioThroughModel.objects.create(instrument=self, portfolio=portfolio)
47
47
 
48
48
  # wbportfolio = factory.SubFactory(PortfolioFactory)
49
49
  # portfolio_computed = factory.SubFactory(PortfolioFactory)
@@ -78,14 +78,14 @@ class AssetPositionImportHandler(ImportExportHandler):
78
78
  data["initial_price"] = data["underlying_quote"].get_price(
79
79
  data["date"], price_date_timedelta=self.MAX_PRICE_DATE_TIMEDELTA
80
80
  )
81
- except ValueError:
82
- raise DeserializationError("Price not provided but can not be found automatically")
81
+ except ValueError as e:
82
+ raise DeserializationError("Price not provided but can not be found automatically") from e
83
83
 
84
84
  # number type deserialization and sanitization
85
85
  # ensure the provided Decimal field are of type Decimal
86
86
  decimal_fields = ["initial_currency_fx_rate", "initial_price", "initial_shares", "weighting"]
87
87
  for field in decimal_fields:
88
- if not (value := data.get(field, None)) is None:
88
+ if (value := data.get(field, None)) is not None:
89
89
  data[field] = Decimal(value)
90
90
 
91
91
  if data["weighting"] == 0:
@@ -36,7 +36,7 @@ class DividendImportHandler(ImportExportHandler):
36
36
  data["currency"] = self.currency_handler.process_object(data["currency"], read_only=True)[0]
37
37
 
38
38
  for field in self.model._meta.get_fields():
39
- if not (value := data.get(field.name, None)) is None and isinstance(field, models.DecimalField):
39
+ if (value := data.get(field.name, None)) is not None and isinstance(field, models.DecimalField):
40
40
  q = 1 / (math.pow(10, 4))
41
41
  data[field.name] = Decimal(value).quantize(Decimal(str(q)))
42
42
 
@@ -24,8 +24,8 @@ class FeesImportHandler(ImportExportHandler):
24
24
  data["product"] = Product.objects.get(**product_data)
25
25
  else:
26
26
  data["product"] = Product.objects.get(id=product_data)
27
- except Product.DoesNotExist:
28
- raise DeserializationError("There is no valid linked product for in this row.")
27
+ except Product.DoesNotExist as e:
28
+ raise DeserializationError("There is no valid linked product for in this row.") from e
29
29
 
30
30
  if "currency" not in data:
31
31
  data["currency"] = data["product"].currency
@@ -65,15 +65,15 @@ class TradeImportHandler(ImportExportHandler):
65
65
  try:
66
66
  product = Product.objects.get(id=underlying_instrument.id)
67
67
  data["shares"] = nominal / product.share_price
68
- except Product.DoesNotExist:
68
+ except Product.DoesNotExist as e:
69
69
  raise DeserializationError(
70
70
  "We cannot compute the number of shares from the nominal value as we cannot find the product share price."
71
- )
71
+ ) from e
72
72
  else:
73
73
  raise DeserializationError("We couldn't find a valid underlying instrument this row.")
74
74
 
75
75
  for field in self.model._meta.get_fields():
76
- if not (value := data.get(field.name, None)) is None and isinstance(field, models.DecimalField):
76
+ if (value := data.get(field.name, None)) is not None and isinstance(field, models.DecimalField):
77
77
  q = (
78
78
  1 / (math.pow(10, 4))
79
79
  ) # we need that convertion mechanism otherwise there is floating point approximation error while casting to decimal and get_instance does not work as expected
@@ -7,7 +7,7 @@ def parse(import_source):
7
7
  if (
8
8
  (data_backend := import_source.source.data_backend)
9
9
  and (backend_class := data_backend.backend_class)
10
- and (default_mapping := getattr(backend_class, "DEFAULT_MAPPING"))
10
+ and (default_mapping := backend_class.DEFAULT_MAPPING)
11
11
  ):
12
12
  df = pd.read_json(import_source.file, orient="records")
13
13
  if not df.empty:
@@ -11,8 +11,8 @@ from wbportfolio.models import Product, Trade
11
11
  def file_name_parse(file_name):
12
12
  dates = re.findall("([0-9]{8})", file_name)
13
13
 
14
- assert len(dates) > 0
15
-
14
+ if len(dates) <= 0:
15
+ raise ValueError("No dates found in the filename")
16
16
  parts_dict = {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
17
17
 
18
18
  isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
@@ -14,8 +14,8 @@ logger = logging.getLogger("importers.parsers.jpmorgan.fee")
14
14
  def file_name_parse(file_name):
15
15
  dates = re.findall("([0-9]{8})", file_name)
16
16
 
17
- assert len(dates) == 1, "Not exactly 1 date found in the filename"
18
-
17
+ if len(dates) != 1:
18
+ raise ValueError("Not exactly 1 date found in the filename")
19
19
  return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
20
20
 
21
21
 
@@ -13,8 +13,8 @@ logger = logging.getLogger("importers.parsers.jp_morgan.strategy")
13
13
  def file_name_parse(file_name):
14
14
  dates = re.findall("([0-9]{8})", file_name)
15
15
 
16
- assert len(dates) == 1, "Not exactly 1 date found in the filename"
17
-
16
+ if len(dates) != 1:
17
+ raise ValueError("Not exactly 1 date found in the filename")
18
18
  return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
19
19
 
20
20
 
@@ -13,8 +13,8 @@ logger = logging.getLogger("importers.parsers.jpmorgan.index")
13
13
  def file_name_parse(file_name):
14
14
  dates = re.findall("([0-9]{8})", file_name)
15
15
 
16
- assert len(dates) == 1, "Not exactly 1 date found in the filename"
17
-
16
+ if len(dates) != 1:
17
+ raise ValueError("Not exactly 1 date found in the filename")
18
18
  return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
19
19
 
20
20
 
@@ -10,7 +10,8 @@ from wbportfolio.models import Product
10
10
  def file_name_parse(file_name):
11
11
  isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
12
12
 
13
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
13
+ if len(isin) != 1:
14
+ raise ValueError("Not exactly 1 isin found in the filename")
14
15
 
15
16
  return {"isin": isin[0]}
16
17
 
@@ -55,8 +55,12 @@ def _get_underlying_instrument(bbg_code, name, currency, instrument_type="equity
55
55
  def file_name_parse_isin(file_name):
56
56
  dates = re.findall(r"_([0-9]{4}-?[0-9]{2}-?[0-9]{2})", file_name)
57
57
  identifier = re.findall(r"([A-Z]{2}(?![A-Z]{10}\b)[A-Z0-9]{10})_", file_name)
58
- assert len(dates) == 2, "Not 2 dates found in the filename"
59
- assert len(identifier) == 1, "Not exactly 1 identifier found in the filename"
58
+
59
+ if len(dates) != 2:
60
+ raise ValueError("Not 2 dates found in the filename")
61
+
62
+ if len(identifier) != 1:
63
+ raise ValueError("Not exactly 1 identifier found in the filename")
60
64
  try:
61
65
  valuation_date = datetime.datetime.strptime(dates[0], "%Y-%m-%d").date()
62
66
  except ValueError:
@@ -11,9 +11,10 @@ from wbportfolio.models import ProductGroup
11
11
  def file_name_parse(file_name):
12
12
  dates = re.findall(r"([0-9]{4}-[0-9]{2}-[0-9]{2})", file_name)
13
13
  isin = re.findall(r"\.([a-zA-Z0-9]*)_", file_name)
14
-
15
- assert len(dates) == 2, "Not 2 dates found in the filename"
16
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
14
+ if len(dates) != 2:
15
+ raise ValueError("Not 2 dates found in the filename")
16
+ if len(isin) != 1:
17
+ raise ValueError("Not exactly 1 isin found in the filename")
17
18
 
18
19
  return {
19
20
  "isin": isin[0],
@@ -10,6 +10,7 @@ files generated by other spreadsheets.
10
10
  """
11
11
 
12
12
  import re
13
+ from ast import literal_eval
13
14
 
14
15
 
15
16
  class Table:
@@ -185,7 +186,7 @@ class SYLK:
185
186
  self.cury = int(val)
186
187
 
187
188
  elif ftd == "K":
188
- val = eval(self.escape(val))
189
+ val = literal_eval(self.escape(val))
189
190
  # if type(val) == int:
190
191
  # if self.currenttype == "date":
191
192
  # # value is offset in days from datebase
@@ -208,20 +209,20 @@ class SYLK:
208
209
  self.printformats.append((format, self.knownformats[format]))
209
210
  else:
210
211
  # hack to guess type...
211
- hasY = "y" in format
212
- hasD = "d" in format
213
- hasH = "h" in format
214
- hasZ = "0" in format
215
- hasP = "." in format
216
- if (hasD or hasY) and hasH:
212
+ has_y = "y" in format
213
+ has_d = "d" in format
214
+ has_h = "h" in format
215
+ has_z = "0" in format
216
+ has_p = "." in format
217
+ if (has_d or has_y) and has_h:
217
218
  dtype = "datetime"
218
- elif hasD or hasY:
219
+ elif has_d or has_y:
219
220
  dtype = "date"
220
- elif hasH:
221
+ elif has_h:
221
222
  dtype = "time"
222
- elif hasP and hasZ:
223
+ elif has_p and has_z:
223
224
  dtype = "float"
224
- elif hasZ:
225
+ elif has_z:
225
226
  dtype = "int"
226
227
  else:
227
228
  dtype = "string"
@@ -10,8 +10,10 @@ def file_name_parse(file_name):
10
10
  dates = re.findall(r"([0-9]{4}-[0-9]{2}-[0-9]{2})", file_name)
11
11
  isin = re.findall(r"\.([a-zA-Z0-9]*)_", file_name)
12
12
 
13
- assert len(dates) == 2, "Not 2 dates found in the filename"
14
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
13
+ if len(dates) != 2:
14
+ raise ValueError("Not 2 dates found in the filename")
15
+ if len(isin) != 1:
16
+ raise ValueError("Not exactly 1 isin found in the filename")
15
17
 
16
18
  return {
17
19
  "isin": isin[0],
@@ -14,8 +14,8 @@ logger = logging.getLogger("importers.parsers.jp_morgan.strategy")
14
14
  def file_name_parse(file_name):
15
15
  dates = re.findall("([0-9]{8})", file_name)
16
16
 
17
- assert len(dates) == 1, "Not exactly 1 date found in the filename"
18
-
17
+ if len(dates) != 1:
18
+ raise ValueError("Not exactly 1 date found in the filename")
19
19
  return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
20
20
 
21
21
 
@@ -23,7 +23,7 @@ def parse(import_source):
23
23
  data = list()
24
24
  prices = list()
25
25
  df_dict = pd.read_excel(BytesIO(import_source.file.read()), engine="openpyxl", sheet_name=None)
26
- for sheet_name, df in df_dict.items():
26
+ for df in df_dict.values():
27
27
  xx, yy = np.where(df == "Ticker")
28
28
  if len(xx) == 1 and len(yy) == 1:
29
29
  df_info = df.iloc[: xx[0] - 1, :].transpose()
@@ -20,7 +20,8 @@ product_mapping = {
20
20
 
21
21
  def file_name_parse(file_name):
22
22
  identifier = re.findall("([0-9]{4}).*", file_name)
23
- assert len(identifier) == 1, "Not exactly one identifier was found."
23
+ if len(identifier) != 1:
24
+ raise ValueError("Not exactly 1 identifier found in the filename")
24
25
  return identifier[0]
25
26
 
26
27
 
@@ -7,9 +7,10 @@ import re
7
7
  def file_name_parse(file_name):
8
8
  dates = re.findall(r"([0-9]{4}-[0-9]{2}-[0-9]{2})", file_name)
9
9
  isin = re.findall(r"\.([a-zA-Z0-9]*)_", file_name)
10
-
11
- assert len(dates) == 2, "Not 2 dates found in the filename"
12
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
10
+ if len(dates) != 2:
11
+ raise ValueError("Not 2 dates found in the filename")
12
+ if len(isin) != 1:
13
+ raise ValueError("Not exactly 1 isin found in the filename")
13
14
 
14
15
  return {
15
16
  "isin": isin[0],
@@ -7,7 +7,8 @@ import xlrd
7
7
  def file_name_parse(file_name):
8
8
  isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
9
9
 
10
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
10
+ if len(isin) != 1:
11
+ raise ValueError("Not exactly 1 isin found in the filename")
11
12
 
12
13
  return {"isin": isin[0]}
13
14
 
@@ -11,7 +11,8 @@ from wbportfolio.import_export.utils import get_file_extension
11
11
  def file_name_parse(file_name):
12
12
  isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
13
13
 
14
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
14
+ if len(isin) != 1:
15
+ raise ValueError("Not exactly 1 isin found in the filename")
15
16
 
16
17
  return {
17
18
  "isin": isin[0],
@@ -24,7 +24,9 @@ def convert_string_to_number(string):
24
24
  return 0.0
25
25
 
26
26
 
27
- def parse_date(date, formats=[]):
27
+ def parse_date(date, formats: list | None = None):
28
+ if formats is None:
29
+ formats = []
28
30
  if isinstance(date, int) or isinstance(date, float):
29
31
  return xldate_as_datetime(int(date), 0).date()
30
32
  if isinstance(date, str):
@@ -4,7 +4,7 @@ import numpy as np
4
4
  import pandas as pd
5
5
  from django.db.models import QuerySet
6
6
  from wbfdm.contrib.metric.backends.base import AbstractBackend, Metric
7
- from wbfdm.contrib.metric.exceptions import MetricInvalidParameterException
7
+ from wbfdm.contrib.metric.exceptions import MetricInvalidParameterError
8
8
 
9
9
  from wbportfolio.models import AssetPosition, Portfolio
10
10
 
@@ -62,7 +62,7 @@ class PortfolioMetricBaseBackend(AbstractBackend[Portfolio]):
62
62
  try:
63
63
  return qs.latest("date").date
64
64
  except AssetPosition.DoesNotExist:
65
- raise MetricInvalidParameterException()
65
+ raise MetricInvalidParameterError() from None
66
66
 
67
67
  def get_queryset(self) -> QuerySet[Portfolio]:
68
68
  product_portfolios = super().get_queryset().filter_active_and_tracked()
@@ -5,6 +5,7 @@ from decimal import Decimal, InvalidOperation
5
5
  from typing import TYPE_CHECKING
6
6
 
7
7
  from django.contrib import admin
8
+ from django.core.exceptions import ObjectDoesNotExist
8
9
  from django.db import models
9
10
  from django.db.models import (
10
11
  Case,
@@ -429,7 +430,7 @@ class AssetPosition(ImportMixin, models.Model):
429
430
  ):
430
431
  try:
431
432
  self.underlying_quote = self.underlying_instrument.children.get(is_primary=True)
432
- except:
433
+ except ObjectDoesNotExist:
433
434
  self.underlying_quote = self.underlying_instrument
434
435
 
435
436
  if not getattr(self, "currency", None):
@@ -32,7 +32,7 @@ class Custodian(WBModel):
32
32
 
33
33
  @classmethod
34
34
  def get_by_mapping(cls, mapping: str, use_similarity=False, create_missing=True):
35
- SIMILIRATY_SCORE = 0.7
35
+ similarity_score = 0.7
36
36
  lower_mapping = mapping.lower()
37
37
  try:
38
38
  return cls.objects.get(mapping__contains=[lower_mapping])
@@ -40,7 +40,7 @@ class Custodian(WBModel):
40
40
  if use_similarity:
41
41
  similar_custodians = cls.objects.annotate(
42
42
  similarity_score=TrigramSimilarity("name", lower_mapping)
43
- ).filter(similarity_score__gt=SIMILIRATY_SCORE)
43
+ ).filter(similarity_score__gt=similarity_score)
44
44
  if similar_custodians.count() == 1:
45
45
  custodian = similar_custodians.first()
46
46
  print(f"find similar custodian {lower_mapping} -> {custodian.name}") # noqa: T201
@@ -50,7 +50,7 @@ class Custodian(WBModel):
50
50
  else:
51
51
  similar_companies = Company.objects.annotate(
52
52
  similarity_score=TrigramSimilarity("name", lower_mapping)
53
- ).filter(similarity_score__gt=SIMILIRATY_SCORE)
53
+ ).filter(similarity_score__gt=similarity_score)
54
54
  if similar_companies.count() == 1:
55
55
  print( # noqa: T201
56
56
  f"Find similar company {lower_mapping} -> {similar_companies.first().name}"
@@ -1,2 +1,2 @@
1
- class InvalidAnalyticPortfolio(Exception):
1
+ class InvalidAnalyticPortfolioError(Exception):
2
2
  pass