This is an automated email from the ASF dual-hosted git repository.

damccorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new dbf9e500e56 [Python] Return every group from Regex.find_all with 
Regex.ALL (#40399)
dbf9e500e56 is described below

commit dbf9e500e5626a6409bcfb58be121cec31b34a6d
Author: Anish Mehta <[email protected]>
AuthorDate: Mon Oct 12 23:44:18 2026 +0530

    [Python] Return every group from Regex.find_all with Regex.ALL (#40399)
    
    * [Python] Return every group from Regex.find_all with Regex.ALL
    
    * Update sdks/python/apache_beam/transforms/util.py
    
    Co-authored-by: Danny McCormick <[email protected]>
    
    * Test outputEmpty=False with two groups and with empty matches
    
    * Gate Regex.ALL outputEmpty on the full match and note the change in 
CHANGES.md
    
    * Update sdks/python/apache_beam/transforms/util.py
    
    Co-authored-by: Danny McCormick <[email protected]>
    
    * Apply suggestion from @damccorm
    
    Co-authored-by: Danny McCormick <[email protected]>
    
    * Drop the leftover outputEmpty line and fix the docstring indent
    
    * Update CHANGES.md
    
    Co-authored-by: Danny McCormick <[email protected]>
    
    ---------
    
    Co-authored-by: Danny McCormick <[email protected]>
---
 CHANGES.md                                      |  1 +
 sdks/python/apache_beam/transforms/util.py      | 13 ++++++-----
 sdks/python/apache_beam/transforms/util_test.py | 29 ++++++++++++++++++++++++-
 3 files changed, 36 insertions(+), 7 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 0525ff92792..b0767fbe9d6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -81,6 +81,7 @@
 * (Java) JmsIO migrated to `jakarta.jms` (JMS 3.1) and ActiveMQ 6.2.5. User 
code implementing `JmsIO.MessageMapper`,
   `valueMapper`, `topicNameMapper`, or providing a `ConnectionFactory` must 
update imports from `javax.jms.*` to
   `jakarta.jms.*`. The module now requires Java 17 at runtime 
([#38729](https://github.com/apache/beam/issues/38729)).
+* (Python) `Regex.find_all` with `group=Regex.ALL` now returns every group, as 
`(match, group1, group2, ...)`, instead of only group 1, and with 
`outputEmpty=False` it drops a match only when the whole match is empty, 
matching the `group=0` behavior 
([#40399](https://github.com/apache/beam/pull/40399)).
 
 ## Deprecations
 
diff --git a/sdks/python/apache_beam/transforms/util.py 
b/sdks/python/apache_beam/transforms/util.py
index d99ebb456b5..b7885544dbf 100644
--- a/sdks/python/apache_beam/transforms/util.py
+++ b/sdks/python/apache_beam/transforms/util.py
@@ -2219,28 +2219,29 @@ class Regex(object):
 
   @staticmethod
   @typehints.with_input_types(str)
-  @typehints.with_output_types(Union[list[str], list[tuple[str, str]]])
+  @typehints.with_output_types(Union[list[str], list[tuple[str, ...]]])
   @ptransform_fn
   def find_all(pcoll, regex, group=0, outputEmpty=True):
     """
     Returns the matches if a portion of the line matches the Regex. By default,
     list of group 0 will return with empty items. To get all groups, pass the
     `Regex.ALL` flag in the `group` parameter which returns all the groups in
-    the tuple format.
+    the tuple format. When using `Regex.ALL`, each item is formatted as a
+    tuple: `(match, group1, group2, ...)`.
 
     Args:
       regex: the regular expression string or (re.compile) pattern.
       group: (optional) name of the group, it can be integer or a string value.
-      outputEmpty: (optional) Should empty be output. True to output empties
-        and false if not.
+      outputEmpty: (optional) Whether to allow matches with empty groups.
+        If false, these outputs will be silently dropped.
     """
     regex = Regex._regex_compile(regex)
 
     def _process(element):
       matches = regex.finditer(element)
       if group == Regex.ALL:
-        yield [(m.group(), m.groups()[0]) for m in matches
-               if outputEmpty or m.groups()[0]]
+        yield [(m.group(), *m.groups()) for m in matches
+               if outputEmpty or m.group()]
       else:
         yield [m.group(group) for m in matches if outputEmpty or 
m.group(group)]
 
diff --git a/sdks/python/apache_beam/transforms/util_test.py 
b/sdks/python/apache_beam/transforms/util_test.py
index e8e7d762b81..bba09245a0f 100644
--- a/sdks/python/apache_beam/transforms/util_test.py
+++ b/sdks/python/apache_beam/transforms/util_test.py
@@ -2714,9 +2714,36 @@ class RegexTest(unittest.TestCase):
           pcol | 'all non empty groups' >> util.Regex.find_all(
               'a(b*)', util.Regex.ALL, outputEmpty=False),
           equal_to([[('ab', 'b'), ('ab', 'b'), ('ab', 'b')],
-                    [('abb', 'bb'), ('abbb', 'bbb')]]),
+                    [('abb', 'bb'), ('a', ''), ('abbb', 'bbb')]]),
           label='CheckAllNonEmptyGroups')
 
+  def test_find_all_groups_multiple_and_no_groups(self):
+    with TestPipeline() as p:
+      pcol = (p | beam.Create(['a=1 b=2 c=']))
+      assert_that(
+          pcol
+          | 'two groups' >> util.Regex.find_all(r'(\w)=(\d?)', util.Regex.ALL),
+          equal_to([[('a=1', 'a', '1'), ('b=2', 'b', '2'), ('c=', 'c', '')]]),
+          label='CheckTwoGroups')
+
+      assert_that(
+          pcol | 'two groups non empty match' >> util.Regex.find_all(
+              r'(\w)=(\d?)', util.Regex.ALL, outputEmpty=False),
+          equal_to([[('a=1', 'a', '1'), ('b=2', 'b', '2'), ('c=', 'c', '')]]),
+          label='CheckTwoGroupsNonEmptyMatch')
+
+      digits = (p | 'create digits' >> beam.Create(['1 2']))
+      assert_that(
+          digits | 'no groups' >> util.Regex.find_all(r'\d*', util.Regex.ALL),
+          equal_to([[('1', ), ('', ), ('2', ), ('', )]]),
+          label='CheckNoGroups')
+
+      assert_that(
+          digits | 'no groups non empty' >> util.Regex.find_all(
+              r'\d*', util.Regex.ALL, outputEmpty=False),
+          equal_to([[('1', ), ('2', )]]),
+          label='CheckNoGroupsNonEmpty')
+
   def test_find_kv(self):
     with TestPipeline() as p:
       pcol = (p | beam.Create(['a b c d']))

Reply via email to