try to fix the setup
This commit is contained in:
parent
b36ffba63a
commit
1162e7046b
11 changed files with 256 additions and 1359 deletions
18
flake.lock
generated
18
flake.lock
generated
|
|
@ -18,7 +18,23 @@
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs",
|
||||||
|
"systems": "systems"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1689347949,
|
||||||
|
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default-linux",
|
||||||
|
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default-linux",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
78
flake.nix
78
flake.nix
|
|
@ -1,12 +1,72 @@
|
||||||
{
|
{
|
||||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
outputs = { self, nixpkgs }: {
|
systems.url = "github:nix-systems/default-linux";
|
||||||
devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {
|
|
||||||
packages = with nixpkgs.legacyPackages.x86_64-linux; [
|
|
||||||
uv
|
|
||||||
python312
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
systems,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
eachSystem = nixpkgs.lib.genAttrs (import systems);
|
||||||
|
pkgsFor = system: nixpkgs.legacyPackages.${system};
|
||||||
|
|
||||||
|
mkPythonEnv =
|
||||||
|
pkgs:
|
||||||
|
pkgs.python312.withPackages (ps: [
|
||||||
|
ps.backoff
|
||||||
|
ps.beautifulsoup4
|
||||||
|
ps.curl-cffi
|
||||||
|
ps.httpx
|
||||||
|
ps.ndjson
|
||||||
|
ps.pydantic
|
||||||
|
ps.requests
|
||||||
|
]);
|
||||||
|
|
||||||
|
mkPlugin =
|
||||||
|
pkgs:
|
||||||
|
let
|
||||||
|
pythonEnv = mkPythonEnv pkgs;
|
||||||
|
in
|
||||||
|
pkgs.vimUtils.buildVimPlugin {
|
||||||
|
pname = "cp-nvim";
|
||||||
|
version = "0-unstable-${self.shortRev or self.dirtyShortRev or "dev"}";
|
||||||
|
src = self;
|
||||||
|
postPatch = ''
|
||||||
|
substituteInPlace lua/cp/utils.lua \
|
||||||
|
--replace-fail "local _nix_python = nil" \
|
||||||
|
"local _nix_python = '${pythonEnv.interpreter}'"
|
||||||
|
'';
|
||||||
|
nvimSkipModule = [
|
||||||
|
"cp.pickers.telescope"
|
||||||
|
"cp.version"
|
||||||
|
];
|
||||||
|
passthru = { inherit pythonEnv; };
|
||||||
|
meta.description = "Competitive programming plugin for Neovim";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
overlays.default = final: prev: {
|
||||||
|
vimPlugins = prev.vimPlugins // {
|
||||||
|
cp-nvim = mkPlugin final;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
packages = eachSystem (system: {
|
||||||
|
default = mkPlugin (pkgsFor system);
|
||||||
|
pythonEnv = mkPythonEnv (pkgsFor system);
|
||||||
|
});
|
||||||
|
|
||||||
|
devShells = eachSystem (system: {
|
||||||
|
default = (pkgsFor system).mkShell {
|
||||||
|
packages = with (pkgsFor system); [
|
||||||
|
uv
|
||||||
|
python312
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ local utils = require('cp.utils')
|
||||||
local function check()
|
local function check()
|
||||||
vim.health.start('cp.nvim [required] ~')
|
vim.health.start('cp.nvim [required] ~')
|
||||||
|
|
||||||
|
utils.setup_python_env()
|
||||||
|
|
||||||
if vim.fn.has('nvim-0.10.0') == 1 then
|
if vim.fn.has('nvim-0.10.0') == 1 then
|
||||||
vim.health.ok('Neovim 0.10.0+ detected')
|
vim.health.ok('Neovim 0.10.0+ detected')
|
||||||
else
|
else
|
||||||
|
|
@ -16,22 +18,31 @@ local function check()
|
||||||
vim.health.error('Windows is not supported')
|
vim.health.error('Windows is not supported')
|
||||||
end
|
end
|
||||||
|
|
||||||
if vim.fn.executable('uv') == 1 then
|
if utils.is_nix_build() then
|
||||||
vim.health.ok('uv executable found')
|
vim.health.ok('Nix-built Python environment detected')
|
||||||
local r = vim.system({ 'uv', '--version' }, { text = true }):wait()
|
local py = utils.get_nix_python()
|
||||||
|
local r = vim.system({ py, '--version' }, { text = true }):wait()
|
||||||
if r.code == 0 then
|
if r.code == 0 then
|
||||||
vim.health.info('uv version: ' .. r.stdout:gsub('\n', ''))
|
vim.health.info('Python: ' .. r.stdout:gsub('\n', ''))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
vim.health.warn('uv not found (install https://docs.astral.sh/uv/ for scraping)')
|
if vim.fn.executable('uv') == 1 then
|
||||||
end
|
vim.health.ok('uv executable found')
|
||||||
|
local r = vim.system({ 'uv', '--version' }, { text = true }):wait()
|
||||||
|
if r.code == 0 then
|
||||||
|
vim.health.info('uv version: ' .. r.stdout:gsub('\n', ''))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
vim.health.warn('uv not found (install https://docs.astral.sh/uv/ for scraping)')
|
||||||
|
end
|
||||||
|
|
||||||
local plugin_path = utils.get_plugin_path()
|
local plugin_path = utils.get_plugin_path()
|
||||||
local venv_dir = plugin_path .. '/.venv'
|
local venv_dir = plugin_path .. '/.venv'
|
||||||
if vim.fn.isdirectory(venv_dir) == 1 then
|
if vim.fn.isdirectory(venv_dir) == 1 then
|
||||||
vim.health.ok('Python virtual environment found at ' .. venv_dir)
|
vim.health.ok('Python virtual environment found at ' .. venv_dir)
|
||||||
else
|
else
|
||||||
vim.health.info('Python virtual environment not set up (created on first scrape)')
|
vim.health.info('Python virtual environment not set up (created on first scrape)')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local time_cap = utils.time_capability()
|
local time_cap = utils.time_capability()
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ end
|
||||||
---@param opts { sync?: boolean, ndjson?: boolean, on_event?: fun(ev: table), on_exit?: fun(result: table) }
|
---@param opts { sync?: boolean, ndjson?: boolean, on_event?: fun(ev: table), on_exit?: fun(result: table) }
|
||||||
local function run_scraper(platform, subcommand, args, opts)
|
local function run_scraper(platform, subcommand, args, opts)
|
||||||
local plugin_path = utils.get_plugin_path()
|
local plugin_path = utils.get_plugin_path()
|
||||||
local cmd = { 'uv', 'run', '--directory', plugin_path, '-m', 'scrapers.' .. platform, subcommand }
|
local cmd = utils.get_python_cmd(platform, plugin_path)
|
||||||
|
vim.list_extend(cmd, { subcommand })
|
||||||
vim.list_extend(cmd, args)
|
vim.list_extend(cmd, args)
|
||||||
|
|
||||||
local env = vim.fn.environ()
|
local env = vim.fn.environ()
|
||||||
|
|
@ -43,7 +44,7 @@ local function run_scraper(platform, subcommand, args, opts)
|
||||||
local handle
|
local handle
|
||||||
handle = uv.spawn(
|
handle = uv.spawn(
|
||||||
cmd[1],
|
cmd[1],
|
||||||
{ args = vim.list_slice(cmd, 2), stdio = { nil, stdout, stderr }, env = env },
|
{ args = vim.list_slice(cmd, 2), stdio = { nil, stdout, stderr }, env = env, cwd = plugin_path },
|
||||||
function(code, signal)
|
function(code, signal)
|
||||||
if buf ~= '' and opts.on_event then
|
if buf ~= '' and opts.on_event then
|
||||||
local ok_tail, ev_tail = pcall(vim.json.decode, buf)
|
local ok_tail, ev_tail = pcall(vim.json.decode, buf)
|
||||||
|
|
@ -102,7 +103,7 @@ local function run_scraper(platform, subcommand, args, opts)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local sysopts = { text = true, timeout = 30000, env = env }
|
local sysopts = { text = true, timeout = 30000, env = env, cwd = plugin_path }
|
||||||
if opts and opts.sync then
|
if opts and opts.sync then
|
||||||
local result = vim.system(cmd, sysopts):wait()
|
local result = vim.system(cmd, sysopts):wait()
|
||||||
return syshandle(result)
|
return syshandle(result)
|
||||||
|
|
|
||||||
|
|
@ -121,13 +121,22 @@ function M.toggle_interactive(interactor_cmd)
|
||||||
end
|
end
|
||||||
local orchestrator =
|
local orchestrator =
|
||||||
vim.fn.fnamemodify(utils.get_plugin_path() .. '/scripts/interact.py', ':p')
|
vim.fn.fnamemodify(utils.get_plugin_path() .. '/scripts/interact.py', ':p')
|
||||||
cmdline = table.concat({
|
if utils.is_nix_build() then
|
||||||
'uv',
|
cmdline = table.concat({
|
||||||
'run',
|
vim.fn.shellescape(utils.get_nix_python()),
|
||||||
vim.fn.shellescape(orchestrator),
|
vim.fn.shellescape(orchestrator),
|
||||||
vim.fn.shellescape(interactor),
|
vim.fn.shellescape(interactor),
|
||||||
vim.fn.shellescape(binary),
|
vim.fn.shellescape(binary),
|
||||||
}, ' ')
|
}, ' ')
|
||||||
|
else
|
||||||
|
cmdline = table.concat({
|
||||||
|
'uv',
|
||||||
|
'run',
|
||||||
|
vim.fn.shellescape(orchestrator),
|
||||||
|
vim.fn.shellescape(interactor),
|
||||||
|
vim.fn.shellescape(binary),
|
||||||
|
}, ' ')
|
||||||
|
end
|
||||||
else
|
else
|
||||||
cmdline = vim.fn.shellescape(binary)
|
cmdline = vim.fn.shellescape(binary)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
129
lua/cp/utils.lua
129
lua/cp/utils.lua
|
|
@ -2,6 +2,8 @@ local M = {}
|
||||||
|
|
||||||
local logger = require('cp.log')
|
local logger = require('cp.log')
|
||||||
|
|
||||||
|
local _nix_python = nil
|
||||||
|
|
||||||
local uname = vim.loop.os_uname()
|
local uname = vim.loop.os_uname()
|
||||||
|
|
||||||
local _time_cached = false
|
local _time_cached = false
|
||||||
|
|
@ -79,43 +81,116 @@ function M.get_plugin_path()
|
||||||
return vim.fn.fnamemodify(plugin_path, ':h:h:h')
|
return vim.fn.fnamemodify(plugin_path, ':h:h:h')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.is_nix_build()
|
||||||
|
return _nix_python ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.get_nix_python()
|
||||||
|
return _nix_python
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.get_python_cmd(module, plugin_path)
|
||||||
|
if _nix_python then
|
||||||
|
return { _nix_python, '-m', 'scrapers.' .. module }
|
||||||
|
end
|
||||||
|
return { 'uv', 'run', '--directory', plugin_path, '-m', 'scrapers.' .. module }
|
||||||
|
end
|
||||||
|
|
||||||
local python_env_setup = false
|
local python_env_setup = false
|
||||||
|
|
||||||
|
local function discover_nix_python()
|
||||||
|
local cache_dir = vim.fn.stdpath('cache') .. '/cp-nvim'
|
||||||
|
local cache_file = cache_dir .. '/nix-python'
|
||||||
|
|
||||||
|
local f = io.open(cache_file, 'r')
|
||||||
|
if f then
|
||||||
|
local cached = f:read('*l')
|
||||||
|
f:close()
|
||||||
|
if cached and vim.fn.executable(cached) == 1 then
|
||||||
|
_nix_python = cached
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local plugin_path = M.get_plugin_path()
|
||||||
|
local result = vim
|
||||||
|
.system(
|
||||||
|
{ 'nix', 'build', plugin_path .. '#pythonEnv', '--no-link', '--print-out-paths' },
|
||||||
|
{ text = true }
|
||||||
|
)
|
||||||
|
:wait()
|
||||||
|
|
||||||
|
if result.code ~= 0 then
|
||||||
|
logger.log('nix build #pythonEnv failed: ' .. (result.stderr or ''), vim.log.levels.WARN)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local store_path = result.stdout:gsub('%s+$', '')
|
||||||
|
local python_path = store_path .. '/bin/python3'
|
||||||
|
|
||||||
|
if vim.fn.executable(python_path) ~= 1 then
|
||||||
|
logger.log('nix python not executable at ' .. python_path, vim.log.levels.WARN)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.fn.mkdir(cache_dir, 'p')
|
||||||
|
f = io.open(cache_file, 'w')
|
||||||
|
if f then
|
||||||
|
f:write(python_path)
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
_nix_python = python_path
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
---@return boolean success
|
---@return boolean success
|
||||||
function M.setup_python_env()
|
function M.setup_python_env()
|
||||||
if python_env_setup then
|
if python_env_setup then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local plugin_path = M.get_plugin_path()
|
if _nix_python then
|
||||||
local venv_dir = plugin_path .. '/.venv'
|
python_env_setup = true
|
||||||
|
return true
|
||||||
if vim.fn.executable('uv') == 0 then
|
|
||||||
logger.log(
|
|
||||||
'uv is not installed. Install it to enable problem scraping: https://docs.astral.sh/uv/',
|
|
||||||
vim.log.levels.WARN
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if vim.fn.isdirectory(venv_dir) == 0 then
|
if vim.fn.executable('uv') == 1 then
|
||||||
logger.log('Setting up Python environment for scrapers...')
|
local plugin_path = M.get_plugin_path()
|
||||||
local env = vim.fn.environ()
|
local venv_dir = plugin_path .. '/.venv'
|
||||||
env.VIRTUAL_ENV = ''
|
|
||||||
env.PYTHONPATH = ''
|
if vim.fn.isdirectory(venv_dir) == 0 then
|
||||||
env.CONDA_PREFIX = ''
|
logger.log('Setting up Python environment for scrapers...')
|
||||||
local result = vim
|
local env = vim.fn.environ()
|
||||||
.system({ 'uv', 'sync' }, { cwd = plugin_path, text = true, env = env })
|
env.VIRTUAL_ENV = ''
|
||||||
:wait()
|
env.PYTHONPATH = ''
|
||||||
if result.code ~= 0 then
|
env.CONDA_PREFIX = ''
|
||||||
logger.log('Failed to setup Python environment: ' .. result.stderr, vim.log.levels.ERROR)
|
local result = vim
|
||||||
return false
|
.system({ 'uv', 'sync' }, { cwd = plugin_path, text = true, env = env })
|
||||||
|
:wait()
|
||||||
|
if result.code ~= 0 then
|
||||||
|
logger.log('Failed to setup Python environment: ' .. result.stderr, vim.log.levels.ERROR)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
logger.log('Python environment setup complete.')
|
||||||
end
|
end
|
||||||
logger.log('Python environment setup complete.')
|
|
||||||
|
python_env_setup = true
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
python_env_setup = true
|
if vim.fn.executable('nix') == 1 then
|
||||||
return true
|
if discover_nix_python() then
|
||||||
|
python_env_setup = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
logger.log(
|
||||||
|
'No Python environment available. Install uv (https://docs.astral.sh/uv/) or use nix.',
|
||||||
|
vim.log.levels.WARN
|
||||||
|
)
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Configure the buffer with good defaults
|
--- Configure the buffer with good defaults
|
||||||
|
|
@ -170,12 +245,8 @@ function M.check_required_runtime()
|
||||||
return false, 'GNU timeout not found: ' .. (timeout.reason or '')
|
return false, 'GNU timeout not found: ' .. (timeout.reason or '')
|
||||||
end
|
end
|
||||||
|
|
||||||
if vim.fn.executable('uv') ~= 1 then
|
|
||||||
return false, 'uv not found (https://docs.astral.sh/uv/)'
|
|
||||||
end
|
|
||||||
|
|
||||||
if not M.setup_python_env() then
|
if not M.setup_python_env() then
|
||||||
return false, 'failed to set up Python virtual environment'
|
return false, 'no Python environment available (install uv or nix)'
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ dependencies = [
|
||||||
"ndjson>=0.3.1",
|
"ndjson>=0.3.1",
|
||||||
"pydantic>=2.11.10",
|
"pydantic>=2.11.10",
|
||||||
"requests>=2.32.5",
|
"requests>=2.32.5",
|
||||||
"scrapling[fetchers]>=0.3.5",
|
|
||||||
"types-requests>=2.32.4.20250913",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from scrapling.fetchers import Fetcher
|
from curl_cffi import requests as curl_requests
|
||||||
|
|
||||||
from .base import BaseScraper
|
from .base import BaseScraper
|
||||||
from .models import (
|
from .models import (
|
||||||
|
|
@ -50,8 +50,9 @@ def _extract_memory_limit(html: str) -> float:
|
||||||
|
|
||||||
|
|
||||||
def _fetch_html_sync(url: str) -> str:
|
def _fetch_html_sync(url: str) -> str:
|
||||||
response = Fetcher.get(url)
|
response = curl_requests.get(url, impersonate="chrome", timeout=TIMEOUT_S)
|
||||||
return str(response.body)
|
response.raise_for_status()
|
||||||
|
return response.text
|
||||||
|
|
||||||
|
|
||||||
class CodeChefScraper(BaseScraper):
|
class CodeChefScraper(BaseScraper):
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import re
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup, Tag
|
from bs4 import BeautifulSoup, Tag
|
||||||
from scrapling.fetchers import Fetcher
|
from curl_cffi import requests as curl_requests
|
||||||
|
|
||||||
from .base import BaseScraper
|
from .base import BaseScraper
|
||||||
from .models import (
|
from .models import (
|
||||||
|
|
@ -19,10 +18,6 @@ from .models import (
|
||||||
TestCase,
|
TestCase,
|
||||||
)
|
)
|
||||||
|
|
||||||
# suppress scrapling logging - https://github.com/D4Vinci/Scrapling/issues/31)
|
|
||||||
logging.getLogger("scrapling").setLevel(logging.CRITICAL)
|
|
||||||
|
|
||||||
|
|
||||||
BASE_URL = "https://codeforces.com"
|
BASE_URL = "https://codeforces.com"
|
||||||
API_CONTEST_LIST_URL = f"{BASE_URL}/api/contest.list"
|
API_CONTEST_LIST_URL = f"{BASE_URL}/api/contest.list"
|
||||||
TIMEOUT_SECONDS = 30
|
TIMEOUT_SECONDS = 30
|
||||||
|
|
@ -140,10 +135,9 @@ def _is_interactive(block: Tag) -> bool:
|
||||||
|
|
||||||
def _fetch_problems_html(contest_id: str) -> str:
|
def _fetch_problems_html(contest_id: str) -> str:
|
||||||
url = f"{BASE_URL}/contest/{contest_id}/problems"
|
url = f"{BASE_URL}/contest/{contest_id}/problems"
|
||||||
page = Fetcher.get(
|
response = curl_requests.get(url, impersonate="chrome", timeout=TIMEOUT_SECONDS)
|
||||||
url,
|
response.raise_for_status()
|
||||||
)
|
return response.text
|
||||||
return page.html_content
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_all_blocks(html: str) -> list[dict[str, Any]]:
|
def _parse_all_blocks(html: str) -> list[dict[str, Any]]:
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from typing import Any
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
from scrapling import fetchers
|
from curl_cffi import requests as curl_requests
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parent.parent
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
FIX = Path(__file__).resolve().parent / "fixtures"
|
FIX = Path(__file__).resolve().parent / "fixtures"
|
||||||
|
|
@ -136,12 +136,15 @@ def run_scraper_offline(fixture_text):
|
||||||
|
|
||||||
case "codeforces":
|
case "codeforces":
|
||||||
|
|
||||||
class MockCodeForcesPage:
|
class MockCurlResponse:
|
||||||
def __init__(self, html: str):
|
def __init__(self, html: str):
|
||||||
self.html_content = html
|
self.text = html
|
||||||
|
|
||||||
def _mock_stealthy_fetch(url: str, **kwargs):
|
def raise_for_status(self):
|
||||||
return MockCodeForcesPage(_router_codeforces(url=url))
|
pass
|
||||||
|
|
||||||
|
def _mock_curl_get(url: str, **kwargs):
|
||||||
|
return MockCurlResponse(_router_codeforces(url=url))
|
||||||
|
|
||||||
def _mock_requests_get(url: str, **kwargs):
|
def _mock_requests_get(url: str, **kwargs):
|
||||||
if "api/contest.list" in url:
|
if "api/contest.list" in url:
|
||||||
|
|
@ -172,7 +175,7 @@ def run_scraper_offline(fixture_text):
|
||||||
raise AssertionError(f"Unexpected requests.get call: {url}")
|
raise AssertionError(f"Unexpected requests.get call: {url}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"Fetcher.get": _mock_stealthy_fetch,
|
"curl_requests.get": _mock_curl_get,
|
||||||
"requests.get": _mock_requests_get,
|
"requests.get": _mock_requests_get,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,21 +215,23 @@ def run_scraper_offline(fixture_text):
|
||||||
return MockResponse(data)
|
return MockResponse(data)
|
||||||
raise AssertionError(f"No fixture for CodeChef url={url!r}")
|
raise AssertionError(f"No fixture for CodeChef url={url!r}")
|
||||||
|
|
||||||
class MockCodeChefPage:
|
class MockCodeChefCurlResponse:
|
||||||
def __init__(self, html: str):
|
def __init__(self, html: str):
|
||||||
self.body = html
|
self.text = html
|
||||||
self.status = 200
|
|
||||||
|
|
||||||
def _mock_stealthy_fetch(url: str, **kwargs):
|
def raise_for_status(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _mock_curl_get(url: str, **kwargs):
|
||||||
if "/problems/" in url:
|
if "/problems/" in url:
|
||||||
problem_id = url.rstrip("/").split("/")[-1]
|
problem_id = url.rstrip("/").split("/")[-1]
|
||||||
html = fixture_text(f"codechef/{problem_id}.html")
|
html = fixture_text(f"codechef/{problem_id}.html")
|
||||||
return MockCodeChefPage(html)
|
return MockCodeChefCurlResponse(html)
|
||||||
raise AssertionError(f"No fixture for CodeChef url={url!r}")
|
raise AssertionError(f"No fixture for CodeChef url={url!r}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"__offline_get_async": __offline_get_async,
|
"__offline_get_async": __offline_get_async,
|
||||||
"Fetcher.get": _mock_stealthy_fetch,
|
"curl_requests.get": _mock_curl_get,
|
||||||
}
|
}
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
|
|
@ -245,7 +250,7 @@ def run_scraper_offline(fixture_text):
|
||||||
offline_fetches = _make_offline_fetches(scraper_name)
|
offline_fetches = _make_offline_fetches(scraper_name)
|
||||||
|
|
||||||
if scraper_name == "codeforces":
|
if scraper_name == "codeforces":
|
||||||
fetchers.Fetcher.get = offline_fetches["Fetcher.get"]
|
curl_requests.get = offline_fetches["curl_requests.get"]
|
||||||
requests.get = offline_fetches["requests.get"]
|
requests.get = offline_fetches["requests.get"]
|
||||||
elif scraper_name == "atcoder":
|
elif scraper_name == "atcoder":
|
||||||
ns._fetch = offline_fetches["_fetch"]
|
ns._fetch = offline_fetches["_fetch"]
|
||||||
|
|
@ -254,7 +259,7 @@ def run_scraper_offline(fixture_text):
|
||||||
httpx.AsyncClient.get = offline_fetches["__offline_fetch_text"]
|
httpx.AsyncClient.get = offline_fetches["__offline_fetch_text"]
|
||||||
elif scraper_name == "codechef":
|
elif scraper_name == "codechef":
|
||||||
httpx.AsyncClient.get = offline_fetches["__offline_get_async"]
|
httpx.AsyncClient.get = offline_fetches["__offline_get_async"]
|
||||||
fetchers.Fetcher.get = offline_fetches["Fetcher.get"]
|
curl_requests.get = offline_fetches["curl_requests.get"]
|
||||||
|
|
||||||
scraper_class = getattr(ns, scraper_classes[scraper_name])
|
scraper_class = getattr(ns, scraper_classes[scraper_name])
|
||||||
scraper = scraper_class()
|
scraper = scraper_class()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue