rapidata 2.36.2__py3-none-any.whl → 2.38.0__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.

Potentially problematic release.


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

Files changed (65) hide show
  1. rapidata/__init__.py +3 -4
  2. rapidata/rapidata_client/__init__.py +1 -4
  3. rapidata/rapidata_client/api/{rapidata_exception.py → rapidata_api_client.py} +119 -2
  4. rapidata/rapidata_client/benchmark/leaderboard/rapidata_leaderboard.py +88 -46
  5. rapidata/rapidata_client/benchmark/participant/_participant.py +26 -9
  6. rapidata/rapidata_client/benchmark/rapidata_benchmark.py +274 -205
  7. rapidata/rapidata_client/benchmark/rapidata_benchmark_manager.py +98 -76
  8. rapidata/rapidata_client/config/__init__.py +3 -0
  9. rapidata/rapidata_client/config/logger.py +135 -0
  10. rapidata/rapidata_client/config/logging_config.py +58 -0
  11. rapidata/rapidata_client/config/managed_print.py +6 -0
  12. rapidata/rapidata_client/config/order_config.py +14 -0
  13. rapidata/rapidata_client/config/rapidata_config.py +14 -9
  14. rapidata/rapidata_client/config/tracer.py +130 -0
  15. rapidata/rapidata_client/config/upload_config.py +14 -0
  16. rapidata/rapidata_client/datapoints/_datapoint.py +1 -1
  17. rapidata/rapidata_client/datapoints/assets/_media_asset.py +1 -1
  18. rapidata/rapidata_client/datapoints/assets/_sessions.py +2 -2
  19. rapidata/rapidata_client/demographic/demographic_manager.py +16 -14
  20. rapidata/rapidata_client/filter/_base_filter.py +11 -5
  21. rapidata/rapidata_client/filter/age_filter.py +9 -3
  22. rapidata/rapidata_client/filter/and_filter.py +20 -5
  23. rapidata/rapidata_client/filter/campaign_filter.py +7 -1
  24. rapidata/rapidata_client/filter/country_filter.py +8 -2
  25. rapidata/rapidata_client/filter/custom_filter.py +9 -3
  26. rapidata/rapidata_client/filter/gender_filter.py +9 -3
  27. rapidata/rapidata_client/filter/language_filter.py +12 -5
  28. rapidata/rapidata_client/filter/new_user_filter.py +3 -4
  29. rapidata/rapidata_client/filter/not_filter.py +17 -5
  30. rapidata/rapidata_client/filter/or_filter.py +20 -5
  31. rapidata/rapidata_client/filter/response_count_filter.py +6 -0
  32. rapidata/rapidata_client/filter/user_score_filter.py +17 -5
  33. rapidata/rapidata_client/order/_rapidata_dataset.py +45 -17
  34. rapidata/rapidata_client/order/_rapidata_order_builder.py +19 -13
  35. rapidata/rapidata_client/order/rapidata_order.py +60 -48
  36. rapidata/rapidata_client/order/rapidata_order_manager.py +239 -195
  37. rapidata/rapidata_client/order/rapidata_results.py +71 -57
  38. rapidata/rapidata_client/rapidata_client.py +36 -23
  39. rapidata/rapidata_client/selection/_base_selection.py +6 -0
  40. rapidata/rapidata_client/selection/static_selection.py +5 -10
  41. rapidata/rapidata_client/settings/_rapidata_setting.py +8 -0
  42. rapidata/rapidata_client/settings/alert_on_fast_response.py +8 -5
  43. rapidata/rapidata_client/settings/free_text_minimum_characters.py +9 -4
  44. rapidata/rapidata_client/validation/rapidata_validation_set.py +20 -16
  45. rapidata/rapidata_client/validation/rapids/rapids.py +7 -1
  46. rapidata/rapidata_client/validation/validation_set_manager.py +285 -268
  47. rapidata/rapidata_client/workflow/_base_workflow.py +6 -1
  48. rapidata/rapidata_client/workflow/_classify_workflow.py +6 -0
  49. rapidata/rapidata_client/workflow/_compare_workflow.py +6 -0
  50. rapidata/rapidata_client/workflow/_draw_workflow.py +6 -0
  51. rapidata/rapidata_client/workflow/_evaluation_workflow.py +6 -0
  52. rapidata/rapidata_client/workflow/_free_text_workflow.py +6 -0
  53. rapidata/rapidata_client/workflow/_locate_workflow.py +6 -0
  54. rapidata/rapidata_client/workflow/_ranking_workflow.py +12 -0
  55. rapidata/rapidata_client/workflow/_select_words_workflow.py +6 -0
  56. rapidata/rapidata_client/workflow/_timestamp_workflow.py +6 -0
  57. rapidata/service/credential_manager.py +1 -1
  58. rapidata/service/openapi_service.py +2 -2
  59. {rapidata-2.36.2.dist-info → rapidata-2.38.0.dist-info}/METADATA +4 -1
  60. {rapidata-2.36.2.dist-info → rapidata-2.38.0.dist-info}/RECORD +62 -59
  61. rapidata/rapidata_client/logging/__init__.py +0 -2
  62. rapidata/rapidata_client/logging/logger.py +0 -122
  63. rapidata/rapidata_client/logging/output_manager.py +0 -20
  64. {rapidata-2.36.2.dist-info → rapidata-2.38.0.dist-info}/LICENSE +0 -0
  65. {rapidata-2.36.2.dist-info → rapidata-2.38.0.dist-info}/WHEEL +0 -0
