mirror of
https://github.com/schrodinger/pymol-open-source.git
synced 2026-06-03 19:54:24 +08:00
Fix ci pipeline for python3.9 and python3.13 (#435)
This commit is contained in:
153
.github/workflows/build.yml
vendored
153
.github/workflows/build.yml
vendored
@@ -7,28 +7,30 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install system dependencies
|
||||
run: >
|
||||
sudo apt-get update;
|
||||
sudo apt-get --no-install-recommends install
|
||||
catch2
|
||||
cmake
|
||||
libfreetype6-dev
|
||||
libglew-dev
|
||||
libglm-dev
|
||||
libmsgpack-dev
|
||||
libnetcdf-dev
|
||||
libpng-dev
|
||||
libxml2-dev
|
||||
python-is-python3
|
||||
python3-biopython
|
||||
python3-dev
|
||||
python3-setuptools
|
||||
python3-numpy
|
||||
python3-pip
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get --no-install-recommends install \
|
||||
catch2 \
|
||||
libfreetype6-dev \
|
||||
libglew-dev \
|
||||
libglm-dev \
|
||||
libmsgpack-dev \
|
||||
libnetcdf-dev \
|
||||
libpng-dev \
|
||||
libxml2-dev
|
||||
|
||||
- name: Install collada2gltf
|
||||
run: |
|
||||
@@ -38,7 +40,7 @@ jobs:
|
||||
- name: Get additional sources
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/rcsb/mmtf-cpp.git
|
||||
cp -R mmtf-cpp/include/mmtf* include/
|
||||
cp -R mmtf-cpp/include/* include/
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
@@ -51,87 +53,120 @@ jobs:
|
||||
run: |
|
||||
pymol -ckqy testing/testing.py --run all
|
||||
|
||||
|
||||
build-Windows:
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
|
||||
env:
|
||||
CONDA_ROOT: ${{github.workspace}}\..\tmp\miniforge
|
||||
MINIFORGE_EXEC: ${{github.workspace}}\..\tmp\miniforge.exe
|
||||
CONDA_ENV_NAME: "testing_env"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- name: Download Miniforge
|
||||
shell: cmd
|
||||
run: |-
|
||||
if not exist %CONDA_ROOT% mkdir %CONDA_ROOT%
|
||||
curl -L -o %MINIFORGE_EXEC% https://github.com/conda-forge/miniforge/releases/download/24.11.0-0/Miniforge3-24.11.0-0-Windows-x86_64.exe
|
||||
start /wait %MINIFORGE_EXEC% /S /D=%CONDA_ROOT%
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Miniforge
|
||||
run: |
|
||||
choco install miniforge3
|
||||
|
||||
- name: Add conda to PATH
|
||||
run: |
|
||||
echo "$env:CONDA" | Out-File -Append -FilePath $env:GITHUB_PATH
|
||||
echo "$env:CONDA\Scripts" | Out-File -Append -FilePath $env:GITHUB_PATH
|
||||
|
||||
- name: Set up Miniforge
|
||||
shell: cmd
|
||||
run: |-
|
||||
CALL %CONDA_ROOT%\\Scripts\\activate.bat
|
||||
conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyside6 glew libxml2 catch2=2.13.3 glm libnetcdf collada2gltf biopython msgpack-python pip python-build
|
||||
|
||||
- name: Conda info
|
||||
shell: cmd
|
||||
run: |-
|
||||
CALL %CONDA_ROOT%\\Scripts\\activate.bat
|
||||
conda info
|
||||
run: |
|
||||
conda init powershell
|
||||
conda create --name $env:CONDA_ENV_NAME -c conda-forge -c schrodinger `
|
||||
python=${{ matrix.python-version }} `
|
||||
pip `
|
||||
catch2=2.13.3 `
|
||||
collada2gltf `
|
||||
freetype `
|
||||
glew `
|
||||
glm `
|
||||
libpng `
|
||||
libxml2 `
|
||||
libnetcdf `
|
||||
|
||||
- name: Get additional sources
|
||||
shell: cmd
|
||||
run: |
|
||||
conda activate $env:CONDA_ENV_NAME
|
||||
|
||||
git clone --depth 1 https://github.com/rcsb/mmtf-cpp.git
|
||||
cp -R mmtf-cpp/include/mmtf* %CONDA_ROOT%/Library/include/
|
||||
Copy-Item -Recurse -Path mmtf-cpp/include\* -Destination "$env:CONDA_PREFIX\Library\include"
|
||||
git clone --depth 1 --single-branch --branch cpp_master https://github.com/msgpack/msgpack-c.git
|
||||
cp -R msgpack-c/include/msgpack* %CONDA_ROOT%/Library/include/
|
||||
Copy-Item -Recurse -Path msgpack-c/include\* -Destination "$env:CONDA_PREFIX\Library\include"
|
||||
dir $env:CONDA_PREFIX\Library\include
|
||||
|
||||
- name: Build PyMOL
|
||||
shell: cmd
|
||||
run: |
|
||||
CALL %CONDA_ROOT%\\Scripts\\activate.bat
|
||||
conda activate $env:CONDA_ENV_NAME
|
||||
pip install -v --config-settings testing=True .[dev]
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
CALL %CONDA_ROOT%\\Scripts\\activate.bat
|
||||
pymol -ckqy testing\\testing.py --run all
|
||||
conda activate $env:CONDA_ENV_NAME
|
||||
pymol -ckqy testing\testing.py --run all
|
||||
|
||||
|
||||
build-MacOS:
|
||||
|
||||
runs-on: macos-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
|
||||
env:
|
||||
CONDA_ROOT: "/tmp/miniforge"
|
||||
CONDA_ENV_NAME: "testing_env"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- name: Set up Miniforge and Build
|
||||
run: |-
|
||||
|
||||
- name: Download Miniforge
|
||||
run: |
|
||||
curl -L -o $CONDA_ROOT.sh https://github.com/conda-forge/miniforge/releases/download/24.11.0-0/Miniforge3-MacOSX-x86_64.sh
|
||||
bash $CONDA_ROOT.sh -b -p $CONDA_ROOT
|
||||
export PATH="$CONDA_ROOT/bin:$PATH"
|
||||
conda config --set quiet yes
|
||||
conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyside6 glew libxml2 catch2=2.13.3 glm libnetcdf collada2gltf biopython msgpack-python pip python-build
|
||||
conda info
|
||||
|
||||
- name: Add conda to PATH
|
||||
run: |
|
||||
echo "${CONDA_ROOT}/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Set up Miniforge
|
||||
run: |
|
||||
conda create --name $CONDA_ENV_NAME -c conda-forge -c schrodinger \
|
||||
python=${{ matrix.python-version }} \
|
||||
pip \
|
||||
catch2=2.13.3 \
|
||||
collada2gltf \
|
||||
freetype \
|
||||
glew \
|
||||
glm \
|
||||
libpng \
|
||||
libxml2 \
|
||||
libnetcdf
|
||||
|
||||
- name: Get additional sources
|
||||
run: |
|
||||
source activate $CONDA_ENV_NAME
|
||||
git clone --depth 1 https://github.com/rcsb/mmtf-cpp.git
|
||||
cp -R mmtf-cpp/include/mmtf* ${CONDA_ROOT}/include/
|
||||
cp -R mmtf-cpp/include/* ${CONDA_PREFIX}/include/
|
||||
git clone --depth 1 --single-branch --branch cpp_master https://github.com/msgpack/msgpack-c.git
|
||||
cp -R msgpack-c/include/msgpack* ${CONDA_ROOT}/include/
|
||||
cp -R msgpack-c/include/* ${CONDA_PREFIX}/include/
|
||||
|
||||
- name: Build PyMOL
|
||||
run: |-
|
||||
run: |
|
||||
source activate $CONDA_ENV_NAME
|
||||
export MACOSX_DEPLOYMENT_TARGET=12.0
|
||||
export PATH="$CONDA_ROOT/bin:$PATH"
|
||||
pip install -v --config-settings testing=True '.[dev]'
|
||||
|
||||
- name: Test
|
||||
run: |-
|
||||
export PATH="$CONDA_ROOT/bin:$PATH"
|
||||
run: |
|
||||
source activate $CONDA_ENV_NAME
|
||||
pymol -ckqy testing/testing.py --run all
|
||||
|
||||
|
||||
@@ -9,27 +9,17 @@
|
||||
|
||||
# we make extensive use of Python's build-in in web infrastructure
|
||||
|
||||
import sys
|
||||
|
||||
if True:
|
||||
import http.server as BaseHTTPServer
|
||||
import io as StringIO
|
||||
import urllib.parse as urlparse
|
||||
from urllib.request import urlopen
|
||||
|
||||
import cgi
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import json
|
||||
import io as StringIO
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from urllib import parse
|
||||
from urllib.request import urlopen
|
||||
|
||||
# we also rely upon Python's json infrastructure
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except:
|
||||
import json
|
||||
|
||||
# standard Python dependencies
|
||||
|
||||
import types, os, sys, traceback, threading
|
||||
|
||||
# NOTE: Let's attempt to follow Python PEP 8 for coding style for this
|
||||
# source code file. URL: http://www.python.org/de/peps/pep-0008
|
||||
@@ -42,7 +32,7 @@ import types, os, sys, traceback, threading
|
||||
|
||||
_json_mime_types = [ 'text/json', 'application/json' ]
|
||||
|
||||
class _PymolHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
class _PymolHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
# for now, we're using a single-threaded server
|
||||
|
||||
@@ -62,7 +52,7 @@ class _PymolHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
def log_message(self, format, *args):
|
||||
if self.server.pymol_logging:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.log_message(self,format,
|
||||
BaseHTTPRequestHandler.log_message(self,format,
|
||||
*args)
|
||||
def process_request(self):
|
||||
"""
|
||||
@@ -88,26 +78,22 @@ class _PymolHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
def parse_args(self):
|
||||
"""
|
||||
parses URL arguments into a urlpath (before the ?)
|
||||
and a cgiFieldStorage object (args after the ?).
|
||||
for example:
|
||||
http://localhost:8080/apply/pymol.cmd.color?color=blue&selection=benz
|
||||
would yield self.fs.getvalue("color") as "blue"
|
||||
and self.fs.getvalue("selection") as "benz"
|
||||
Example:
|
||||
http://localhost:8080/apply/pymol.cmd.color?color=blue&selection=benz
|
||||
qsl == "color=blue&selection=benz"
|
||||
parse.parse_qs(qs) == {'color': ['blue'], 'selection': ['benz']}
|
||||
self.urlpath would be "/apply/pymol.cmd.color"
|
||||
"""
|
||||
if (self.command == "POST"):
|
||||
self.fs = cgi.FieldStorage(fp=self.rfile, headers=self.headers,
|
||||
environ = {'REQUEST_METHOD':'POST'},
|
||||
keep_blank_values = 1)
|
||||
self.fs = {}
|
||||
|
||||
if self.command == "POST":
|
||||
self.fs = self.headers
|
||||
self.urlpath = self.path
|
||||
elif (self.command == "GET"):
|
||||
scheme,netloc,path,params,qs,fragment = urlparse.urlparse(self.path)
|
||||
self.fs = cgi.FieldStorage(environ = {'REQUEST_METHOD':'GET',
|
||||
'QUERY_STRING':qs},
|
||||
keep_blank_values = 1)
|
||||
|
||||
elif self.command == "GET":
|
||||
_, _, path, _, qs, _ = parse.urlparse(self.path)
|
||||
self.fs = parse.parse_qs(qs, keep_blank_values=True)
|
||||
self.urlpath = path
|
||||
else:
|
||||
self.fs = None
|
||||
|
||||
def process_urlpath(self):
|
||||
"""
|
||||
@@ -230,26 +216,39 @@ class _PymolHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
query_kwds = {}
|
||||
send_multi_result_list = False
|
||||
|
||||
for k in self.fs.keys():
|
||||
if k[0:1] == '_': # leading-underscore argument (special handling)
|
||||
if k == '_callback':
|
||||
self.callback = self.fs.getfirst(k)
|
||||
elif k == '_json': # main path for Javascript API
|
||||
method = json.loads(self.fs.getfirst(k))
|
||||
for key, value in self.fs.items():
|
||||
first_value = value[0]
|
||||
|
||||
# leading-underscore argument (special handling)
|
||||
if key[0:1] == "_":
|
||||
|
||||
if key == "_callback":
|
||||
self.callback = first_value
|
||||
|
||||
# main path for Javascript API
|
||||
elif key == "_json":
|
||||
# [ "my_method", [ arg1, ... ] , { 'key1' : 'val1, ... } ]
|
||||
# or
|
||||
# [ [ "my_met1", [ arg1, ... ], { 'key1' : 'val1, ... } ],
|
||||
# [ "my_met2", [ arg1, ... ], { 'key1' : 'val1, ... } ] ]
|
||||
elif k == '_method': # tentative, not in spec -- may disappear
|
||||
method = json.loads(first_value)
|
||||
|
||||
# tentative, not in spec -- may disappear
|
||||
elif key == "_method":
|
||||
# a method name "my_method"
|
||||
method = json.loads(self.fs.getfirst(k))
|
||||
elif k == '_args': # tentative, not in spec -- may disappear
|
||||
args = json.loads(self.fs.getfirst(k))
|
||||
elif k == '_kwds': # tentative, not in spec -- may disappear
|
||||
kwds = json.loads(self.fs.getfirst(k))
|
||||
# other underscore arguments are ignored (not passed on)
|
||||
elif k[0:1] != '_':
|
||||
query_kwds[k] = self.fs.getfirst(k)
|
||||
method = json.loads(first_value)
|
||||
|
||||
# tentative, not in spec -- may disappear
|
||||
elif key == "_args":
|
||||
args = json.loads(first_value)
|
||||
|
||||
# tentative, not in spec -- may disappear
|
||||
elif key == "_kwds":
|
||||
kwds = json.loads(first_value)
|
||||
|
||||
# other underscore arguments are ignored (not passed on)
|
||||
elif key[0:1] != "_":
|
||||
query_kwds[key] = value[0]
|
||||
|
||||
blocks = []
|
||||
if isinstance(method, str):
|
||||
@@ -413,32 +412,30 @@ class _PymolHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
for debugging requests
|
||||
"""
|
||||
self.wfile_write("%s\n" % self.command)
|
||||
if (self.fs):
|
||||
for k in self.fs.keys():
|
||||
self.wfile_write("%s = " % k)
|
||||
# key can have multiple values, as with checkboxes,
|
||||
# but also arbitrarily
|
||||
if (isinstance(self.fs[k], list)):
|
||||
self.wfile_write("%s\n" % self.fs.getlist(k))
|
||||
else:
|
||||
# key can be uploaded file
|
||||
if (self.fs[k].filename):
|
||||
self.wfile_write("%s\n" % self.fs[k].filename)
|
||||
fp = self.fs[k].file
|
||||
#self.wfile.write("FILE %s" % cgi.escape(repr(fp)))
|
||||
#self.wfile.write("%s\n" % fp.name)
|
||||
# fails for StringIO instances
|
||||
self.wfile_write("%s\n" % repr(fp))
|
||||
# two ways to get file contents
|
||||
#file_contents = self.fs.getvalue(k)
|
||||
#file_contents = fp.read()
|
||||
#self.wfile.write("%s" % file_contents)
|
||||
else:
|
||||
#plain-old key/value
|
||||
self.wfile_write("%s\n" % self.fs.getvalue(k))
|
||||
else:
|
||||
if not self.fs:
|
||||
self.wfile_write("No args\n")
|
||||
|
||||
for key, value in self.fs.items():
|
||||
self.wfile_write("%s = " % key)
|
||||
|
||||
# key can have multiple values, as with checkboxes,
|
||||
# but also arbitrarily
|
||||
if (isinstance(value, list)):
|
||||
self.wfile_write(f"{value}\n")
|
||||
continue
|
||||
|
||||
# key can be uploaded file
|
||||
if value.filename:
|
||||
self.wfile_write(f"{self.fs[key].filename}\n")
|
||||
fp = value.file
|
||||
self.wfile_write(f"{repr(fp)}\n")
|
||||
# two ways to get file contents
|
||||
#file_contents = self.fs.getvalue(k)
|
||||
#file_contents = fp.read()
|
||||
#self.wfile.write("%s" % file_contents)
|
||||
else:
|
||||
self.wfile_write(f"{value}\n")
|
||||
|
||||
# this is the public class we're exposing to PyMOL consortium members
|
||||
|
||||
class PymolHttpd:
|
||||
@@ -475,7 +472,7 @@ class PymolHttpd:
|
||||
|
||||
session['pymol.cmd.label'] = self_cmd.label2 # no-eval version
|
||||
|
||||
self.server = BaseHTTPServer.HTTPServer(('', self.port),
|
||||
self.server = HTTPServer(('', self.port),
|
||||
_PymolHTTPRequestHandler)
|
||||
self.server.wrap_natives = wrap_natives
|
||||
self.server.custom_headers = headers
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# -*
|
||||
# Z* -------------------------------------------------------------------
|
||||
|
||||
from typing import Iterable, Optional
|
||||
from typing import Iterable, Optional, Union
|
||||
from collections import defaultdict
|
||||
from pymol import parsing
|
||||
|
||||
@@ -30,7 +30,7 @@ class Shortcut:
|
||||
if filter_leading_underscore
|
||||
else keywords
|
||||
)
|
||||
self.shortcut: dict[str, str | int] = {}
|
||||
self.shortcut: dict[str, Union[str, int]] = {}
|
||||
self.abbreviation_dict = defaultdict(list)
|
||||
|
||||
for keyword in self.keywords:
|
||||
@@ -41,7 +41,7 @@ class Shortcut:
|
||||
def __contains__(self, keyword: str) -> bool:
|
||||
return keyword in self.shortcut
|
||||
|
||||
def __getitem__(self, keyword: str) -> Optional[int | str]:
|
||||
def __getitem__(self, keyword: str) -> Optional[Union[int, str]]:
|
||||
return self.shortcut.get(keyword)
|
||||
|
||||
def __delitem__(self, keyword: str) -> None:
|
||||
@@ -128,7 +128,7 @@ class Shortcut:
|
||||
|
||||
def interpret(
|
||||
self, keyword: str, mode: bool = False
|
||||
) -> Optional[int | str | list[str]]:
|
||||
) -> Optional[Union[int, str, list[str]]]:
|
||||
"""
|
||||
Returns None (no hit), str (one hit) or list (multiple hits)
|
||||
|
||||
@@ -172,7 +172,7 @@ class Shortcut:
|
||||
|
||||
def auto_err(
|
||||
self, keyword: str, descrip: Optional[str] = None
|
||||
) -> Optional[int | str | list[str]]:
|
||||
) -> Optional[Union[int, str, list[str]]]:
|
||||
"""
|
||||
Automatically raises an error if a keyword is unknown or ambiguous.
|
||||
|
||||
|
||||
@@ -21,14 +21,19 @@ dependencies = [
|
||||
build-backend = "backend"
|
||||
backend-path = ["_custom_build"]
|
||||
requires = [
|
||||
"cmake>=3.13.3",
|
||||
"numpy>=2.0",
|
||||
"setuptools>=69.2.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pillow==10.3.0",
|
||||
"biopython>=1.80",
|
||||
"msgpack==1.0.8",
|
||||
"pillow==11.1.0",
|
||||
"PySide6==6.8.1",
|
||||
"pytest==8.2.2",
|
||||
"requests==2.32.3",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import os
|
||||
import sys
|
||||
import pymol
|
||||
from pymol import cmd, testing, stored
|
||||
import socket
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
pdbstr = '''ATOM 1 N GLY 22 -1.195 0.201 -0.206 1.00 0.00 N
|
||||
ATOM 2 CA GLY 22 0.230 0.318 -0.502 1.00 0.00 C
|
||||
ATOM 3 C GLY 22 1.059 -0.390 0.542 1.00 0.00 C
|
||||
ATOM 4 O GLY 22 0.545 -0.975 1.499 1.00 0.00 O
|
||||
ATOM 5 H GLY 22 -1.558 -0.333 0.660 1.00 0.00 H
|
||||
ATOM 6 3HA GLY 22 0.482 1.337 -0.514 0.00 0.00 H
|
||||
ATOM 7 HA GLY 22 0.434 -0.159 -1.479 1.00 0.00 H
|
||||
import requests
|
||||
|
||||
import pymol
|
||||
from pymol import cmd, testing
|
||||
|
||||
|
||||
pdbstr = '''ATOM 1 N GLY 22 -1.195 0.201 -0.206 1.00 0.00 N
|
||||
ATOM 2 CA GLY 22 0.230 0.318 -0.502 1.00 0.00 C
|
||||
ATOM 3 C GLY 22 1.059 -0.390 0.542 1.00 0.00 C
|
||||
ATOM 4 O GLY 22 0.545 -0.975 1.499 1.00 0.00 O
|
||||
ATOM 5 H GLY 22 -1.558 -0.333 0.660 1.00 0.00 H
|
||||
ATOM 6 3HA GLY 22 0.482 1.337 -0.514 0.00 0.00 H
|
||||
ATOM 7 HA GLY 22 0.434 -0.159 -1.479 1.00 0.00 H
|
||||
END
|
||||
'''
|
||||
|
||||
@@ -45,7 +52,6 @@ mmodstr = ''' 7 gly
|
||||
|
||||
|
||||
def _get_free_port() -> int:
|
||||
import socket
|
||||
with socket.socket() as s:
|
||||
s.bind(("", 0))
|
||||
return s.getsockname()[1]
|
||||
@@ -608,43 +614,39 @@ class TestImporting(testing.PyMOLTestCase):
|
||||
'network'
|
||||
) # doesn't use external network, but is very slow while being connected to VPN
|
||||
def testLoadPWG(self):
|
||||
if sys.version_info[0] < 3:
|
||||
import urllib2
|
||||
else:
|
||||
import urllib.request as urllib2
|
||||
|
||||
content_index = b'Hello World\n'
|
||||
content_index = b"Hello World\n"
|
||||
port = _get_free_port()
|
||||
baseurl = 'http://localhost:%d' % port
|
||||
base_url = f"http://localhost:{port}"
|
||||
|
||||
def urlread(url):
|
||||
handle = urllib2.urlopen(url, timeout=3.0)
|
||||
try:
|
||||
return handle.info(), handle.read()
|
||||
finally:
|
||||
handle.close()
|
||||
with TemporaryDirectory() as root_dir:
|
||||
root_dir = Path(root_dir)
|
||||
index_file = root_dir / "index.html"
|
||||
start_pwg_file = root_dir / "start.pwg"
|
||||
|
||||
with testing.mkdtemp() as rootdir:
|
||||
filename = os.path.join(rootdir, 'index.html')
|
||||
with open(filename, 'wb') as handle:
|
||||
handle.write(content_index)
|
||||
index_file.write_bytes(content_index)
|
||||
|
||||
filename = os.path.join(rootdir, 'start.pwg')
|
||||
with open(filename, 'w') as handle:
|
||||
handle.write('root %s\n' % rootdir)
|
||||
handle.write('port %d\n' % port)
|
||||
handle.write('header add Test-Key Test-Value\n') # new in 2.4
|
||||
cmd.load(filename)
|
||||
pwg_content = (
|
||||
f"root {root_dir}\n"
|
||||
f"port {port}\n"
|
||||
"header add Test-Key Test-Value\n"
|
||||
)
|
||||
|
||||
header, content = urlread(baseurl)
|
||||
self.assertEqual(header['Test-Key'], 'Test-Value')
|
||||
self.assertEqual(content, content_index)
|
||||
start_pwg_file.write_text(pwg_content)
|
||||
cmd.load(str(start_pwg_file))
|
||||
|
||||
response = requests.get(url=base_url, timeout=5)
|
||||
response.raise_for_status()
|
||||
|
||||
self.assertEqual(response.headers["Test-Key"], "Test-Value")
|
||||
self.assertEqual(response.content, content_index)
|
||||
|
||||
# Warning: can't call locking functions here, will dead-lock
|
||||
# get_version is non-locking since 1.7.4
|
||||
|
||||
header, content = urlread(baseurl + '/apply/pymol.cmd.get_version')
|
||||
content = content.decode('ascii', errors='ignore')
|
||||
response = requests.get(f"{base_url}/apply/pymol.cmd.get_version", timeout=5)
|
||||
response.raise_for_status()
|
||||
|
||||
content = response.content.decode("ascii", errors="ignore")
|
||||
self.assertTrue(content, cmd.get_version()[0] in content)
|
||||
|
||||
@testing.requires_version('2.1')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
|
||||
from pymol.shortcut import Shortcut
|
||||
@@ -48,7 +50,7 @@ def test_all_keywords(sc: Shortcut):
|
||||
],
|
||||
)
|
||||
def test_full_prefix_hits(
|
||||
sc: Shortcut, prefixs: list[str], expected_result: str | list[str]
|
||||
sc: Shortcut, prefixs: list[str], expected_result: Union[str, list[str]]
|
||||
):
|
||||
for prefix in prefixs:
|
||||
result = sc.interpret(prefix)
|
||||
|
||||
Reference in New Issue
Block a user