impectPy 2.5.9__tar.gz → 2.5.10__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.
Files changed (36) hide show
  1. {impectpy-2.5.9 → impectpy-2.5.10}/PKG-INFO +16 -4
  2. {impectpy-2.5.9 → impectpy-2.5.10}/README.md +16 -4
  3. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/__init__.py +2 -1
  4. impectpy-2.5.10/impectPy/data.py +54 -0
  5. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/impect.py +27 -2
  6. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy.egg-info/PKG-INFO +16 -4
  7. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy.egg-info/SOURCES.txt +3 -1
  8. {impectpy-2.5.9 → impectpy-2.5.10}/setup.py +1 -1
  9. impectpy-2.5.10/tests/test_package.py +291 -0
  10. {impectpy-2.5.9 → impectpy-2.5.10}/LICENSE.md +0 -0
  11. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/access_token.py +0 -0
  12. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/config.py +0 -0
  13. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/events.py +0 -0
  14. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/formations.py +0 -0
  15. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/generate_xml.py +0 -0
  16. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/helpers.py +0 -0
  17. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/iterations.py +0 -0
  18. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/matches.py +0 -0
  19. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/player_iteration_averages.py +0 -0
  20. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/player_iteration_scores.py +0 -0
  21. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/player_match_scores.py +0 -0
  22. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/player_matchsums.py +0 -0
  23. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/player_profile_scores.py +0 -0
  24. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/set_pieces.py +0 -0
  25. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/squad_coefficients.py +0 -0
  26. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/squad_iteration_averages.py +0 -0
  27. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/squad_iteration_scores.py +0 -0
  28. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/squad_match_scores.py +0 -0
  29. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/squad_matchsums.py +0 -0
  30. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/squad_ratings.py +0 -0
  31. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/starting_positions.py +0 -0
  32. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy/substitutions.py +0 -0
  33. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy.egg-info/dependency_links.txt +0 -0
  34. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy.egg-info/requires.txt +0 -0
  35. {impectpy-2.5.9 → impectpy-2.5.10}/impectPy.egg-info/top_level.txt +0 -0
  36. {impectpy-2.5.9 → impectpy-2.5.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: impectPy
3
- Version: 2.5.9
3
+ Version: 2.5.10
4
4
  Summary: A Python package to facilitate interaction with the Impect customer API
5
5
  Home-page: https://github.com/ImpectAPI/impectPy
6
6
  Author: Impect
@@ -25,9 +25,9 @@ Dynamic: summary
25
25
 
26
26
  A package provided by: Impect GmbH
27
27
 
28
- Version: v2.5.9
28
+ Version: v2.5.10
29
29
 
30
- **Updated: April 9th 2026**
30
+ **Updated: April 13th 2026**
31
31
 
32
32
  ---
33
33
 
@@ -58,7 +58,7 @@ pip install impectPy
58
58
  You can also install it from [GitHub](https://github.com/) with:
59
59
 
60
60
  ```cmd
61
- pip install git+https://github.com/ImpectAPI/impectPy.git@v2.5.9
61
+ pip install git+https://github.com/ImpectAPI/impectPy.git@v2.5.10
62
62
  ```
63
63
 
64
64
  ## Usage
@@ -302,6 +302,15 @@ calls made on the client side already. The rate limit is read from the first lim
302
302
  policy sent back by the API, so if this limit increases over time, this package will
303
303
  act accordingly.
304
304
 
305
+ ### Generic API Call
306
+ You can also use this package to make individual API calls querying a specific endpoint
307
+ such as the "squads" endpoint.
308
+
309
+ ```python
310
+ # query any IMPECT customer API endpoint
311
+ data = ip.getData(url=f"https://api.impect.com/v5/customerapi/iterations/{iteration}/squads", token=token)
312
+ ```
313
+
305
314
  ### SportsCodeXML
306
315
 
307
316
  It is also possible to convert a dataframe containing event data into an XML file,
@@ -449,6 +458,9 @@ squadIterationScores = api.getSquadIterationScores(iteration=iteration)
449
458
 
450
459
  # get player profile scores
451
460
  playerProfileScores = api.getPlayerProfileScores(iteration=iteration, positions=positions)
461
+
462
+ # query single API endpoint (e.g. squads for iteration 518)
463
+ data = api.getData(url=f"https://api.impect.com/v5/customerapi/iterations/{iteration}/squads")
452
464
  ```
453
465
 
454
466
  ## Final Notes
@@ -2,9 +2,9 @@
2
2
 
3
3
  A package provided by: Impect GmbH
4
4
 
5
- Version: v2.5.9
5
+ Version: v2.5.10
6
6
 
7
- **Updated: April 9th 2026**
7
+ **Updated: April 13th 2026**
8
8
 
9
9
  ---
10
10
 
@@ -35,7 +35,7 @@ pip install impectPy
35
35
  You can also install it from [GitHub](https://github.com/) with:
36
36
 
37
37
  ```cmd
38
- pip install git+https://github.com/ImpectAPI/impectPy.git@v2.5.9
38
+ pip install git+https://github.com/ImpectAPI/impectPy.git@v2.5.10
39
39
  ```
40
40
 
41
41
  ## Usage
@@ -279,6 +279,15 @@ calls made on the client side already. The rate limit is read from the first lim
279
279
  policy sent back by the API, so if this limit increases over time, this package will
280
280
  act accordingly.
281
281
 
282
+ ### Generic API Call
283
+ You can also use this package to make individual API calls querying a specific endpoint
284
+ such as the "squads" endpoint.
285
+
286
+ ```python
287
+ # query any IMPECT customer API endpoint
288
+ data = ip.getData(url=f"https://api.impect.com/v5/customerapi/iterations/{iteration}/squads", token=token)
289
+ ```
290
+
282
291
  ### SportsCodeXML
283
292
 
284
293
  It is also possible to convert a dataframe containing event data into an XML file,
@@ -426,9 +435,12 @@ squadIterationScores = api.getSquadIterationScores(iteration=iteration)
426
435
 
427
436
  # get player profile scores
428
437
  playerProfileScores = api.getPlayerProfileScores(iteration=iteration, positions=positions)
438
+
439
+ # query single API endpoint (e.g. squads for iteration 518)
440
+ data = api.getData(url=f"https://api.impect.com/v5/customerapi/iterations/{iteration}/squads")
429
441
  ```
430
442
 
431
443
  ## Final Notes
432
444
 
433
445
  Further documentation on the data and explanations of variables can be
434
- found in our [Glossary](https://glossary.impect.com/).
446
+ found in our [Glossary](https://glossary.impect.com/).
@@ -1,5 +1,5 @@
1
1
  # define version attribute
2
- __version__ = "2.5.8"
2
+ __version__ = "2.5.10"
3
3
 
4
4
  # import modules
5
5
  from .access_token import getAccessToken
@@ -22,5 +22,6 @@ from .squad_coefficients import getSquadCoefficients
22
22
  from .formations import getFormations
23
23
  from .substitutions import getSubstitutions
24
24
  from .starting_positions import getStartingPositions
25
+ from .data import getData
25
26
  from .config import Config as Config
26
27
  from .impect import Impect as Impect
@@ -0,0 +1,54 @@
1
+ # load packages
2
+ import pandas as pd
3
+ from typing import Optional, Dict, Any
4
+ from impectPy.helpers import RateLimitedAPI, ImpectSession
5
+
6
+
7
+ ######
8
+ #
9
+ # This function returns a dataframe from any Impect API endpoint
10
+ #
11
+ ######
12
+
13
+
14
+ def getData(
15
+ url: str, token: str, method: str = "GET", data: Optional[Dict[str, Any]] = None,
16
+ session: Optional[ImpectSession] = None
17
+ ) -> pd.DataFrame:
18
+ """Returns a processed DataFrame from any Impect API endpoint.
19
+
20
+ Args:
21
+ url (str): Full URL of the API endpoint.
22
+ token (str): Bearer token for authentication.
23
+ method (str): HTTP method. Defaults to "GET".
24
+ data (Optional[Dict[str, Any]]): Optional request body. Defaults to None.
25
+ session (Optional[ImpectSession]): The session object to use for the API calls. Defaults to a new ImpectSession.
26
+
27
+ Returns:
28
+ pd.DataFrame: Processed response data.
29
+ """
30
+ # create an instance of RateLimitedAPI
31
+ connection = RateLimitedAPI(session or ImpectSession())
32
+
33
+ # set auth header
34
+ connection.session.headers.update({"Authorization": f"Bearer {token}"})
35
+
36
+ return getDataFromHost(url=url, method=method, connection=connection, data=data)
37
+
38
+
39
+ def getDataFromHost(
40
+ url: str, method: str, connection: RateLimitedAPI, data: Optional[Dict[str, Any]] = None
41
+ ) -> pd.DataFrame:
42
+ """Core implementation: executes a rate-limited API call and returns a processed DataFrame.
43
+
44
+ Args:
45
+ url (str): Full URL of the API endpoint.
46
+ method (str): HTTP method.
47
+ connection (RateLimitedAPI): Authenticated connection object.
48
+ data (Optional[Dict[str, Any]]): Optional request body. Defaults to None.
49
+
50
+ Returns:
51
+ pd.DataFrame: Processed response data.
52
+ """
53
+ response = connection.make_api_request_limited(url=url, method=method, data=data)
54
+ return response.process_response(endpoint=url)
@@ -1,4 +1,10 @@
1
+ from typing import Optional, Dict, Any
2
+ from xml.etree import ElementTree as ET
3
+
4
+ import pandas as pd
5
+
1
6
  from impectPy.config import Config
7
+
2
8
  from .helpers import RateLimitedAPI
3
9
  from .access_token import getAccessTokenFromUrl
4
10
  from .iterations import getIterationsFromHost
@@ -20,8 +26,7 @@ from .squad_coefficients import getSquadCoefficientsFromHost
20
26
  from .formations import getFormationsFromHost
21
27
  from .substitutions import getSubstitutionsFromHost
22
28
  from .starting_positions import getStartingPositionsFromHost
23
- import pandas as pd
24
- from xml.etree import ElementTree as ET
29
+ from .data import getDataFromHost
25
30
 
26
31
 
27
32
  class Impect:
@@ -130,6 +135,26 @@ class Impect:
130
135
  matches, self.connection, self.__config.HOST
131
136
  )
132
137
 
138
+ def getData(
139
+ self, url: str, method: str = "GET", data: Optional[Dict[str, Any]] = None
140
+ ) -> pd.DataFrame:
141
+ """Returns a processed DataFrame from any Impect API endpoint.
142
+
143
+ Accepts a full URL or a path. If a path is provided (does not start
144
+ with 'http'), the configured host is prepended automatically.
145
+
146
+ Args:
147
+ url (str): Full URL or path of the API endpoint.
148
+ method (str): HTTP method. Defaults to "GET".
149
+ data (Optional[Dict[str, Any]]): Optional request body. Defaults to None.
150
+
151
+ Returns:
152
+ pd.DataFrame: Processed response data.
153
+ """
154
+ if not url.startswith("http"):
155
+ url = f"{self.__config.HOST}{url}"
156
+ return getDataFromHost(url=url, method=method, connection=self.connection, data=data)
157
+
133
158
  @staticmethod
134
159
  def generateXML(
135
160
  events: pd.DataFrame,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: impectPy
3
- Version: 2.5.9
3
+ Version: 2.5.10
4
4
  Summary: A Python package to facilitate interaction with the Impect customer API
5
5
  Home-page: https://github.com/ImpectAPI/impectPy
6
6
  Author: Impect
@@ -25,9 +25,9 @@ Dynamic: summary
25
25
 
26
26
  A package provided by: Impect GmbH
27
27
 
28
- Version: v2.5.9
28
+ Version: v2.5.10
29
29
 
30
- **Updated: April 9th 2026**
30
+ **Updated: April 13th 2026**
31
31
 
32
32
  ---
33
33
 
@@ -58,7 +58,7 @@ pip install impectPy
58
58
  You can also install it from [GitHub](https://github.com/) with:
59
59
 
60
60
  ```cmd
61
- pip install git+https://github.com/ImpectAPI/impectPy.git@v2.5.9
61
+ pip install git+https://github.com/ImpectAPI/impectPy.git@v2.5.10
62
62
  ```
63
63
 
64
64
  ## Usage
@@ -302,6 +302,15 @@ calls made on the client side already. The rate limit is read from the first lim
302
302
  policy sent back by the API, so if this limit increases over time, this package will
303
303
  act accordingly.
304
304
 
305
+ ### Generic API Call
306
+ You can also use this package to make individual API calls querying a specific endpoint
307
+ such as the "squads" endpoint.
308
+
309
+ ```python
310
+ # query any IMPECT customer API endpoint
311
+ data = ip.getData(url=f"https://api.impect.com/v5/customerapi/iterations/{iteration}/squads", token=token)
312
+ ```
313
+
305
314
  ### SportsCodeXML
306
315
 
307
316
  It is also possible to convert a dataframe containing event data into an XML file,
@@ -449,6 +458,9 @@ squadIterationScores = api.getSquadIterationScores(iteration=iteration)
449
458
 
450
459
  # get player profile scores
451
460
  playerProfileScores = api.getPlayerProfileScores(iteration=iteration, positions=positions)
461
+
462
+ # query single API endpoint (e.g. squads for iteration 518)
463
+ data = api.getData(url=f"https://api.impect.com/v5/customerapi/iterations/{iteration}/squads")
452
464
  ```
453
465
 
454
466
  ## Final Notes
@@ -4,6 +4,7 @@ setup.py
4
4
  impectPy/__init__.py
5
5
  impectPy/access_token.py
6
6
  impectPy/config.py
7
+ impectPy/data.py
7
8
  impectPy/events.py
8
9
  impectPy/formations.py
9
10
  impectPy/generate_xml.py
@@ -29,4 +30,5 @@ impectPy.egg-info/PKG-INFO
29
30
  impectPy.egg-info/SOURCES.txt
30
31
  impectPy.egg-info/dependency_links.txt
31
32
  impectPy.egg-info/requires.txt
32
- impectPy.egg-info/top_level.txt
33
+ impectPy.egg-info/top_level.txt
34
+ tests/test_package.py
@@ -17,7 +17,7 @@ setup(
17
17
  "pandas>=2.2.0",
18
18
  "numpy>=1.24.2"],
19
19
  # *strongly* suggested for sharing
20
- version="2.5.9",
20
+ version="2.5.10",
21
21
  # The license can be anything you like
22
22
  license="MIT",
23
23
  description="A Python package to facilitate interaction with the Impect customer API",
@@ -0,0 +1,291 @@
1
+ # load packages
2
+ import sys
3
+ import importlib
4
+ import logging
5
+ import os
6
+ import re
7
+ import subprocess
8
+ from pathlib import Path
9
+ from tqdm import tqdm
10
+ from dotenv import load_dotenv
11
+ import pandas as pd
12
+
13
+ execute_functions = True
14
+
15
+ # branch comparison configuration
16
+ BRANCH_A_NAME = "main"
17
+ BRANCH_B_NAME = "release"
18
+
19
+ REPO_ROOT = Path(__file__).resolve().parents[1]
20
+ BRANCH_PATHS = {
21
+ BRANCH_A_NAME: REPO_ROOT,
22
+ BRANCH_B_NAME: REPO_ROOT,
23
+ }
24
+
25
+
26
+ def checkout_branch(branch_name: str):
27
+ repo_path = BRANCH_PATHS.get(branch_name)
28
+ if repo_path is None or not repo_path.exists():
29
+ raise ValueError(f"Unknown branch or missing repo path: '{branch_name}'")
30
+
31
+ try:
32
+ subprocess.run(
33
+ ["git", "-C", str(repo_path), "fetch"],
34
+ check=True,
35
+ stdout=subprocess.DEVNULL
36
+ )
37
+ subprocess.run(
38
+ ["git", "-C", str(repo_path), "checkout", branch_name],
39
+ check=True,
40
+ stdout=subprocess.DEVNULL
41
+ )
42
+ except subprocess.CalledProcessError as e:
43
+ print(f"Error checking out branch '{branch_name}' in {repo_path}: {e}")
44
+
45
+
46
+ def load_impect(env: str):
47
+ modules_to_remove = [m for m in sys.modules if m.startswith("impectPy")]
48
+
49
+ for module_name in modules_to_remove:
50
+ del sys.modules[module_name]
51
+
52
+ sys.path = [p for p in sys.path if "impectPy" not in p]
53
+
54
+ importlib.invalidate_caches()
55
+
56
+ repo_root = BRANCH_PATHS.get(env)
57
+ if repo_root is None:
58
+ raise ValueError(f"Unknown environment '{env}'. Check BRANCH_PATHS.")
59
+
60
+ sys.path.insert(0, str(repo_root))
61
+ return importlib.import_module("impectPy")
62
+
63
+ if execute_functions:
64
+
65
+ # define login credentials
66
+ load_dotenv()
67
+ username = os.getenv("USERNAME")
68
+ password = os.getenv("PASSWORD")
69
+
70
+ # define logger
71
+ logging.basicConfig(
72
+ level=logging.ERROR,
73
+ format="%(asctime)s - %(name)s - %(levelname)s - ID=%(id)s - URL=%(url)s - %(message)s"
74
+ )
75
+
76
+ # define object to be passed onto functions
77
+ iteration = 1385
78
+
79
+ matches = [
80
+ 232434,
81
+ 202485
82
+ ]
83
+
84
+ positions = [
85
+ "GOALKEEPER",
86
+ "LEFT_WINGBACK_DEFENDER",
87
+ "RIGHT_WINGBACK_DEFENDER",
88
+ "CENTRAL_DEFENDER",
89
+ "DEFENSE_MIDFIELD",
90
+ "CENTRAL_MIDFIELD",
91
+ "ATTACKING_MIDFIELD",
92
+ "LEFT_WINGER",
93
+ "RIGHT_WINGER",
94
+ "CENTER_FORWARD"
95
+ ]
96
+
97
+ # define branches
98
+ branches = [BRANCH_A_NAME, BRANCH_B_NAME]
99
+
100
+ # iterate over envs
101
+ for branch in branches:
102
+
103
+ os.makedirs(f"files/{branch}", exist_ok=True)
104
+
105
+ checkout_branch(branch)
106
+ impectPy = load_impect(branch)
107
+ print(f"impectPy Version: {impectPy.__version__}")
108
+
109
+ config = impectPy.Config()
110
+ api = impectPy.Impect(config=config)
111
+ api.login(username, password)
112
+
113
+ with tqdm(total=21, desc=f"{branch}: Executing functions...", unit="chunk") as pbar:
114
+
115
+ # get iterations
116
+ iterations = api.getIterations()
117
+ iterations.to_csv(f"files/{branch}/iterations.csv")
118
+ pbar.update()
119
+
120
+ # get squad ratings
121
+ ratings = api.getSquadRatings(iteration)
122
+ ratings.to_csv(f"files/{branch}/ratings.csv")
123
+ pbar.update()
124
+
125
+ # get squad coefficients
126
+ coefficients = api.getSquadCoefficients(iteration)
127
+ coefficients.to_csv(f"files/{branch}/coefficients.csv")
128
+ pbar.update()
129
+
130
+ # get matches
131
+ matchplan = api.getMatches(iteration)
132
+ matchplan.to_csv(f"files/{branch}/matchplan.csv")
133
+ pbar.update()
134
+
135
+ # get match info
136
+ formations = api.getFormations(matches)
137
+ formations.to_csv(f"files/{branch}/formations.csv")
138
+ pbar.update()
139
+ substitutions = api.getSubstitutions(matches)
140
+ substitutions.to_csv(f"files/{branch}/substitutions.csv")
141
+ pbar.update()
142
+ startingPositions = api.getStartingPositions(matches)
143
+ startingPositions.to_csv(f"files/{branch}/startingPositions.csv")
144
+ pbar.update()
145
+
146
+ # get match events
147
+ events = api.getEvents(matches, include_kpis=True, include_set_pieces=True)
148
+ events.to_csv(f"files/{branch}/events.csv")
149
+ pbar.update()
150
+
151
+ # get set pieces
152
+ set_pieces = api.getSetPieces(matches)
153
+ set_pieces.to_csv(f"files/{branch}/set_pieces.csv")
154
+ pbar.update()
155
+
156
+ # get player iteration averages
157
+ playerIterationAverages = api.getPlayerIterationAverages(iteration)
158
+ playerIterationAverages.to_csv(f"files/{branch}/playerIterationAverages.csv")
159
+ pbar.update()
160
+
161
+ # get player matchsums
162
+ playerMatchsums = api.getPlayerMatchsums(matches)
163
+ playerMatchsums.to_csv(f"files/{branch}/playerMatchsums.csv")
164
+ pbar.update()
165
+
166
+ # get squad iteration averages
167
+ squadIterationAverages = api.getSquadIterationAverages(iteration)
168
+ squadIterationAverages.to_csv(f"files/{branch}/squadIterationAverages.csv")
169
+ pbar.update()
170
+
171
+ # get squad matchsums
172
+ squadMatchsums = api.getSquadMatchsums(matches)
173
+ squadMatchsums.to_csv(f"files/{branch}/squadMatchsums.csv")
174
+ pbar.update()
175
+
176
+ # get player match scores
177
+ playerMatchScores = api.getPlayerMatchScores(matches)
178
+ playerMatchScores.to_csv(f"files/{branch}/playerMatchScores.csv")
179
+ pbar.update()
180
+ playerMatchScores_2 = api.getPlayerMatchScores(matches, positions)
181
+ playerMatchScores_2.to_csv(f"files/{branch}/playerMatchScores_2.csv")
182
+ pbar.update()
183
+
184
+ # get squad match scores
185
+ squadMatchScores = api.getSquadMatchScores(matches)
186
+ squadMatchScores.to_csv(f"files/{branch}/squadMatchScores.csv")
187
+ pbar.update()
188
+
189
+ # get player iteration scores
190
+ playerIterationScores = api.getPlayerIterationScores(iteration)
191
+ playerIterationScores.to_csv(f"files/{branch}/playerIterationScores.csv")
192
+ pbar.update()
193
+ playerIterationScores_2 = api.getPlayerIterationScores(iteration, positions)
194
+ playerIterationScores_2.to_csv(f"files/{branch}/playerIterationScores_2.csv")
195
+ pbar.update()
196
+
197
+ # get squad iteration scores
198
+ squadIterationScores = api.getSquadIterationScores(iteration)
199
+ squadIterationScores.to_csv(f"files/{branch}/squadIterationScores.csv")
200
+ pbar.update()
201
+
202
+ # get player profile scores
203
+ playerProfileScores = api.getPlayerProfileScores(iteration, positions)
204
+ playerProfileScores.to_csv(f"files/{branch}/playerProfileScores.csv")
205
+ pbar.update()
206
+
207
+ # # get data using generic getData() method
208
+ # pbar.set_description("Get data using generic getData() method...")
209
+ # data = api.getData(f"https://api.impect.com/v5/customerapi/iterations/{iteration}/players")
210
+ # pbar.update()
211
+
212
+ print("\nRunning auto-diff between source and test outputs...\n")
213
+
214
+ base_path = Path(__file__).parent / "files"
215
+ source_path = base_path / BRANCH_A_NAME
216
+ test_path = base_path / BRANCH_B_NAME
217
+
218
+ excluded_columns = [
219
+ # "playerCountry"
220
+ ]
221
+
222
+ failed = False
223
+ assertion_error_patterns = {
224
+ "column": re.compile(r'column name="([^"]+)"'),
225
+ "percentage": re.compile(r'values are different \(([\d.]+)\s*%\)'),
226
+ "first_diff": re.compile(
227
+ r'At positional index (\d+), first diff:\s*(.*?)\s*!=\s*(.*)'
228
+ ),
229
+ }
230
+
231
+ for src_file in source_path.glob("*.csv"):
232
+ test_file = test_path / src_file.name
233
+
234
+ if not test_file.exists():
235
+ print(f"[MISSING] {src_file.name} not found in test/")
236
+ failed = True
237
+ continue
238
+
239
+ src_df = pd.read_csv(src_file, low_memory=False)
240
+ test_df = pd.read_csv(test_file, low_memory=False)
241
+
242
+ src_df = src_df[[col for col in src_df.columns if col not in excluded_columns]]
243
+ test_df = test_df[[col for col in test_df.columns if col not in excluded_columns]]
244
+
245
+ try:
246
+ pd.testing.assert_frame_equal(
247
+ src_df,
248
+ test_df,
249
+ check_dtype=False,
250
+ check_like=True
251
+ )
252
+ print(f"[OK] {src_file.name}")
253
+ except AssertionError as e:
254
+ parsed_error = str(e)
255
+
256
+ column = assertion_error_patterns["column"].search(parsed_error).group(1)
257
+ percentage = float(assertion_error_patterns["percentage"].search(parsed_error).group(1))
258
+
259
+ first_diff_match = assertion_error_patterns["first_diff"].search(parsed_error)
260
+ if first_diff_match:
261
+ first_diff = {
262
+ "index": int(first_diff_match.group(1)),
263
+ "left": first_diff_match.group(2),
264
+ "right": first_diff_match.group(3),
265
+ }
266
+ else:
267
+ # pandas uses [index]/[left]/[right] array format when all values differ
268
+ idx_match = re.search(r'\[index\]:\s*\[([^]]+)', parsed_error)
269
+ left_match = re.search(r'\[left\]:\s*\[([^]]+)', parsed_error)
270
+ right_match = re.search(r'\[right\]:\s*\[([^]]+)', parsed_error)
271
+ first_diff = {
272
+ "index": idx_match.group(1).split(",")[0].strip() if idx_match else "?",
273
+ "left": left_match.group(1).split(",")[0].strip() if left_match else "?",
274
+ "right": right_match.group(1).split(",")[0].strip() if right_match else "?",
275
+ }
276
+
277
+ result = {
278
+ "column": column,
279
+ "percentage_diff": percentage,
280
+ "first_diff": first_diff,
281
+ }
282
+
283
+ print(f"[DIFF] {src_file.name} : Column '{result['column']}‘ (Delta: {result['percentage_diff']:.2f}%)') : "
284
+ f"First Diff at index {result['first_diff']['index']} "
285
+ f"({result['first_diff']['left']} != {result['first_diff']['right']})")
286
+ failed = True
287
+
288
+ if failed:
289
+ raise AssertionError("Auto-diff failed: source and test outputs differ")
290
+
291
+ print("\nAll source vs test outputs are identical ✅")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes