Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to API #76

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/api/endpoints/clubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ def get_club_players(club_id: str, season_id: Optional[str] = None) -> dict:
tfmkt = TransfermarktClubPlayers(club_id=club_id, season_id=season_id)
club_players = tfmkt.get_club_players()
return club_players


@router.get("/{club_id}/staffs")
def get_club_staffs(club_id: str) -> dict:
tfmkt = TransfermarktClubPlayers(club_id=club_id)
club_staffs = tfmkt.get_club_staffs()
return club_staffs
Comment on lines +33 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a dedicated TransfermarktClubStaffs class

The new endpoint for retrieving club staffs is a good addition and follows the existing pattern. However, there are a few points to consider:

  1. The endpoint is using TransfermarktClubPlayers to get staff information. It would be more appropriate to use a dedicated TransfermarktClubStaffs class for better separation of concerns.

  2. There's no error handling or input validation for the club_id. Consider adding try-except blocks and validating the input to ensure robustness.

  3. The return type is a generic dict. For better type hinting and API documentation, consider creating a Pydantic model for the staff data structure.

Here's a suggested refactoring:

from fastapi import APIRouter, HTTPException
from app.services.clubs.staff import TransfermarktClubStaffs
from app.models.staff import StaffResponse  # Create this Pydantic model

@router.get("/{club_id}/staffs", response_model=StaffResponse)
def get_club_staffs(club_id: str) -> StaffResponse:
    try:
        tfmkt = TransfermarktClubStaffs(club_id=club_id)
        club_staffs = tfmkt.get_club_staffs()
        return StaffResponse(**club_staffs)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail="An unexpected error occurred")

This refactoring introduces error handling, uses a dedicated staff class, and improves type hinting with a Pydantic model.

2 changes: 2 additions & 0 deletions app/services/clubs/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def get_club_profile(self) -> dict:
"countryID": safe_regex(self.get_text_by_xpath(Clubs.Profile.LEAGUE_COUNTRY_ID), REGEX_COUNTRY_ID, "id"),
"countryName": self.get_text_by_xpath(Clubs.Profile.LEAGUE_COUNTRY_NAME),
"tier": self.get_text_by_xpath(Clubs.Profile.LEAGUE_TIER),
"position": self.get_text_by_xpath(Clubs.Profile.LEAGUE_POSITION),
"inLeagueSince": self.get_text_by_xpath(Clubs.Profile.IN_LEAGUE_SINCE),
}
self.response["historicalCrests"] = [
safe_split(crest, "?")[0] for crest in self.get_list_by_xpath(Clubs.Profile.CRESTS_HISTORICAL)
Expand Down
82 changes: 82 additions & 0 deletions app/services/clubs/staff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from dataclasses import dataclass
from datetime import datetime

from app.services.base import TransfermarktBase
from app.utils.regex import REGEX_STAFF_ID
from app.utils.utils import clean_response, extract_from_url, safe_regex
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused import extract_from_url.

The extract_from_url function is imported but not used anywhere in the code. Removing unused imports helps keep the codebase clean and maintainable.

Apply this diff to remove the unused import:

-from app.utils.utils import clean_response, extract_from_url, safe_regex
+from app.utils.utils import clean_response, safe_regex
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from app.utils.utils import clean_response, extract_from_url, safe_regex
from app.utils.utils import clean_response, safe_regex
🧰 Tools
🪛 Ruff

6-6: app.utils.utils.extract_from_url imported but unused

Remove unused import: app.utils.utils.extract_from_url

(F401)

from app.utils.xpath import Clubs


@dataclass
class TransfermarktClubStaffs(TransfermarktBase):
"""
A class for retrieving and parsing the staff members of a football club from Transfermarkt.
Args:
club_id (str): The unique identifier of the football club.
URL (str): The URL template for the club's staff page on Transfermarkt.
"""

club_id: str = None
URL: str = "https://www.transfermarkt.us/-/mitarbeiter/verein/{club_id}/plus/1"
Comment on lines +20 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate club_id during initialization.

The club_id attribute defaults to None, but it's essential for formatting the URL in __post_init__. If club_id is None, formatting the URL will raise a TypeError.

Consider adding a validation check in __post_init__ to ensure club_id is provided:

def __post_init__(self) -> None:
    if not self.club_id:
        raise ValueError("The 'club_id' must be provided and cannot be None.")
    self.URL = self.URL.format(club_id=self.club_id)
    # ... rest of the method

Alternatively, make club_id a required field without a default value:

club_id: str


Comment on lines +20 to +22
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate club_id during initialization.

The club_id attribute defaults to None, but it is essential for formatting the URL in __post_init__. If club_id is None, formatting the URL will raise a TypeError. Consider adding a validation check to ensure club_id is provided.

Apply this diff to add validation:

 def __post_init__(self) -> None:
+    if not self.club_id:
+        raise ValueError("The 'club_id' must be provided and cannot be None.")
     self.URL = self.URL.format(club_id=self.club_id)
     self.page = self.request_url_page()

Alternatively, make club_id a required field without a default value:

-    club_id: str = None
+    club_id: str
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
club_id: str = None
URL: str = "https://www.transfermarkt.us/-/mitarbeiter/verein/{club_id}/plus/1"
club_id: str
URL: str = "https://www.transfermarkt.us/-/mitarbeiter/verein/{club_id}/plus/1"
def __post_init__(self) -> None:
if not self.club_id:
raise ValueError("The 'club_id' must be provided and cannot be None.")
self.URL = self.URL.format(club_id=self.club_id)
self.page = self.request_url_page()

def __post_init__(self) -> None:
"""Initialize the TransfermarktClubStaffs class."""
self.URL = self.URL.format(club_id=self.club_id)
self.page = self.request_url_page()
self.raise_exception_if_not_found(xpath=Clubs.Staff.CLUB_NAME)

def __parse_club_staffs(self) -> list[dict]:
"""
Parse staff information from the webpage and return a list of dictionaries, each representing a staff member.
Returns:
list[dict]: A list of staff information dictionaries.
"""
staffs_ids = [
safe_regex(ids, REGEX_STAFF_ID, "id") for ids in self.get_list_by_xpath(Clubs.Staff.ID)
]
staffs_names = self.page.xpath(Clubs.Staff.NAME)
staffs_jobs = self.page.xpath(Clubs.Staff.JOB)
staffs_ages = self.page.xpath(Clubs.Staff.AGE)
staffs_nationalities = self.page.xpath(Clubs.Staff.NATIONALITIES)
staffs_appointed = self.page.xpath(Clubs.Staff.APPOINTED)
staffs_contracts = self.page.xpath(Clubs.Staff.CONTRACT)
staffs_last_club = self.page.xpath(Clubs.Staff.LAST_CLUB)

return [
{
"id": idx,
"name": name,
"job": job,
"age": age,
"nationality": nationality,
"appointed": appointed,
"contract": contract,
"lastClub": last_club,
}
for idx, name, job, age, nationality, appointed, contract, last_club in zip( # noqa: E501
staffs_ids,
staffs_names,
staffs_jobs,
staffs_ages,
staffs_nationalities,
staffs_appointed,
staffs_contracts,
staffs_last_club,
)
]
Comment on lines +29 to +68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure extracted lists have equal lengths before zipping.

When zipping the extracted staff data lists, if the lists have unequal lengths, it could lead to data misalignment or runtime errors.

Consider adding a check to verify that all lists are of the same length before proceeding:

def __parse_club_staffs(self) -> list[dict]:
    # ... existing code ...

    list_lengths = [
        len(staffs_ids),
        len(staffs_names),
        len(staffs_jobs),
        len(staffs_ages),
        len(staffs_nationalities),
        len(staffs_appointed),
        len(staffs_contracts),
        len(staffs_last_club),
    ]
    if len(set(list_lengths)) != 1:
        raise ValueError("Extracted staff data lists have inconsistent lengths.")

    # ... rest of the method

This check will ensure data integrity and prevent potential runtime errors.


def get_club_staffs(self) -> dict:
"""
Retrieve and parse staff information for the specified football club.
Returns:
dict: A dictionary containing the club's unique identifier, staff information, and the timestamp of when
the data was last updated.
"""
self.response["id"] = self.club_id
self.response["staffs"] = self.__parse_club_staffs()
self.response["updatedAt"] = datetime.now()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use timezone-aware datetime for updatedAt.

Storing timezone-aware timestamps can prevent potential issues related to timezone discrepancies. It's recommended to use UTC for consistent time representation.

Apply this diff to make updatedAt timezone-aware:

-from datetime import datetime
+from datetime import datetime, timezone

 self.response["updatedAt"] = datetime.now()
+self.response["updatedAt"] = datetime.now(timezone.utc)

Committable suggestion was skipped due to low confidence.


return clean_response(self.response)
Comment on lines +70 to +82
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use timezone-aware datetime for updatedAt.

Using timezone-aware timestamps can prevent potential issues related to timezone discrepancies. It's recommended to use UTC for consistent time representation across different systems.

Apply this diff to make updatedAt timezone-aware:

-from datetime import datetime
+from datetime import datetime, timezone

 def get_club_staffs(self) -> dict:
     # ... existing code ...
-    self.response["updatedAt"] = datetime.now()
+    self.response["updatedAt"] = datetime.now(timezone.utc)
     # ... rest of the method

This change ensures that the timestamp is consistently represented in UTC, regardless of the system's local time settings.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_club_staffs(self) -> dict:
"""
Retrieve and parse staff information for the specified football club.
Returns:
dict: A dictionary containing the club's unique identifier, staff information, and the timestamp of when
the data was last updated.
"""
self.response["id"] = self.club_id
self.response["staffs"] = self.__parse_club_staffs()
self.response["updatedAt"] = datetime.now()
return clean_response(self.response)
from datetime import datetime, timezone
def get_club_staffs(self) -> dict:
"""
Retrieve and parse staff information for the specified football club.
Returns:
dict: A dictionary containing the club's unique identifier, staff information, and the timestamp of when
the data was last updated.
"""
self.response["id"] = self.club_id
self.response["staffs"] = self.__parse_club_staffs()
self.response["updatedAt"] = datetime.now(timezone.utc)
return clean_response(self.response)

3 changes: 2 additions & 1 deletion app/utils/regex.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
REGEX_MEMBERS_DATE: str = r"\(Score: (?P<date>.+)\)"
REGEX_BG_COLOR: str = r"background-color:(?P<color>.+);"
REGEX_CHART_CLUB_ID: str = r"(?P<club_id>\d+)"
REGEX_COUNTRY_ID: str = r"(?P<id>\d)"
REGEX_COUNTRY_ID: str = r"(?P<id>\d+)"
REGEX_DOB_AGE: str = r"^(?P<dob>\w{3} \d{1,2}, \d{4}) \((?P<age>\d{2})\)"
REGEX_STAFF_ID: str = r'/small/(\d+)-'
33 changes: 30 additions & 3 deletions app/utils/xpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ class Achievements:


class Clubs:
class Staff:
CLUB_NAME = "//header//h1//text()"
ID = "//div[@class='large-12 columns']//table[@class='inline-table']//td[@rowspan='2']/img/@src"
NAME = "//div[@class='large-12 columns']//table[@class='inline-table']//td[@class='hauptlink']/a/text()"
JOB = "//div[@class='large-12 columns']//table[@class='inline-table']//td[@class='hauptlink']/following-sibling::tr[1]/td/text()"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Line exceeds maximum allowed length (137 > 120 characters).

Line 99 is too long, exceeding the project's maximum line length of 120 characters. This can affect code readability and maintainability.

Consider splitting the XPath expression into multiple lines for better readability:

- JOB = "//div[@class='large-12 columns']//table[@class='inline-table']//td[@class='hauptlink']/following-sibling::tr[1]/td/text()"
+ JOB = (
+     "//div[@class='large-12 columns']//table[@class='inline-table']"
+     "//td[@class='hauptlink']/following-sibling::tr[1]/td/text()"
+ )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
JOB = "//div[@class='large-12 columns']//table[@class='inline-table']//td[@class='hauptlink']/following-sibling::tr[1]/td/text()"
JOB = (
"//div[@class='large-12 columns']//table[@class='inline-table']"
"//td[@class='hauptlink']/following-sibling::tr[1]/td/text()"
)
🧰 Tools
🪛 Ruff

99-99: Line too long (137 > 120)

(E501)

AGE = "//div[@class='large-12 columns']//td[@class='zentriert'][2]/text()"
NATIONALITIES = "//div[@class='large-12 columns']//td[@class='zentriert'][3]/img/@title"
APPOINTED = "//div[@class='large-12 columns']//td[@class='zentriert'][4]/text()"
CONTRACT = "//div[@class='large-12 columns']//td[@class='zentriert'][5]/text()"
LAST_CLUB = "//div[@class='large-12 columns']//td[@class='zentriert'][6]/a/@title"

Comment on lines +95 to +105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider simplifying complex XPath expressions in the Staff class.

The XPath expressions in lines 95-105 use deeply nested paths, which may be fragile if the HTML structure changes. Simplifying these expressions or using more specific selectors can improve maintainability.

🧰 Tools
🪛 Ruff

99-99: Line too long (137 > 120)

(E501)

class Profile:
URL = "//div[@class='datenfakten-wappen']//@href"
NAME = "//header//h1//text()"
Expand Down Expand Up @@ -124,6 +135,8 @@ class Profile:
LEAGUE_COUNTRY_ID = "//div[@class='data-header__club-info']//img[contains(@class, 'flaggenrahmen')]//@data-src"
LEAGUE_COUNTRY_NAME = "//div[@class='data-header__club-info']//img[contains(@class, 'flaggenrahmen')]//@title"
LEAGUE_TIER = "//div[@class='data-header__club-info']//strong//text()//following::span[1]/a/text()[2]"
LEAGUE_POSITION = "//div[@class='data-header__club-info']//following::span[3]//text()//following::span[1]/a/text()[1]"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Line exceeds maximum allowed length (126 > 120 characters).

Line 138 exceeds the maximum line length of 120 characters, which may hinder code readability.

Consider reformatting the line:

- LEAGUE_POSITION = "//div[@class='data-header__club-info']//following::span[3]//text()//following::span[1]/a/text()[1]"
+ LEAGUE_POSITION = (
+     "//div[@class='data-header__club-info']//following::span[3]//text()"
+     "//following::span[1]/a/text()[1]"
+ )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LEAGUE_POSITION = "//div[@class='data-header__club-info']//following::span[3]//text()//following::span[1]/a/text()[1]"
LEAGUE_POSITION = (
"//div[@class='data-header__club-info']//following::span[3]//text()"
"//following::span[1]/a/text()[1]"
)
🧰 Tools
🪛 Ruff

138-138: Line too long (126 > 120)

(E501)

IN_LEAGUE_SINCE = "//div[@class='data-header__club-info']//following::span[4]//text()//following::span[1]/a/text()[1]"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Line exceeds maximum allowed length (126 > 120 characters).

Line 139 is longer than the allowed 120 characters, affecting readability.

Refactor the line for better readability:

- IN_LEAGUE_SINCE = "//div[@class='data-header__club-info']//following::span[4]//text()//following::span[1]/a/text()[1]"
+ IN_LEAGUE_SINCE = (
+     "//div[@class='data-header__club-info']//following::span[4]//text()"
+     "//following::span[1]/a/text()[1]"
+ )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
IN_LEAGUE_SINCE = "//div[@class='data-header__club-info']//following::span[4]//text()//following::span[1]/a/text()[1]"
IN_LEAGUE_SINCE = (
"//div[@class='data-header__club-info']//following::span[4]//text()"
"//following::span[1]/a/text()[1]"
)
🧰 Tools
🪛 Ruff

139-139: Line too long (126 > 120)

(E501)

CRESTS_HISTORICAL = "//div[@class='wappen-datenfakten-wappen']//@src"

class Search:
Expand All @@ -140,9 +153,9 @@ class Players:
CLUB_URL = "//li[@id='overview']//@href"
PAGE_NATIONALITIES = "//td[img[@class='flaggenrahmen']]"
PAGE_INFOS = "//td[@class='posrela']"
NAMES = "//td[@class='posrela']//a//text()"
NAMES = "//table[@class='inline-table']//a//text()"
URLS = "//td[@class='hauptlink']//@href"
POSITIONS = "//td[@class='posrela']//tr[2]//text()"
POSITIONS = "//table[@class='inline-table']//tr[2]//text()"
DOB_AGE = "//div[@id='yw1']//td[3]//text()"
NATIONALITIES = ".//img//@title"
JOINED = ".//span/node()/@title"
Expand All @@ -168,6 +181,20 @@ class Past:

class Competitions:
class Profile:
LEAGUE_COUNTRY_NAME = "//div[@class='data-header__club-info']//following::span[1]/a/text()[1]"
LEAGUE_COUNTRY_ID = "//div[@class='data-header__club-info']//following::span[1]/a//@href"
LEAGUE_TIER = "//div[@class='data-header__club-info']//following::span[2]//span[2]/text()"
REIGNING_CHAMPION = "//div[@class='data-header__club-info']//following::span[3]//text()//following::span[1]/a/text()[1]"
RECORD_HOLDING_CHAMPION = "//div[@class='data-header__club-info']//following::span[4]//text()//following::a/text()[1]"
UEFA_COEFFICIENT_POSITION = "//div[@class='data-header__club-info']//following::span[5]//text()//following::a/text()[1]"
UEFA_COEFFICIENT_POINT = "//div[@class='data-header__club-info']//following::span[5]//text()//following::span[1]//following::span[1]/text()[1]"
Comment on lines +187 to +190
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Lines exceed maximum allowed length in Competitions.Profile class.

Lines 187 to 190 exceed the maximum line length of 120 characters:

  • Line 187: 128 characters
  • Line 188: 126 characters
  • Line 189: 128 characters
  • Line 190: 151 characters

Please split these XPath expressions into multiple lines. For example, line 190 can be refactored as:

- UEFA_COEFFICIENT_POINT = "//div[@class='data-header__club-info']//following::span[5]//text()//following::span[1]//following::span[1]/text()[1]"
+ UEFA_COEFFICIENT_POINT = (
+     "//div[@class='data-header__club-info']//following::span[5]//text()"
+     "//following::span[1]//following::span[1]/text()[1]"
+ )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
REIGNING_CHAMPION = "//div[@class='data-header__club-info']//following::span[3]//text()//following::span[1]/a/text()[1]"
RECORD_HOLDING_CHAMPION = "//div[@class='data-header__club-info']//following::span[4]//text()//following::a/text()[1]"
UEFA_COEFFICIENT_POSITION = "//div[@class='data-header__club-info']//following::span[5]//text()//following::a/text()[1]"
UEFA_COEFFICIENT_POINT = "//div[@class='data-header__club-info']//following::span[5]//text()//following::span[1]//following::span[1]/text()[1]"
REIGNING_CHAMPION = (
"//div[@class='data-header__club-info']//following::span[3]//text()"
"//following::span[1]/a/text()[1]"
)
RECORD_HOLDING_CHAMPION = (
"//div[@class='data-header__club-info']//following::span[4]//text()"
"//following::a/text()[1]"
)
UEFA_COEFFICIENT_POSITION = (
"//div[@class='data-header__club-info']//following::span[5]//text()"
"//following::a/text()[1]"
)
UEFA_COEFFICIENT_POINT = (
"//div[@class='data-header__club-info']//following::span[5]//text()"
"//following::span[1]//following::span[1]/text()[1]"
)
🧰 Tools
🪛 Ruff

187-187: Line too long (128 > 120)

(E501)


188-188: Line too long (126 > 120)

(E501)


189-189: Line too long (128 > 120)

(E501)


190-190: Line too long (151 > 120)

(E501)

NUMBER_OF_TEAMS = "//div[@class='data-header__info-box']//div[@class='data-header__details']//li[1]/span/text()"
NUMBER_OF_PLAYERS = "//div[@class='data-header__info-box']//div[@class='data-header__details']//li[2]/span/text()"
NUMBER_OF_FOREIGNERS = "//div[@class='data-header__info-box']//div[@class='data-header__details']//li[3]/span/a/text()"
Comment on lines +192 to +193
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Lines exceed maximum allowed length in Competitions.Profile class.

  • Line 192: 122 characters
  • Line 193: 127 characters

Consider breaking these lines to enhance readability. For instance, line 193 can be refactored:

- NUMBER_OF_FOREIGNERS = "//div[@class='data-header__info-box']//div[@class='data-header__details']//li[3]/span/a/text()"
+ NUMBER_OF_FOREIGNERS = (
+     "//div[@class='data-header__info-box']//div[@class='data-header__details']"
+     "//li[3]/span/a/text()"
+ )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NUMBER_OF_PLAYERS = "//div[@class='data-header__info-box']//div[@class='data-header__details']//li[2]/span/text()"
NUMBER_OF_FOREIGNERS = "//div[@class='data-header__info-box']//div[@class='data-header__details']//li[3]/span/a/text()"
NUMBER_OF_PLAYERS = "//div[@class='data-header__info-box']//div[@class='data-header__details']//li[2]/span/text()"
NUMBER_OF_FOREIGNERS = (
"//div[@class='data-header__info-box']//div[@class='data-header__details']"
"//li[3]/span/a/text()"
)
🧰 Tools
🪛 Ruff

192-192: Line too long (122 > 120)

(E501)


193-193: Line too long (127 > 120)

(E501)

TOTAL_MARKET_VALUE = "//div[@class='data-header__box--small']/text()[2]"
TOTAL_MARKET_VALUE_UNIT = "//div[@class='data-header__box--small']//span[@class='waehrung'][2]/text()"
AVERAGE_MARKET_VALUE = "//div[@class='data-header__info-box']//div[@class='data-header__details']//ul[2]//li[1]/span/text()"
AVERAGE_AGE = "//div[@class='data-header__info-box']//div[@class='data-header__details']//ul[2]//li[2]/span/text()"
Comment on lines +196 to +197
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Lines exceed maximum allowed length in Competitions.Profile class.

  • Line 196: 132 characters
  • Line 197: 123 characters

Please refactor these lines for better readability. Example for line 196:

- AVERAGE_MARKET_VALUE = "//div[@class='data-header__info-box']//div[@class='data-header__details']//ul[2]//li[1]/span/text()"
+ AVERAGE_MARKET_VALUE = (
+     "//div[@class='data-header__info-box']//div[@class='data-header__details']"
+     "//ul[2]//li[1]/span/text()"
+ )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AVERAGE_MARKET_VALUE = "//div[@class='data-header__info-box']//div[@class='data-header__details']//ul[2]//li[1]/span/text()"
AVERAGE_AGE = "//div[@class='data-header__info-box']//div[@class='data-header__details']//ul[2]//li[2]/span/text()"
AVERAGE_MARKET_VALUE = (
"//div[@class='data-header__info-box']//div[@class='data-header__details']"
"//ul[2]//li[1]/span/text()"
)
AVERAGE_AGE = (
"//div[@class='data-header__info-box']//div[@class='data-header__details']"
"//ul[2]//li[2]/span/text()"
)
🧰 Tools
🪛 Ruff

196-196: Line too long (132 > 120)

(E501)


197-197: Line too long (123 > 120)

(E501)

URL = "//a[@class='tm-tab']//@href"
NAME = "//div[@class='data-header__headline-container']//h1//text()"

Expand All @@ -189,4 +216,4 @@ class Clubs:

class Pagination:
PAGE_NUMBER_LAST = "//li[contains(@class, 'list-item--icon-last-page')]//@href"
PAGE_NUMBER_ACTIVE = "//li[contains(@class, 'list-item--active')]//@href"
PAGE_NUMBER_ACTIVE = "//li[contains(@class, 'list-item--active')]//@href"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add a newline at the end of the file.

Line 219: The file is missing a trailing newline, which is a common convention to ensure compatibility with various tools and editors.

Please add a newline at the end of the file.

🧰 Tools
🪛 Ruff

219-219: No newline at end of file

Add trailing newline

(W292)