@@ -1,29 +1,31 @@
1
1
  from rapidata.service.openapi_service import OpenAPIService
2
2
  from rapidata.rapidata_client.datapoints.assets import MediaAsset
3
- from rapidata.api_client.models.create_demographic_rapid_model import CreateDemographicRapidModel
3
+ from rapidata.api_client.models.create_demographic_rapid_model import (
4
+ CreateDemographicRapidModel,
5
+ )
4
6
  from rapidata.api_client.models.classify_payload import ClassifyPayload
5
- from rapidata.rapidata_client.logging import logger
7
+ from rapidata.rapidata_client.config import logger
8
+
6
9
 
7
10
  class DemographicManager:
8
11
  def __init__(self, openapi_service: OpenAPIService):
9
12
  self._openapi_service = openapi_service
10
13
  logger.debug("DemographicManager initialized")
11
-
12
- def create_demographic_rapid(self,
13
- instruction: str,
14
- answer_options: list[str],
15
- datapoint: str,
16
- key: str):
17
-
14
+
15
+ def create_demographic_rapid(
16
+ self, instruction: str, answer_options: list[str], datapoint: str, key: str
17
+ ):
18
+
18
19
  media = MediaAsset(path=datapoint)
19
20
  model = CreateDemographicRapidModel(
20
21
  key=key,
21
22
  payload=ClassifyPayload(
22
23
  _t="ClassifyPayload",
23
24
  possibleCategories=answer_options,
24
- title=instruction
25
- )
25
+ title=instruction,
26
+ ),
27
+ )
28
+
29
+ self._openapi_service.rapid_api.rapid_demographic_post(
30
+ model=model, file=[media.to_file()]
26
31
  )
27
-
28
- self._openapi_service.rapid_api.rapid_demographic_post(model=model, file=[media.to_file()])
29
-
@@ -13,9 +13,9 @@ class RapidataFilter:
13
13
  """Enable the | operator to create OrFilter combinations."""
14
14
  if not isinstance(other, RapidataFilter):
15
15
  return NotImplemented
16
-
16
+
17
17
  from rapidata.rapidata_client.filter.or_filter import OrFilter
18
-
18
+
19
19
  # If self is already an OrFilter, extend its filters list
20
20
  if isinstance(self, OrFilter):
21
21
  if isinstance(other, OrFilter):
@@ -33,9 +33,9 @@ class RapidataFilter:
33
33
  """Enable the & operator to create AndFilter combinations."""
34
34
  if not isinstance(other, RapidataFilter):
35
35
  return NotImplemented
36
-
36
+
37
37
  from rapidata.rapidata_client.filter.and_filter import AndFilter
38
-
38
+
39
39
  # If self is already an AndFilter, extend its filters list
40
40
  if isinstance(self, AndFilter):
41
41
  if isinstance(other, AndFilter):
@@ -52,10 +52,16 @@ class RapidataFilter:
52
52
  def __invert__(self):
53
53
  """Enable the ~ operator to create NotFilter negations."""
54
54
  from rapidata.rapidata_client.filter.not_filter import NotFilter
55
-
55
+
56
56
  # If self is already a NotFilter, return the original filter (double negation)
57
57
  if isinstance(self, NotFilter):
58
58
  return self.filter
59
59
  # Create a new NotFilter
60
60
  else:
61
61
  return NotFilter(self)
62
+
63
+ def __str__(self) -> str:
64
+ return f"{self.__class__.__name__}()"
65
+
66
+ def __repr__(self) -> str:
67
+ return f"{self.__class__.__name__}()"
@@ -6,10 +6,10 @@ from rapidata.rapidata_client.filter.models.age_group import AgeGroup
6
6
 
7
7
  class AgeFilter(RapidataFilter):
8
8
  """AgeFilter Class
9
-
9
+
10
10
  Can be used to filter who to target based on age groups.
11
-
12
-
11
+
12
+
13
13
  Args:
14
14
  age_groups (list[AgeGroup]): List of age groups to filter by."""
15
15
 
@@ -21,3 +21,9 @@ class AgeFilter(RapidataFilter):
21
21
  _t="AgeFilter",
22
22
  ageGroups=[age_group._to_backend_model() for age_group in self.age_groups],
23
23
  )
24
+
25
+ def __str__(self) -> str:
26
+ return f"AgeFilter(age_groups={self.age_groups})"
27
+
28
+ def __repr__(self) -> str:
29
+ return f"AgeFilter(age_groups={self.age_groups!r})"
@@ -1,7 +1,9 @@
1
1
  from typing import Any
2
2
  from rapidata.rapidata_client.filter._base_filter import RapidataFilter
3
3
  from rapidata.api_client.models.and_user_filter_model import AndUserFilterModel
4
- from rapidata.api_client.models.and_user_filter_model_filters_inner import AndUserFilterModelFiltersInner
4
+ from rapidata.api_client.models.and_user_filter_model_filters_inner import (
5
+ AndUserFilterModelFiltersInner,
6
+ )
5
7
 
6
8
 
7
9
  class AndFilter(RapidataFilter):
@@ -10,21 +12,34 @@ class AndFilter(RapidataFilter):
10
12
 
11
13
  Args:
12
14
  filters (list[RapidataFilter]): A list of filters to be combined with AND.
13
-
15
+
14
16
  Example:
15
17
  ```python
16
18
  from rapidata import AndFilter, LanguageFilter, CountryFilter
17
19
 
18
- AndFilter([LanguageFilter(["en"]), CountryFilter(["US"])])
20
+ AndFilter([LanguageFilter(["en"]), CountryFilter(["US"])])
19
21
  ```
20
22
 
21
23
  This will match users who have their phone set to English AND are located in the United States.
22
24
  """
25
+
23
26
  def __init__(self, filters: list[RapidataFilter]):
24
27
  if not all(isinstance(filter, RapidataFilter) for filter in filters):
25
28
  raise ValueError("Filters must be a RapidataFilter object")
26
-
29
+
27
30
  self.filters = filters
28
31
 
29
32
  def _to_model(self):
30
- return AndUserFilterModel(_t="AndFilter", filters=[AndUserFilterModelFiltersInner(filter._to_model()) for filter in self.filters])
33
+ return AndUserFilterModel(
34
+ _t="AndFilter",
35
+ filters=[
36
+ AndUserFilterModelFiltersInner(filter._to_model())
37
+ for filter in self.filters
38
+ ],
39
+ )
40
+
41
+ def __str__(self) -> str:
42
+ return f"AndFilter(filters={self.filters})"
43
+
44
+ def __repr__(self) -> str:
45
+ return f"AndFilter(filters={self.filters!r})"
@@ -11,7 +11,7 @@ class CampaignFilter(RapidataFilter):
11
11
  Can be used to filter who to target based on campaign IDs.
12
12
 
13
13
  This filter can only be used when directly in contact with Rapidata.
14
-
14
+
15
15
  Args:
16
16
  campaign_ids (list[str]): List of campaign IDs to filter by.
17
17
  """
@@ -24,3 +24,9 @@ class CampaignFilter(RapidataFilter):
24
24
  _t="CampaignFilter",
25
25
  campaignIds=self.campaign_ids,
26
26
  )
27
+
28
+ def __str__(self) -> str:
29
+ return f"CampaignFilter(campaign_ids={self.campaign_ids})"
30
+
31
+ def __repr__(self) -> str:
32
+ return f"CampaignFilter(campaign_ids={self.campaign_ids!r})"
@@ -7,7 +7,7 @@ class CountryFilter(RapidataFilter):
7
7
  """CountryFilter Class
8
8
 
9
9
  Can be used to filter who to target based on country codes.
10
-
10
+
11
11
  Args:
12
12
  country_codes (list[str]): List of country codes (capitalized) to filter by.
13
13
  """
@@ -16,7 +16,7 @@ class CountryFilter(RapidataFilter):
16
16
  # check that all characters in the country codes are uppercase
17
17
  if not isinstance(country_codes, list):
18
18
  raise ValueError("Country codes must be a list")
19
-
19
+
20
20
  if not all([code.isupper() for code in country_codes]):
21
21
  raise ValueError("Country codes must be uppercase")
22
22
 
@@ -24,3 +24,9 @@ class CountryFilter(RapidataFilter):
24
24
 
25
25
  def _to_model(self):
26
26
  return CountryUserFilterModel(_t="CountryFilter", countries=self.country_codes)
27
+
28
+ def __str__(self) -> str:
29
+ return f"CountryFilter(country_codes={self.country_codes})"
30
+
31
+ def __repr__(self) -> str:
32
+ return f"CountryFilter(country_codes={self.country_codes!r})"
@@ -8,10 +8,10 @@ class CustomFilter(RapidataFilter):
8
8
 
9
9
  Can be used to filter who to target based on custom filters.
10
10
 
11
- Ought to be used with contact to Rapidata.
12
-
11
+ Ought to be used with contact to Rapidata.
12
+
13
13
  Warning: If identifier does not exist, order will not get any responses.
14
-
14
+
15
15
  Args:
16
16
  identifier (str): Identifier of the custom filter.
17
17
  values (list[str]): List of values to filter by.
@@ -27,3 +27,9 @@ class CustomFilter(RapidataFilter):
27
27
  identifier=self.identifier,
28
28
  values=self.values,
29
29
  )
30
+
31
+ def __str__(self) -> str:
32
+ return f"CustomFilter(identifier={self.identifier}, values={self.values})"
33
+
34
+ def __repr__(self) -> str:
35
+ return f"CustomFilter(identifier={self.identifier!r}, values={self.values!r})"
@@ -6,10 +6,10 @@ from rapidata.rapidata_client.filter.models.gender import Gender
6
6
 
7
7
  class GenderFilter(RapidataFilter):
8
8
  """GenderFilter Class
9
-
9
+
10
10
  Can be used to filter who to target based on their gender.
11
-
12
-
11
+
12
+
13
13
  Args:
14
14
  genders (list[Gender]): List of genders to filter by."""
15
15
 
@@ -21,3 +21,9 @@ class GenderFilter(RapidataFilter):
21
21
  _t="GenderFilter",
22
22
  genders=[gender._to_backend_model() for gender in self.genders],
23
23
  )
24
+
25
+ def __str__(self) -> str:
26
+ return f"GenderFilter(genders={self.genders})"
27
+
28
+ def __repr__(self) -> str:
29
+ return f"GenderFilter(genders={self.genders!r})"
@@ -7,26 +7,27 @@ from rapidata.api_client.models.language_user_filter_model import (
7
7
 
8
8
  class LanguageFilter(RapidataFilter):
9
9
  """LanguageFilter Class
10
-
10
+
11
11
  Can be used to filter who to target based on language codes.
12
12
 
13
13
  Args:
14
14
  language_codes (list[str]): List of language codes to filter by.
15
-
16
- Example:
15
+
16
+ Example:
17
17
  ```python
18
18
  LanguageFilter(["en", "de"])
19
19
  ```
20
20
  This will limit the order to be shown to only people who have their phone set to english or german
21
21
  """
22
+
22
23
  def __init__(self, language_codes: list[str]):
23
24
  if not isinstance(language_codes, list):
24
25
  raise ValueError("Language codes must be a list")
25
-
26
+
26
27
  # check that all characters in the language codes are lowercase
27
28
  if not all([code.islower() for code in language_codes]):
28
29
  raise ValueError("Language codes must be lowercase")
29
-
30
+
30
31
  for code in language_codes:
31
32
  if not len(code) == 2:
32
33
  raise ValueError("Language codes must be two characters long")
@@ -35,3 +36,9 @@ class LanguageFilter(RapidataFilter):
35
36
 
36
37
  def _to_model(self):
37
38
  return LanguageUserFilterModel(_t="LanguageFilter", languages=self.languages)
39
+
40
+ def __str__(self):
41
+ return f"LanguageFilter({self.languages})"
42
+
43
+ def __repr__(self):
44
+ return f"LanguageFilter({self.languages})"
@@ -1,13 +1,12 @@
1
1
  from rapidata.rapidata_client.filter._base_filter import RapidataFilter
2
2
  from rapidata.api_client.models.new_user_filter_model import NewUserFilterModel
3
3
 
4
+
4
5
  class NewUserFilter(RapidataFilter):
5
6
  """NewUserFilter Class
6
-
7
+
7
8
  Can be used to filter new users.
8
9
  """
9
10
 
10
11
  def _to_model(self):
11
- return NewUserFilterModel(
12
- _t="NewUserFilter"
13
- )
12
+ return NewUserFilterModel(_t="NewUserFilter")
@@ -1,7 +1,9 @@
1
1
  from typing import Any
2
2
  from rapidata.rapidata_client.filter._base_filter import RapidataFilter
3
3
  from rapidata.api_client.models.not_user_filter_model import NotUserFilterModel
4
- from rapidata.api_client.models.and_user_filter_model_filters_inner import AndUserFilterModelFiltersInner
4
+ from rapidata.api_client.models.and_user_filter_model_filters_inner import (
5
+ AndUserFilterModelFiltersInner,
6
+ )
5
7
 
6
8
 
7
9
  class NotFilter(RapidataFilter):
@@ -10,21 +12,31 @@ class NotFilter(RapidataFilter):
10
12
 
11
13
  Args:
12
14
  filter (RapidataFilter): The filter whose condition should be negated.
13
-
15
+
14
16
  Example:
15
17
  ```python
16
18
  from rapidata import NotFilter, LanguageFilter
17
19
 
18
- NotFilter(LanguageFilter(["en"]))
20
+ NotFilter(LanguageFilter(["en"]))
19
21
  ```
20
22
 
21
23
  This will limit the order to be shown to only people who have their phone set to a language other than English.
22
24
  """
25
+
23
26
  def __init__(self, filter: RapidataFilter):
24
27
  if not isinstance(filter, RapidataFilter):
25
28
  raise ValueError("Filter must be a RapidataFilter object")
26
-
29
+
27
30
  self.filter = filter
28
31
 
29
32
  def _to_model(self):
30
- return NotUserFilterModel(_t="NotFilter", filter=AndUserFilterModelFiltersInner(self.filter._to_model()))
33
+ return NotUserFilterModel(
34
+ _t="NotFilter",
35
+ filter=AndUserFilterModelFiltersInner(self.filter._to_model()),
36
+ )
37
+
38
+ def __str__(self) -> str:
39
+ return f"NotFilter(filter={self.filter})"
40
+
41
+ def __repr__(self) -> str:
42
+ return f"NotFilter(filter={self.filter!r})"
@@ -1,7 +1,9 @@
1
1
  from typing import Any
2
2
  from rapidata.rapidata_client.filter._base_filter import RapidataFilter
3
3
  from rapidata.api_client.models.or_user_filter_model import OrUserFilterModel
4
- from rapidata.api_client.models.and_user_filter_model_filters_inner import AndUserFilterModelFiltersInner
4
+ from rapidata.api_client.models.and_user_filter_model_filters_inner import (
5
+ AndUserFilterModelFiltersInner,
6
+ )
5
7
 
6
8
 
7
9
  class OrFilter(RapidataFilter):
@@ -10,21 +12,34 @@ class OrFilter(RapidataFilter):
10
12
 
11
13
  Args:
12
14
  filters (list[RapidataFilter]): A list of filters to be combined with OR.
13
-
15
+
14
16
  Example:
15
17
  ```python
16
18
  from rapidata import OrFilter, LanguageFilter, CountryFilter
17
19
 
18
- OrFilter([LanguageFilter(["en"]), CountryFilter(["US"])])
20
+ OrFilter([LanguageFilter(["en"]), CountryFilter(["US"])])
19
21
  ```
20
22
 
21
23
  This will match users who either have their phone set to English OR are located in the United States.
22
24
  """
25
+
23
26
  def __init__(self, filters: list[RapidataFilter]):
24
27
  if not all(isinstance(filter, RapidataFilter) for filter in filters):
25
28
  raise ValueError("Filters must be a RapidataFilter object")
26
-
29
+
27
30
  self.filters = filters
28
31
 
29
32
  def _to_model(self):
30
- return OrUserFilterModel(_t="OrFilter", filters=[AndUserFilterModelFiltersInner(filter._to_model()) for filter in self.filters])
33
+ return OrUserFilterModel(
34
+ _t="OrFilter",
35
+ filters=[
36
+ AndUserFilterModelFiltersInner(filter._to_model())
37
+ for filter in self.filters
38
+ ],
39
+ )
40
+
41
+ def __str__(self) -> str:
42
+ return f"OrFilter(filters={self.filters})"
43
+
44
+ def __repr__(self) -> str:
45
+ return f"OrFilter(filters={self.filters!r})"
@@ -49,3 +49,9 @@ class ResponseCountFilter(RapidataFilter):
49
49
  dimension=self.dimension,
50
50
  operator=self.operator,
51
51
  )
52
+
53
+ def __str__(self) -> str:
54
+ return f"ResponseCountFilter(response_count={self.response_count}, dimension={self.dimension}, operator={self.operator})"
55
+
56
+ def __repr__(self) -> str:
57
+ return f"ResponseCountFilter(response_count={self.response_count!r}, dimension={self.dimension!r}, operator={self.operator!r})"
@@ -7,28 +7,34 @@ from rapidata.api_client.models.user_score_user_filter_model import (
7
7
 
8
8
  class UserScoreFilter(RapidataFilter):
9
9
  """UserScoreFilter Class
10
-
10
+
11
11
  Can be used to filter who to target based on their user score.
12
-
12
+
13
13
  Args:
14
14
  lower_bound (float): The lower bound of the user score.
15
15
  upper_bound (float): The upper bound of the user score.
16
16
  dimension (str): The dimension of the userScore to be considerd for the filter.
17
17
 
18
- Example:
18
+ Example:
19
19
  ```python
20
20
  UserScoreFilter(0.5, 0.9)
21
21
  ```
22
22
  This will only show the order to users that have a UserScore of >=0.5 and <=0.9
23
23
  """
24
- def __init__(self, lower_bound: float = 0.0, upper_bound: float = 1.0, dimension: str | None = None):
24
+
25
+ def __init__(
26
+ self,
27
+ lower_bound: float = 0.0,
28
+ upper_bound: float = 1.0,
29
+ dimension: str | None = None,
30
+ ):
25
31
  if lower_bound < 0 or lower_bound > 1:
26
32
  raise ValueError("The lower bound must be between 0 and 1.")
27
33
  if upper_bound < 0 or upper_bound > 1:
28
34
  raise ValueError("The upper bound must be between 0 and 1.")
29
35
  if lower_bound >= upper_bound:
30
36
  raise ValueError("The lower bound must be less than the upper bound.")
31
-
37
+
32
38
  self.upper_bound = upper_bound
33
39
  self.lower_bound = lower_bound
34
40
  self.dimension = dimension
@@ -40,3 +46,9 @@ class UserScoreFilter(RapidataFilter):
40
46
  lowerbound=self.lower_bound,
41
47
  dimension=self.dimension,
42
48
  )
