Archived
feat: add Tyomarkkinatori job listing parser and API integration
This commit is contained in:
@@ -1 +1,4 @@
|
|||||||
from .duunitori import parse_duunitori as duunitori
|
from .duunitori import parse_duunitori as duunitori
|
||||||
|
from .tyomarkkinatori import parse_tyomarkkinatori as tyomarkkinatori
|
||||||
|
|
||||||
|
__all__ = ["duunitori", "tyomarkkinatori"]
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
from httpx import AsyncClient
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from lib import logger
|
||||||
|
from lib.exceptions import FetchException
|
||||||
|
from models.job_listing import JobListing
|
||||||
|
|
||||||
|
|
||||||
|
class _TyomarkkinatoriPaging(BaseModel):
|
||||||
|
pageSize: int = 90
|
||||||
|
pageNumber: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class _TyomarkkinatoriFilters(BaseModel):
|
||||||
|
publishedAfter: str | None = None
|
||||||
|
closesBefore: str | None = None
|
||||||
|
query: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class _TyomarkkinatoriParams(BaseModel):
|
||||||
|
"""Model for storing parameters for Tyomarkkinatori API."""
|
||||||
|
|
||||||
|
query: str
|
||||||
|
paging: _TyomarkkinatoriPaging = _TyomarkkinatoriPaging()
|
||||||
|
filters: _TyomarkkinatoriFilters = _TyomarkkinatoriFilters()
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.query = self.query.strip()
|
||||||
|
self.filters.query = self.query.strip()
|
||||||
|
|
||||||
|
|
||||||
|
async def _parse_item(item: dict) -> JobListing:
|
||||||
|
"""Parse a single job listing from the search results page."""
|
||||||
|
logger.debug(f"Parsing job listing {item['id']}", extra={"item": item})
|
||||||
|
return JobListing(
|
||||||
|
url=f"https://tyomarkkinatori.fi/tyopaikat/{item['id']}",
|
||||||
|
company=item["employer"]["businessName"]["fi"]
|
||||||
|
if "fi" in item["employer"]["businessName"]
|
||||||
|
else item["employer"]["name"],
|
||||||
|
title=(
|
||||||
|
item["title"].get("fi")
|
||||||
|
or item["title"].get("sv")
|
||||||
|
or item["title"].get("en")
|
||||||
|
or ""
|
||||||
|
).strip(),
|
||||||
|
description=(
|
||||||
|
(
|
||||||
|
item["lead"].get("fi")
|
||||||
|
or item["lead"].get("sv")
|
||||||
|
or item["lead"].get("en")
|
||||||
|
or ""
|
||||||
|
).strip()
|
||||||
|
or item["lead"].get("sv")
|
||||||
|
or item["lead"].get("en")
|
||||||
|
or ""
|
||||||
|
).strip(),
|
||||||
|
apply_url="",
|
||||||
|
business_id=item["employer"]["businessId"],
|
||||||
|
due_date=item["applicationPeriodEndDate"],
|
||||||
|
posted_at=item["publishDate"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def parse_tyomarkkinatori(search_term: str) -> list[JobListing]:
|
||||||
|
"""Parse job listings from Tyomarkkinatori based on a search term."""
|
||||||
|
base_url = "https://tyomarkkinatori.fi/api/jobpostingfulltext/search/v1/search"
|
||||||
|
all_job_listings = []
|
||||||
|
|
||||||
|
async with AsyncClient() as client:
|
||||||
|
params = _TyomarkkinatoriParams(query=search_term)
|
||||||
|
response = await client.post(base_url, json=params.model_dump(mode="json"))
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(
|
||||||
|
f"Failed to fetch job listings from {base_url}",
|
||||||
|
extra={"response": response},
|
||||||
|
)
|
||||||
|
raise FetchException(base_url, response.status_code)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
total_pages = data["totalPages"]
|
||||||
|
logger.debug(f"Found {total_pages} pages of search results")
|
||||||
|
|
||||||
|
# Parse 1st page
|
||||||
|
for item in data["content"]:
|
||||||
|
job_listing = await _parse_item(item)
|
||||||
|
all_job_listings.append(job_listing)
|
||||||
|
|
||||||
|
return all_job_listings
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
from datetime import datetime
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
|
||||||
class JobListing(BaseModel):
|
class JobListing(BaseModel):
|
||||||
@@ -10,3 +12,6 @@ class JobListing(BaseModel):
|
|||||||
business_id: str | None = Field(None, description="Business ID")
|
business_id: str | None = Field(None, description="Business ID")
|
||||||
url: str | None = Field(None, description="Job URL")
|
url: str | None = Field(None, description="Job URL")
|
||||||
apply_url: str | None = Field(None, description="Apply URL")
|
apply_url: str | None = Field(None, description="Apply URL")
|
||||||
|
due_date: datetime | None = Field(None, description="Due date")
|
||||||
|
posted_at: datetime | None = Field(None, description="Posted at")
|
||||||
|
fetch_at: datetime = Field(datetime.now(ZoneInfo("UTC")), description="Fetch date")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from lib.exceptions import NotImplementedException
|
from lib.exceptions import NotImplementedException
|
||||||
from lib import logger
|
from lib import logger
|
||||||
from lib.parsers import duunitori
|
from lib.parsers import duunitori, tyomarkkinatori
|
||||||
from models.job_listing import JobListing
|
from models.job_listing import JobListing
|
||||||
|
|
||||||
router = APIRouter(prefix="/job_postings", tags=["Job Postings"])
|
router = APIRouter(prefix="/job_postings", tags=["Job Postings"])
|
||||||
@@ -21,14 +21,16 @@ async def get_job_postings(search_term: str, source: PostingSource = PostingSour
|
|||||||
postings: list[JobListing] = []
|
postings: list[JobListing] = []
|
||||||
match source:
|
match source:
|
||||||
case PostingSource.ALL:
|
case PostingSource.ALL:
|
||||||
logger.warning("Only duunitori is implemented at the moment.")
|
logger.warning(
|
||||||
|
"Only Duunitori and Tyomarkkinatori are implemented at the moment."
|
||||||
|
)
|
||||||
postings = await duunitori(search_term)
|
postings = await duunitori(search_term)
|
||||||
|
postings.extend(await tyomarkkinatori(search_term))
|
||||||
case PostingSource.LINKEDIN:
|
case PostingSource.LINKEDIN:
|
||||||
logger.error(f"Job postings from {source.value} Not implemented yet")
|
logger.error(f"Job postings from {source.value} Not implemented yet")
|
||||||
raise NotImplementedException()
|
raise NotImplementedException()
|
||||||
case PostingSource.DUUNITORI:
|
case PostingSource.DUUNITORI:
|
||||||
postings = await duunitori(search_term)
|
postings = await duunitori(search_term)
|
||||||
case PostingSource.TYOMARKKINATORI:
|
case PostingSource.TYOMARKKINATORI:
|
||||||
logger.error(f"Job postings from {source.value} Not implemented yet")
|
postings = await tyomarkkinatori(search_term)
|
||||||
raise NotImplementedException()
|
|
||||||
return postings
|
return postings
|
||||||
|
|||||||
Reference in New Issue
Block a user