scrapely-python 0.1.20__py3-none-any.whl → 0.1.22__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.
scrapely/__init__.py CHANGED
@@ -132,7 +132,7 @@ from .errors import (
132
132
  raise_for_status,
133
133
  )
134
134
 
135
- __version__ = '0.1.20'
135
+ __version__ = '0.1.22'
136
136
  __all__ = [
137
137
  # Core
138
138
  'Actor',
scrapely/actor.py CHANGED
@@ -783,6 +783,8 @@ class Actor(metaclass=_ActorMeta):
783
783
  *,
784
784
  event_name: Optional[str] = None,
785
785
  charges: Optional[Dict[str, int]] = None,
786
+ charge_per: str = 'row',
787
+ dataset: Optional[Union[str, Dataset]] = None,
786
788
  ) -> ChargeResult:
787
789
  """Push data to the default dataset, charging pay-per-event items.
788
790
 
@@ -802,6 +804,16 @@ class Actor(metaclass=_ActorMeta):
802
804
  charges 1 ``result`` and 3 ``page`` events for every row. A row is stored
803
805
  only when all of its events were charged; rows the spending limit cannot
804
806
  pay for in full are not stored. Cannot be combined with ``charged_event_name``.
807
+ charge_per: ``'row'`` (default) charges ``charges`` for every row; ``'batch'``
808
+ charges them once for the whole push, e.g.
809
+ ``push_data(page_rows, charges={'document': 1}, charge_per='batch')``:
810
+ all rows are stored if the charge is accepted, none if it is refused.
811
+ dataset: Push to this named dataset (name, or a Dataset from ``open_dataset``)
812
+ instead of the default one, with the same charge-first semantics:
813
+ ``push_data(rows, charged_event_name='comment', dataset=f'comments-{run_id}')``.
814
+ Rows of a named dataset carry no implicit default-dataset-item event and are
815
+ not gated by the default dataset's spending-limit safety net; charged pushes
816
+ store only the rows that were charged.
805
817
 
806
818
  Returns:
807
819
  ChargeResult describing how many items were charged and pushed.
@@ -822,6 +834,13 @@ class Actor(metaclass=_ActorMeta):
822
834
  if cls._default_dataset is None:
823
835
  cls._default_dataset = await cls.open_dataset()
824
836
 
837
+ target = cls._default_dataset
838
+ if dataset is not None:
839
+ target = dataset if isinstance(dataset, Dataset) else await cls.open_dataset(name=dataset)
840
+ is_default = target is cls._default_dataset or (
841
+ getattr(target, 'id', None) is not None and target.id == getattr(cls._default_dataset, 'id', None)
842
+ )
843
+
825
844
  items = data if isinstance(data, list) else [data]
826
845
  charging_manager = cls.get_charging_manager()
827
846
  if charges is not None:
@@ -831,15 +850,18 @@ class Actor(metaclass=_ActorMeta):
831
850
  charging_manager,
832
851
  items,
833
852
  charges,
834
- True, # Actor.push_data always targets the default dataset
835
- cls._default_dataset.push_data,
853
+ is_default,
854
+ target.push_data,
855
+ charge_per=charge_per,
836
856
  )
857
+ if charge_per != 'row':
858
+ raise ValueError("charge_per needs charges={...}")
837
859
  return await push_data_and_charge(
838
860
  charging_manager,
839
861
  items,
840
862
  event_name,
841
- True, # Actor.push_data always targets the default dataset
842
- cls._default_dataset.push_data,
863
+ is_default,
864
+ target.push_data,
843
865
  )
844
866
 
845
867
  @classmethod
scrapely/charging.py CHANGED
@@ -1657,9 +1657,15 @@ async def push_data_with_charges(
1657
1657
  charges: Dict[str, int],
1658
1658
  is_default_dataset: bool,
1659
1659
  push_fn: Any,
1660
+ charge_per: str = 'row',
1660
1661
  ) -> ChargeResult:
1661
1662
  """Push rows that each cost several events, atomically per row.
1662
1663
 
1664
+ ``charge_per='batch'``: ``charges`` are charged ONCE for the whole push (e.g. one
1665
+ ``document`` for all of its page rows). If every event is accepted all rows are
1666
+ stored, otherwise none (and nothing stays billed). The prepaid-row balance is
1667
+ neither used nor changed.
1668
+
1663
1669
  ``charges`` maps event name -> events per row, e.g. ``{'result': 1, 'page': 3}``.