49
+
50
+ def __str__(self) -> str:
51
+ return f"UserScoreFilter(lower_bound={self.lower_bound}, upper_bound={self.upper_bound}, dimension={self.dimension})"
52
+
53
+ def __repr__(self) -> str:
54
+ return f"UserScoreFilter(lower_bound={self.lower_bound!r}, upper_bound={self.upper_bound!r}, dimension={self.dimension!r})"
@@ -6,18 +6,17 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
6
6
  from tqdm import tqdm
7
7
 
8
8
  from typing import Generator
9
- from rapidata.rapidata_client.logging import (
10
- logger,
11
- managed_print,
12
- RapidataOutputManager,
13
- )
9
+ from rapidata.rapidata_client.config import logger, managed_print
14
10
  import time
15
11
  import threading
16
- from rapidata.rapidata_client.api.rapidata_exception import (
12
+ from rapidata.rapidata_client.api.rapidata_api_client import (
17
13
  suppress_rapidata_error_logging,
18
14
  )
19
15
  from rapidata.rapidata_client.config.rapidata_config import rapidata_config
20
16
 
17
+ # Add OpenTelemetry context imports for thread propagation
18
+ from opentelemetry import context as otel_context
19
+
21
20
 
22
21
  def chunk_list(lst: list, chunk_size: int) -> Generator:
23
22
  for i in range(0, len(lst), chunk_size):
@@ -62,22 +61,37 @@ class RapidataDataset:
62
61
  )
63
62
  return datapoint
64
63
 
64
+ def upload_with_context(
65
+ context: otel_context.Context, datapoint: Datapoint, index: int
66
+ ) -> Datapoint:
67
+ """Wrapper function that runs upload_text_datapoint with the provided context."""
68
+ token = otel_context.attach(context)
69
+ try:
70
+ return upload_text_datapoint(datapoint, index)
71
+ finally:
72
+ otel_context.detach(token)
73
+
65
74
  successful_uploads: list[Datapoint] = []
66
75
  failed_uploads: list[Datapoint] = []
67
76
 
77
+ # Capture the current OpenTelemetry context before creating threads
78
+ current_context = otel_context.get_current()
79
+
68
80
  total_uploads = len(datapoints)
69
81
  with ThreadPoolExecutor(
70
- max_workers=rapidata_config.maxUploadWorkers
82
+ max_workers=rapidata_config.upload.maxWorkers
71
83
  ) as executor:
72
84
  future_to_datapoint = {
73
- executor.submit(upload_text_datapoint, datapoint, index=i): datapoint
85
+ executor.submit(
86
+ upload_with_context, current_context, datapoint, i
87
+ ): datapoint
74
88
  for i, datapoint in enumerate(datapoints)
75
89
  }
76
90
 
77
91
  with tqdm(
78
92
  total=total_uploads,
79
93
  desc="Uploading text datapoints",
80
- disable=RapidataOutputManager.silent_mode,
94
+ disable=rapidata_config.logging.silent_mode,
81
95
  ) as pbar:
82
96
  for future in as_completed(future_to_datapoint.keys()):
83
97
  datapoint = future_to_datapoint[future]
@@ -119,7 +133,7 @@ class RapidataDataset:
119
133
  urls = datapoint.get_urls()
120
134
 
121
135
  last_exception = None
122
- for attempt in range(rapidata_config.uploadMaxRetries):
136
+ for attempt in range(rapidata_config.upload.maxRetries):
123
137
  try:
124
138
  with suppress_rapidata_error_logging():
125
139
  self.openapi_service.dataset_api.dataset_dataset_id_datapoints_post(
@@ -136,7 +150,7 @@ class RapidataDataset:
136
150
 
137
151
  except Exception as e:
138
152
  last_exception = e
139
- if attempt < rapidata_config.uploadMaxRetries - 1:
153
+ if attempt < rapidata_config.upload.maxRetries - 1:
140
154
  # Exponential backoff: wait 1s, then 2s, then 4s
141
155
  retry_delay = 2**attempt
142
156
  time.sleep(retry_delay)
@@ -144,13 +158,13 @@ class RapidataDataset:
144
158
  logger.debug(
145
159
  "Retrying %s of %s...",
146
160
  attempt + 1,
147
- rapidata_config.uploadMaxRetries,
161
+ rapidata_config.upload.maxRetries,
148
162
  )
149
163
 
150
164
  # If we get here, all retries failed
151
165
  local_failed.append(datapoint)
152
166
  tqdm.write(
153
- f"Upload failed for {datapoint} after {rapidata_config.uploadMaxRetries} attempts. \nFinal error: \n{str(last_exception)}"
167
+ f"Upload failed for {datapoint} after {rapidata_config.upload.maxRetries} attempts. \nFinal error: \n{str(last_exception)}"
154
168
  )
155
169
 
156
170
  return local_successful, local_failed
@@ -183,7 +197,7 @@ class RapidataDataset:
183
197
  with tqdm(
184
198
  total=total_uploads,
185
199
  desc="Uploading datapoints",
186
- disable=RapidataOutputManager.silent_mode,
200
+ disable=rapidata_config.logging.silent_mode,
187
201
  ) as pbar:
188
202
  prev_ready = 0
189
203
  prev_failed = 0
@@ -291,17 +305,31 @@ class RapidataDataset:
291
305
  successful_uploads: list[Datapoint] = []
292
306
  failed_uploads: list[Datapoint] = []
293
307
 
308
+ def process_upload_with_context(
309
+ context: otel_context.Context, datapoint: Datapoint, index: int
310
+ ) -> tuple[list[Datapoint], list[Datapoint]]:
311
+ """Wrapper function that runs _process_single_upload with the provided context."""
312
+ token = otel_context.attach(context)
313
+ try:
314
+ return self._process_single_upload(datapoint, index)
315
+ finally:
316
+ otel_context.detach(token)
317
+
318
+ # Capture the current OpenTelemetry context before creating threads
319
+ current_context = otel_context.get_current()
320
+
294
321
  try:
295
322
  with ThreadPoolExecutor(
296
- max_workers=rapidata_config.maxUploadWorkers
323
+ max_workers=rapidata_config.upload.maxWorkers
297
324
  ) as executor:
298
325
  # Process uploads in chunks to avoid overwhelming the system
299
326
  for chunk_idx, chunk in enumerate(chunk_list(datapoints, chunk_size)):
300
327
  futures = [
301
328
  executor.submit(
302
- self._process_single_upload,
329
+ process_upload_with_context,
330
+ current_context,
303
331
  datapoint,
304
- index=(chunk_idx * chunk_size + i),
332
+ chunk_idx * chunk_size + i,
305
333
  )
306
334
  for i, datapoint in enumerate(chunk)
307
335
  ]