warmpath 0.1.0__tar.gz → 0.2.0__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.
- warmpath-0.2.0/AGENTS.md +7 -0
- {warmpath-0.1.0 → warmpath-0.2.0}/PKG-INFO +1 -1
- {warmpath-0.1.0 → warmpath-0.2.0}/README.md +21 -0
- warmpath-0.2.0/Taskfile.yaml +37 -0
- {warmpath-0.1.0 → warmpath-0.2.0}/pyproject.toml +4 -10
- warmpath-0.2.0/scripts/release.sh +86 -0
- warmpath-0.2.0/tests/test_cli_commands.py +219 -0
- {warmpath-0.1.0 → warmpath-0.2.0}/tests/test_mutual_connections.py +0 -50
- {warmpath-0.1.0 → warmpath-0.2.0}/uv.lock +6 -6
- {warmpath-0.1.0 → warmpath-0.2.0}/warmpath/cli.py +121 -179
- warmpath-0.1.0/Taskfile.yaml +0 -20
- warmpath-0.1.0/tests/test_cli_commands.py +0 -725
- {warmpath-0.1.0 → warmpath-0.2.0}/.gitignore +0 -0
- {warmpath-0.1.0 → warmpath-0.2.0}/UNLICENSE +0 -0
- {warmpath-0.1.0 → warmpath-0.2.0}/docs/skill.md +0 -0
- {warmpath-0.1.0 → warmpath-0.2.0}/warmpath/__init__.py +0 -0
- {warmpath-0.1.0 → warmpath-0.2.0}/warmpath.jpg +0 -0
warmpath-0.2.0/AGENTS.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Verification
|
|
4
|
+
|
|
5
|
+
- Run `task check` after important code or configuration changes.
|
|
6
|
+
- Report any failures from `task check`, or explain why it could not be run.
|
|
7
|
+
- Do not claim that verification succeeded unless `task check` completed successfully.
|
|
@@ -17,6 +17,27 @@ Find LinkedIn mutuals and warm paths using your logged-in LinkedIn cookies.
|
|
|
17
17
|
|
|
18
18
|
Warmpath needs the `li_at` and `JSESSIONID` cookies. Keep this file private; it lives outside the repository.
|
|
19
19
|
|
|
20
|
+
## Development
|
|
21
|
+
|
|
22
|
+
[go-task](https://taskfile.dev/) is required to run the repository checks. Install it with Homebrew on macOS:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
brew install go-task
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For other platforms, follow the [go-task installation guide](https://taskfile.dev/docs/installation). Verify that the `task` command is available before running any checks:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
task --version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then install the Python development dependencies and run the full check suite:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
uv sync
|
|
38
|
+
task check
|
|
39
|
+
```
|
|
40
|
+
|
|
20
41
|
## Usage
|
|
21
42
|
|
|
22
43
|
### Company
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
version: 3
|
|
2
|
+
|
|
3
|
+
env:
|
|
4
|
+
UV_CACHE_DIR: .uv-cache
|
|
5
|
+
|
|
6
|
+
tasks:
|
|
7
|
+
typecheck:
|
|
8
|
+
run: once
|
|
9
|
+
desc: Type-check Python with Pyright.
|
|
10
|
+
cmd: uv run python -m pyright warmpath
|
|
11
|
+
|
|
12
|
+
test:
|
|
13
|
+
run: once
|
|
14
|
+
desc: Run Python unit and live integration tests.
|
|
15
|
+
cmd: uv run pytest
|
|
16
|
+
|
|
17
|
+
check:
|
|
18
|
+
run: once
|
|
19
|
+
deps:
|
|
20
|
+
- typecheck
|
|
21
|
+
- test
|
|
22
|
+
|
|
23
|
+
build:
|
|
24
|
+
desc: Build source and wheel distributions.
|
|
25
|
+
cmds:
|
|
26
|
+
- rm -rf dist
|
|
27
|
+
- UV_CACHE_DIR="${TMPDIR:-/tmp}/warmpath-uv-cache" uv build --no-sources
|
|
28
|
+
|
|
29
|
+
outdated:
|
|
30
|
+
desc: Print outdated project dependencies.
|
|
31
|
+
cmd: uv tree --outdated
|
|
32
|
+
|
|
33
|
+
release:
|
|
34
|
+
desc: Check, build, tag, publish, and smoke-test a committed version.
|
|
35
|
+
deps:
|
|
36
|
+
- check
|
|
37
|
+
cmd: scripts/release.sh
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "warmpath"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Local LinkedIn visible-connections scraper."
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
|
-
dependencies = [
|
|
7
|
-
"open-linkedin-api>=2.3.1",
|
|
8
|
-
]
|
|
6
|
+
dependencies = ["open-linkedin-api>=2.3.1"]
|
|
9
7
|
|
|
10
8
|
[dependency-groups]
|
|
11
|
-
dev = [
|
|
12
|
-
"pytest>=8.4.1",
|
|
13
|
-
"pytest-xdist>=3.8.0",
|
|
14
|
-
"pyright>=1.1.402",
|
|
15
|
-
]
|
|
9
|
+
dev = ["pytest>=8.4.1", "pytest-xdist>=3.8.0", "pyright>=1.1.411"]
|
|
16
10
|
|
|
17
11
|
[tool.pytest.ini_options]
|
|
18
|
-
addopts = "-n auto"
|
|
12
|
+
addopts = "-n auto --dist loadgroup"
|
|
19
13
|
testpaths = ["tests"]
|
|
20
14
|
|
|
21
15
|
[project.scripts]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
PACKAGE="warmpath"
|
|
5
|
+
TOKEN_FILE="${PYPI_TOKEN_FILE:-.pypi-token}"
|
|
6
|
+
export UV_CACHE_DIR="${RELEASE_UV_CACHE_DIR:-${TMPDIR:-/tmp}/warmpath-uv-cache}"
|
|
7
|
+
|
|
8
|
+
if test "$#" -ne 0; then
|
|
9
|
+
echo "usage: $0" >&2
|
|
10
|
+
exit 2
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
version="$(uv version --short)"
|
|
14
|
+
tag="v${version}"
|
|
15
|
+
|
|
16
|
+
if ! test -f "$TOKEN_FILE"; then
|
|
17
|
+
echo "$TOKEN_FILE is required for publishing." >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
token="$(tr -d '\r\n\t ' < "$TOKEN_FILE")"
|
|
22
|
+
test -n "$token"
|
|
23
|
+
case "$token" in
|
|
24
|
+
pypi-*) ;;
|
|
25
|
+
*) echo "$TOKEN_FILE must contain a PyPI token." >&2; exit 1 ;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
rm -rf dist
|
|
29
|
+
if test -n "$(git status --porcelain --untracked-files=all)"; then
|
|
30
|
+
echo "Working tree is not clean; commit before release." >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
if git show-ref --verify --quiet "refs/tags/$tag"; then
|
|
35
|
+
echo "Tag $tag already exists locally." >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
remote_tag="$(git ls-remote --tags origin "refs/tags/$tag")"
|
|
40
|
+
if test -n "$remote_tag"; then
|
|
41
|
+
echo "Tag $tag already exists on origin." >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
echo "Building artifacts"
|
|
46
|
+
uv build --no-sources
|
|
47
|
+
|
|
48
|
+
sdist="dist/${PACKAGE}-${version}.tar.gz"
|
|
49
|
+
wheel="dist/${PACKAGE}-${version}-py3-none-any.whl"
|
|
50
|
+
|
|
51
|
+
echo "Verifying artifacts"
|
|
52
|
+
test -f "$sdist"
|
|
53
|
+
test -f "$wheel"
|
|
54
|
+
|
|
55
|
+
tar tf "$sdist" | grep -q "^${PACKAGE}-${version}/${PACKAGE}/cli.py$"
|
|
56
|
+
uv run python -m zipfile -l "$wheel" | grep -q "${PACKAGE}/cli.py"
|
|
57
|
+
|
|
58
|
+
if tar tf "$sdist" | grep -q '\.pypi-token'; then
|
|
59
|
+
echo ".pypi-token leaked into sdist." >&2
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if tar tf "$sdist" | grep -q '\.uv-cache'; then
|
|
64
|
+
echo ".uv-cache leaked into sdist." >&2
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if uv run python -m zipfile -l "$wheel" | grep -q '\.pypi-token'; then
|
|
69
|
+
echo ".pypi-token leaked into wheel." >&2
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if uv run python -m zipfile -l "$wheel" | grep -q '\.uv-cache'; then
|
|
74
|
+
echo ".uv-cache leaked into wheel." >&2
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
echo "Tagging ${PACKAGE} ${version}"
|
|
79
|
+
git tag -a "$tag" -m "${PACKAGE} ${version}"
|
|
80
|
+
git push origin "$tag"
|
|
81
|
+
|
|
82
|
+
echo "Publishing ${PACKAGE} ${version}"
|
|
83
|
+
UV_PUBLISH_TOKEN="$token" uv publish
|
|
84
|
+
|
|
85
|
+
echo "Smoke-testing uvx ${PACKAGE}"
|
|
86
|
+
uvx --refresh-package "$PACKAGE" "$PACKAGE" --help
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
from importlib.metadata import version as distribution_version
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from warmpath import cli
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
12
|
+
RUSLAN_URL = "https://www.linkedin.com/in/ruslan-gilemzianov/"
|
|
13
|
+
VIOLETTA_URL = "https://www.linkedin.com/in/violetta-shmatkova-844a0986/"
|
|
14
|
+
TIMUR_URL = "https://www.linkedin.com/in/timur-pokayonkov/"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_cli(*args: str, timeout: int = 30) -> subprocess.CompletedProcess[str]:
|
|
18
|
+
return subprocess.run(
|
|
19
|
+
[
|
|
20
|
+
sys.executable,
|
|
21
|
+
"-m",
|
|
22
|
+
"warmpath.cli",
|
|
23
|
+
*args,
|
|
24
|
+
],
|
|
25
|
+
cwd=ROOT,
|
|
26
|
+
text=True,
|
|
27
|
+
capture_output=True,
|
|
28
|
+
timeout=timeout,
|
|
29
|
+
check=False,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def linkedin_cookie_file() -> Path:
|
|
34
|
+
cookie_file = cli.DEFAULT_COOKIE_FILE
|
|
35
|
+
if not cookie_file.exists() or cookie_file.stat().st_size == 0:
|
|
36
|
+
pytest.skip("LinkedIn cookies are required for live integration tests")
|
|
37
|
+
return cookie_file
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def run_live_cli(
|
|
41
|
+
tmp_path: Path,
|
|
42
|
+
*args: str,
|
|
43
|
+
timeout: int = 180,
|
|
44
|
+
) -> subprocess.CompletedProcess[str]:
|
|
45
|
+
return run_cli(
|
|
46
|
+
*args,
|
|
47
|
+
"--cache-dir",
|
|
48
|
+
str(tmp_path),
|
|
49
|
+
"--cookie-file",
|
|
50
|
+
str(linkedin_cookie_file()),
|
|
51
|
+
"--refresh-cache",
|
|
52
|
+
timeout=timeout,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def require_live_linkedin_result(result: subprocess.CompletedProcess[str]) -> None:
|
|
57
|
+
unavailable_errors = (
|
|
58
|
+
"ConnectionError",
|
|
59
|
+
"JSONDecodeError",
|
|
60
|
+
"NameResolutionError",
|
|
61
|
+
"nodename nor servname provided",
|
|
62
|
+
)
|
|
63
|
+
if result.returncode != 0 and any(
|
|
64
|
+
error in result.stderr for error in unavailable_errors
|
|
65
|
+
):
|
|
66
|
+
pytest.skip("LinkedIn API is unavailable")
|
|
67
|
+
|
|
68
|
+
assert result.returncode == 0, result.stderr
|
|
69
|
+
if "No reachable employees found" in result.stdout:
|
|
70
|
+
pytest.skip("LinkedIn company search returned no live results")
|
|
71
|
+
if "No reachable profiles found" in result.stdout:
|
|
72
|
+
pytest.skip("LinkedIn skill search returned no live results")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_top_level_help_shows_command_shapes() -> None:
|
|
76
|
+
result = run_cli("--help")
|
|
77
|
+
|
|
78
|
+
assert result.returncode == 0
|
|
79
|
+
assert "human PROFILE_URL" in result.stdout
|
|
80
|
+
assert "company COMPANY" in result.stdout
|
|
81
|
+
assert "skill SKILL" in result.stdout
|
|
82
|
+
assert f"human {RUSLAN_URL}" in result.stdout
|
|
83
|
+
assert "company https://www.linkedin.com/company/ozon-tech" in result.stdout
|
|
84
|
+
assert "skill Flutter" in result.stdout
|
|
85
|
+
assert "--version" in result.stdout
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_top_level_version_uses_distribution_metadata() -> None:
|
|
89
|
+
result = run_cli("--version")
|
|
90
|
+
|
|
91
|
+
assert result.returncode == 0
|
|
92
|
+
assert result.stdout == f"warmpath {distribution_version('warmpath')}\n"
|
|
93
|
+
assert result.stderr == ""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_profile_flag_is_not_a_top_level_command() -> None:
|
|
97
|
+
result = run_cli("--profile", "mitchellh")
|
|
98
|
+
|
|
99
|
+
assert result.returncode != 0
|
|
100
|
+
assert "unrecognized arguments" in result.stderr
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_connections_is_not_a_command() -> None:
|
|
104
|
+
result = run_cli("connections", "--help")
|
|
105
|
+
|
|
106
|
+
assert result.returncode != 0
|
|
107
|
+
assert "Unknown command: connections" in result.stderr
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_human_is_the_profile_mutuals_command() -> None:
|
|
111
|
+
result = run_cli("human", "--help")
|
|
112
|
+
|
|
113
|
+
assert result.returncode == 0
|
|
114
|
+
assert "profile URL" in result.stdout
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_company_is_the_company_command() -> None:
|
|
118
|
+
result = run_cli("company", "--help")
|
|
119
|
+
|
|
120
|
+
assert result.returncode == 0
|
|
121
|
+
assert "LinkedIn /company/ URL" in result.stdout
|
|
122
|
+
assert "Default: 5." in result.stdout
|
|
123
|
+
assert "company-path" not in result.stdout
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_skill_is_the_skill_command() -> None:
|
|
127
|
+
result = run_cli("skill", "--help")
|
|
128
|
+
|
|
129
|
+
assert result.returncode == 0
|
|
130
|
+
assert "skill name to search for" in result.stdout
|
|
131
|
+
assert "Default: 2." in result.stdout
|
|
132
|
+
assert "Default: 5." in result.stdout
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_company_default_limit_is_five() -> None:
|
|
136
|
+
args = cli.parse_company_args(["https://www.linkedin.com/company/binance/"])
|
|
137
|
+
|
|
138
|
+
assert args.limit == 5
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_default_paths_use_user_directories() -> None:
|
|
142
|
+
args = cli.parse_company_args(["https://www.linkedin.com/company/binance/"])
|
|
143
|
+
|
|
144
|
+
assert (
|
|
145
|
+
args.cookie_file
|
|
146
|
+
== Path.home() / ".config" / "warmpath" / "linkedin.cookies"
|
|
147
|
+
)
|
|
148
|
+
assert args.cache_dir == Path.home() / ".cache" / "warmpath"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_resolve_path_expands_home() -> None:
|
|
152
|
+
assert cli.resolve_path(Path("~/warmpath-test")) == Path.home() / "warmpath-test"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@pytest.mark.xdist_group(name="linkedin")
|
|
156
|
+
def test_company_yadro_excludes_out_of_network_profile(tmp_path) -> None:
|
|
157
|
+
result = run_live_cli(
|
|
158
|
+
tmp_path,
|
|
159
|
+
"company",
|
|
160
|
+
"yadro",
|
|
161
|
+
"--limit",
|
|
162
|
+
"10",
|
|
163
|
+
"--max-degree",
|
|
164
|
+
"2",
|
|
165
|
+
timeout=120,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
require_live_linkedin_result(result)
|
|
169
|
+
assert "https://www.linkedin.com/in/egorkazachkov/" not in result.stdout
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@pytest.mark.xdist_group(name="linkedin")
|
|
173
|
+
def test_company_avito_large_search_includes_expected_second_degree_profile(
|
|
174
|
+
tmp_path,
|
|
175
|
+
) -> None:
|
|
176
|
+
result = run_live_cli(
|
|
177
|
+
tmp_path,
|
|
178
|
+
"company",
|
|
179
|
+
"Avito",
|
|
180
|
+
"--limit",
|
|
181
|
+
"500",
|
|
182
|
+
timeout=240,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
require_live_linkedin_result(result)
|
|
186
|
+
assert VIOLETTA_URL in result.stdout
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_skill_default_max_depth_is_two() -> None:
|
|
190
|
+
args = cli.parse_skill_args(["Flutter"])
|
|
191
|
+
|
|
192
|
+
assert args.max_depth == 2
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_skill_default_limit_is_five() -> None:
|
|
196
|
+
args = cli.parse_skill_args(["Flutter"])
|
|
197
|
+
|
|
198
|
+
assert args.limit == 5
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_company_path_is_not_a_command() -> None:
|
|
202
|
+
result = run_cli("company-path", "--help")
|
|
203
|
+
|
|
204
|
+
assert result.returncode != 0
|
|
205
|
+
assert "Unknown command: company-path" in result.stderr
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@pytest.mark.xdist_group(name="linkedin")
|
|
209
|
+
def test_skill_leadership_large_search_includes_expected_profile(tmp_path) -> None:
|
|
210
|
+
result = run_live_cli(
|
|
211
|
+
tmp_path,
|
|
212
|
+
"skill",
|
|
213
|
+
"Leadership",
|
|
214
|
+
"--limit",
|
|
215
|
+
"25",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
require_live_linkedin_result(result)
|
|
219
|
+
assert TIMUR_URL in result.stdout
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from warmpath.cli import (
|
|
2
2
|
company_path_candidate,
|
|
3
3
|
connection_result_row,
|
|
4
|
-
fetch_mutual_connection_rows,
|
|
5
4
|
render_company_path_result,
|
|
6
5
|
)
|
|
7
6
|
|
|
@@ -84,55 +83,6 @@ def test_second_degree_candidate_renders_visible_mutual_connections() -> None:
|
|
|
84
83
|
assert "unknown introducer" not in rendered
|
|
85
84
|
|
|
86
85
|
|
|
87
|
-
def test_fetch_mutual_connection_rows_searches_first_degree_connections(
|
|
88
|
-
tmp_path,
|
|
89
|
-
) -> None:
|
|
90
|
-
class FakeApi:
|
|
91
|
-
params = None
|
|
92
|
-
limit = None
|
|
93
|
-
|
|
94
|
-
def search(self, params, limit):
|
|
95
|
-
self.params = params
|
|
96
|
-
self.limit = limit
|
|
97
|
-
return [
|
|
98
|
-
{
|
|
99
|
-
"entityUrn": "urn:li:fsd_profile:ACoAACeBStkBoUxCGfrnXFevZJkQ-UX6eHu4deU",
|
|
100
|
-
"entityCustomTrackingInfo": {"memberDistance": "DISTANCE_1"},
|
|
101
|
-
"title": {"text": "Anastasiia Krivobokova"},
|
|
102
|
-
"primarySubtitle": {"text": "Senior HR Business Partner"},
|
|
103
|
-
"secondarySubtitle": {"text": "Amsterdam"},
|
|
104
|
-
"navigationUrl": "https://www.linkedin.com/in/anastasiaandreewnaa/",
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"entityUrn": "urn:li:fsd_profile:ACoAACJd_TgBcn1VPQemkT4e3qsSPkR9WjEYhy8",
|
|
108
|
-
"entityCustomTrackingInfo": {"memberDistance": "DISTANCE_1"},
|
|
109
|
-
"title": {"text": "Andrey Zhuchkov"},
|
|
110
|
-
"primarySubtitle": {"text": "/"},
|
|
111
|
-
"secondarySubtitle": {"text": "Russia"},
|
|
112
|
-
"navigationUrl": "https://www.linkedin.com/in/a-zhuchkov/",
|
|
113
|
-
},
|
|
114
|
-
]
|
|
115
|
-
|
|
116
|
-
api = FakeApi()
|
|
117
|
-
rows = fetch_mutual_connection_rows(
|
|
118
|
-
api,
|
|
119
|
-
"ACoAABaR-MoBLHrbfL3jmaglgQoONjPGWrfmTSE",
|
|
120
|
-
limit=2,
|
|
121
|
-
cache_dir=tmp_path,
|
|
122
|
-
refresh_cache=True,
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
assert "connectionOf,value:List(ACoAABaR-MoBLHrbfL3jmaglgQoONjPGWrfmTSE)" in api.params[
|
|
126
|
-
"filters"
|
|
127
|
-
]
|
|
128
|
-
assert "network,value:List(F)" in api.params["filters"]
|
|
129
|
-
assert api.limit == 2
|
|
130
|
-
assert [row["name"] for row in rows] == [
|
|
131
|
-
"Anastasiia Krivobokova",
|
|
132
|
-
"Andrey Zhuchkov",
|
|
133
|
-
]
|
|
134
|
-
|
|
135
|
-
|
|
136
86
|
def test_second_degree_candidate_without_mutuals_keeps_unresolved_status() -> None:
|
|
137
87
|
row = {
|
|
138
88
|
"name": "Ruslan Gilemzianov",
|
|
@@ -143,7 +143,7 @@ name = "exceptiongroup"
|
|
|
143
143
|
version = "1.3.1"
|
|
144
144
|
source = { registry = "https://pypi.org/simple" }
|
|
145
145
|
dependencies = [
|
|
146
|
-
{ name = "typing-extensions"
|
|
146
|
+
{ name = "typing-extensions" },
|
|
147
147
|
]
|
|
148
148
|
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
|
149
149
|
wheels = [
|
|
@@ -315,15 +315,15 @@ wheels = [
|
|
|
315
315
|
|
|
316
316
|
[[package]]
|
|
317
317
|
name = "pyright"
|
|
318
|
-
version = "1.1.
|
|
318
|
+
version = "1.1.411"
|
|
319
319
|
source = { registry = "https://pypi.org/simple" }
|
|
320
320
|
dependencies = [
|
|
321
321
|
{ name = "nodeenv" },
|
|
322
322
|
{ name = "typing-extensions" },
|
|
323
323
|
]
|
|
324
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
324
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7e/ab/265f7dc69d28113ebba19092e57b075f41543b2ed048429c5f56e2b88eac/pyright-1.1.411.tar.gz", hash = "sha256:d885a0551f2e763b089a02702174e7f4ba77548cddabc972ab86d1f7f1b0f998", size = 4112861, upload-time = "2026-06-25T02:14:06.37Z" }
|
|
325
325
|
wheels = [
|
|
326
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
326
|
+
{ url = "https://files.pythonhosted.org/packages/0a/49/385be530a6a5b78d1cbcd5c2e38debc8959a2fc6bdb716f4e581002979fc/pyright-1.1.411-py3-none-any.whl", hash = "sha256:dc7c72a8e2700c55baa127554040e067041ea53ccfd50bf96308cc4291c7d5d9", size = 6181526, upload-time = "2026-06-25T02:14:04.691Z" },
|
|
327
327
|
]
|
|
328
328
|
|
|
329
329
|
[[package]]
|
|
@@ -455,7 +455,7 @@ wheels = [
|
|
|
455
455
|
|
|
456
456
|
[[package]]
|
|
457
457
|
name = "warmpath"
|
|
458
|
-
version = "0.
|
|
458
|
+
version = "0.2.0"
|
|
459
459
|
source = { editable = "." }
|
|
460
460
|
dependencies = [
|
|
461
461
|
{ name = "open-linkedin-api" },
|
|
@@ -473,7 +473,7 @@ requires-dist = [{ name = "open-linkedin-api", specifier = ">=2.3.1" }]
|
|
|
473
473
|
|
|
474
474
|
[package.metadata.requires-dev]
|
|
475
475
|
dev = [
|
|
476
|
-
{ name = "pyright", specifier = ">=1.1.
|
|
476
|
+
{ name = "pyright", specifier = ">=1.1.411" },
|
|
477
477
|
{ name = "pytest", specifier = ">=8.4.1" },
|
|
478
478
|
{ name = "pytest-xdist", specifier = ">=3.8.0" },
|
|
479
479
|
]
|