1664
1670
  A row is stored only when every one of its events was charged: the full set
1665
1671
  is charged first, rows the budget cannot fully pay for are not stored, and
@@ -1669,6 +1675,9 @@ async def push_data_with_charges(
1669
1675
  dataset the default-dataset-item event (when priced) is added per row.
1670
1676
  """
1671
1677
  charges = _validate_row_charges(charges)
1678
+ if charge_per not in ('row', 'batch'):
1679
+ raise ValueError("charge_per must be 'row' or 'batch'")
1680
+ batch = charge_per == 'batch'
1672
1681
  rows_in = items if isinstance(items, list) else [items]
1673
1682
  if not rows_in:
1674
1683
  return ChargeResult(chargeable_within_limit={}, charged_count=0, event_charge_limit_reached=False)
@@ -1691,7 +1700,8 @@ async def push_data_with_charges(
1691
1700
  resolve_default_dataset_item_event_name(charging_manager._per_event_prices)
1692
1701
  if default_event and default_event not in per_row and \
1693
1702
  (charging_manager._calculate_event_price(default_event) or Decimal('0')) > 0:
1694
- per_row[default_event] = 1
1703
+ # Per stored row, also for a batch.
1704
+ per_row[default_event] = len(rows_in) if batch else 1
1695
1705
  if not per_row:
1696
1706
  # Every listed event is free: a plain (unpaid) push.
1697
1707
  return await _unpaid_push_locked(charging_manager, rows_in, is_default_dataset, push_fn)
@@ -1702,7 +1712,8 @@ async def push_data_with_charges(
1702
1712
  row_cost = sum(
1703
1713
  (charging_manager._calculate_event_price(n) or Decimal('0')) * Decimal(c) for n, c in per_row.items()
1704
1714
  )
1705
- rows = len(rows_in)
1715
+ # Units charged for: rows, or the one batch (row_cost is then the batch's cost).
1716
+ rows = 1 if batch else len(rows_in)
1706
1717
  if charging_manager._max_total_charge_usd is not None and row_cost > 0:
1707
1718
  remaining = charging_manager._remaining_budget_usd()
1708
1719
  rows = min(rows, max(0, int(remaining / row_cost)))
@@ -1775,11 +1786,11 @@ async def push_data_with_charges(
1775
1786
  event_charge_limit_reached=True,
1776
1787
  )
1777
1788
 
1778
- kept = rows_in[:rows]
1779
- if rows < len(rows_in):
1789
+ kept = rows_in if batch else rows_in[:rows]
1790
+ if len(kept) < len(rows_in):
1780
1791
  charging_manager._mark_charge_limit_hit()
1781
1792
  if is_default_dataset:
1782
- charging_manager._note_refused_default_dataset_items(len(rows_in) - rows)
1793
+ charging_manager._note_refused_default_dataset_items(len(rows_in) - len(kept))
1783
1794
  try:
1784
1795
  await _gated_push(push_fn, kept)
1785
1796
  except Exception as push_err:
@@ -1789,8 +1800,8 @@ async def push_data_with_charges(
1789
1800
  raise
1790
1801
  return ChargeResult(
1791
1802
  chargeable_within_limit=charging_manager.compute_chargeable(),
1792
- charged_count=rows,
1793
- event_charge_limit_reached=rows < len(rows_in) or charging_manager.is_charge_limit_reached(),
1803
+ charged_count=len(kept),
1804
+ event_charge_limit_reached=len(kept) < len(rows_in) or charging_manager.is_charge_limit_reached(),
1794
1805
  )
1795
1806
 
1796
1807
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrapely-python
3
- Version: 0.1.20
3
+ Version: 0.1.22
4
4
  Summary: Scrapely Python SDK - A toolkit for building Actors on the Scrapely platform
5
5
  Author-email: Scrapely <support@scrape.ly>
6
6
  License-Expression: MIT AND Apache-2.0
@@ -1,9 +1,9 @@
1
- scrapely/__init__.py,sha256=i4A_AaMoKY5cMXqvx7Df9eyucWihcJTRz4ra4HobviI,5746
1
+ scrapely/__init__.py,sha256=cxlsfPD_FlaCCerWYImS5rGRnKxE9INU61hDl3C04Y0,5746
2
2
  scrapely/_browser_locations.py,sha256=8QVVnkms2bDn3_LDYgYDR59MvFx8757l_JYnZ7xb3oI,145269
3
- scrapely/actor.py,sha256=Lsb4_14RCGQy-EWACnqijo7Hz5xvPKSDZcu4B8_ZrHc,81685
3
+ scrapely/actor.py,sha256=yLy05XzoFwslw0D4aSM38rDIGd81JvPqpngY467hWxQ,82969
4
4
  scrapely/browser_configuration.py,sha256=jAZapNSHLFW05B96df4wNBtcyiiU9DA3ml0yTL1FwfA,5770
5
5
  scrapely/cdp_retry.py,sha256=hdTJEAIKhApM7IOk1DrQEl8NoflSJSmqd4RbNJ2IsgI,8540
6
- scrapely/charging.py,sha256=uba7lPFx7ENY4aPjqQAN-SDY1wjXZ83jLxcnyGxYDTE,83121
6
+ scrapely/charging.py,sha256=ns-kuUJtl8hBI4-3iBisC62BggJG67yYOa1yVVItoUI,83800
7
7
  scrapely/charging_client.py,sha256=-4q-Ww7gu9EZXpM9QIn1Vd4vwAXmgL_WEKqaQIx3ViA,3961
8
8
  scrapely/configuration.py,sha256=Of-lKm7hoPGUQO8d08kV7fAy2co9QTfb8Of_8ds5PSA,17028
9
9
  scrapely/errors.py,sha256=DCAlVvE3jToZuPEG2HVDL7i8D8hLboomQ5JpSOcsXoQ,4107
@@ -39,10 +39,10 @@ scrapely/storage/_file_system/_key_value_store_client.py,sha256=4Ipgbh4qEqAkwQJ9
39
39
  scrapely/storage/_file_system/_storage_client.py,sha256=aaGJ4P0QAtIgEMl6yHpuobeWFD5M4rMC5HwhOOvuGuo,1794
40
40
  scrapely/storage_clients/__init__.py,sha256=cs1YX7JV2_GKBXeiiJgkQFali08b5_y_jH6Yw7iROAA,656
41
41
  scrapely/storages/__init__.py,sha256=4PAELhJNZ3exJ7Uouicn-bCQ58NafKjhLBixQ8ATQc4,173
42
- scrapely_python-0.1.20.dist-info/licenses/LICENSE,sha256=pQGY0XwjIYh-a2HfawxHq1en53bXkFkDj2Q70jKWHr4,1088
43
- scrapely_python-0.1.20.dist-info/licenses/LICENSE-APACHE,sha256=N8m0FzMq8i4UbTgXZvKEZJSD-27OF8pWZYS_atz4kTU,11355
44
- scrapely_python-0.1.20.dist-info/licenses/NOTICE,sha256=DEsNrw6UdSIqdYYqMWVFgJnyt6jUivTD7iFU6YM_e1A,1122
45
- scrapely_python-0.1.20.dist-info/METADATA,sha256=ilbOda4UXoNvHh1O91s-GUv8aQ9C8gA1HF_48yb2Tp8,3487
46
- scrapely_python-0.1.20.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
47
- scrapely_python-0.1.20.dist-info/top_level.txt,sha256=T4B9UMrs4q99QSAFyD1zdXhl40KnLhBKqrDkPhOTsmY,9
48
- scrapely_python-0.1.20.dist-info/RECORD,,
42
+ scrapely_python-0.1.22.dist-info/licenses/LICENSE,sha256=pQGY0XwjIYh-a2HfawxHq1en53bXkFkDj2Q70jKWHr4,1088
43
+ scrapely_python-0.1.22.dist-info/licenses/LICENSE-APACHE,sha256=N8m0FzMq8i4UbTgXZvKEZJSD-27OF8pWZYS_atz4kTU,11355
44
+ scrapely_python-0.1.22.dist-info/licenses/NOTICE,sha256=DEsNrw6UdSIqdYYqMWVFgJnyt6jUivTD7iFU6YM_e1A,1122
45
+ scrapely_python-0.1.22.dist-info/METADATA,sha256=K-JZZELeDs7kIXmXodCXXVF3YCvozeqESmkQ5StszVw,3487
46
+ scrapely_python-0.1.22.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
47
+ scrapely_python-0.1.22.dist-info/top_level.txt,sha256=T4B9UMrs4q99QSAFyD1zdXhl40KnLhBKqrDkPhOTsmY,9
48
+ scrapely_python-0.1.22.dist-info/RECORD,,