Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions .github/workflows/build-20.04.yml

This file was deleted.

5 changes: 3 additions & 2 deletions .github/workflows/build-24.04.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ name: CI Build
on:
push:
branches: [ "master" ]
tags: '**'
tags:
- '**'
pull_request:
branches: [ "master" ]

Expand All @@ -17,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/[email protected]
Expand Down
4 changes: 2 additions & 2 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ formats:
- htmlzip

build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: "3.11"
python: "3.12"

sphinx:
configuration: docs/conf.py
Expand Down
72 changes: 36 additions & 36 deletions jsonrpclib/SimpleJSONRPCServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
# variant of this package.
SimpleXMLRPCDispatcher = xmlrpcserver.SimpleXMLRPCDispatcher
SimpleXMLRPCRequestHandler = xmlrpcserver.SimpleXMLRPCRequestHandler
CGIXMLRPCRequestHandler = xmlrpcserver.CGIXMLRPCRequestHandler
resolve_dotted_attribute = xmlrpcserver.resolve_dotted_attribute # type: ignore # noqa: E501 # pylint: disable=invalid-name,line-too-long
import socketserver
except (ImportError, AttributeError):
Expand All @@ -55,7 +54,6 @@

SimpleXMLRPCDispatcher = xmlrpcserver.SimpleXMLRPCDispatcher # type: ignore # noqa: E501 # pylint: disable=invalid-name,line-too-long
SimpleXMLRPCRequestHandler = xmlrpcserver.SimpleXMLRPCRequestHandler # type: ignore # noqa: E501 # pylint: disable=invalid-name,line-too-long
CGIXMLRPCRequestHandler = xmlrpcserver.CGIXMLRPCRequestHandler # type: ignore # noqa: E501 # pylint: disable=invalid-name,line-too-long
resolve_dotted_attribute = xmlrpcserver.resolve_dotted_attribute # type: ignore # noqa: E501 # pylint: disable=invalid-name,line-too-long
import SocketServer as socketserver # type: ignore

Expand Down Expand Up @@ -688,41 +686,43 @@ def server_close(self):

# ------------------------------------------------------------------------------

if sys.version_info < (3, 15):
CGIXMLRPCRequestHandler = xmlrpcserver.CGIXMLRPCRequestHandler

class CGIJSONRPCRequestHandler(
SimpleJSONRPCDispatcher, CGIXMLRPCRequestHandler
):
"""
JSON-RPC CGI handler (and dispatcher)
"""

def __init__(self, encoding="UTF-8", config=jsonrpclib.config.DEFAULT):
class CGIJSONRPCRequestHandler(
SimpleJSONRPCDispatcher, CGIXMLRPCRequestHandler
):
"""
Sets up the dispatcher

:param encoding: Dispatcher encoding
:param config: A JSONRPClib Config instance
JSON-RPC CGI handler (and dispatcher)
"""
SimpleJSONRPCDispatcher.__init__(self, encoding, config)
CGIXMLRPCRequestHandler.__init__(self, encoding=encoding)

def handle_jsonrpc(self, request_text):
"""
Handle a JSON-RPC request
"""
try:
writer = sys.stdout.buffer
except AttributeError:
writer = sys.stdout

response = self._marshaled_dispatch(request_text)
response = response.encode(self.encoding)
print("Content-Type:", self.json_config.content_type)
print("Content-Length:", len(response))
print()
sys.stdout.flush()
writer.write(response)
writer.flush()

# XML-RPC alias
handle_xmlrpc = handle_jsonrpc
def __init__(self, encoding="UTF-8", config=jsonrpclib.config.DEFAULT):
"""
Sets up the dispatcher

:param encoding: Dispatcher encoding
:param config: A JSONRPClib Config instance
"""
SimpleJSONRPCDispatcher.__init__(self, encoding, config)
CGIXMLRPCRequestHandler.__init__(self, encoding=encoding)

def handle_jsonrpc(self, request_text):
"""
Handle a JSON-RPC request
"""
try:
writer = sys.stdout.buffer
except AttributeError:
writer = sys.stdout

response = self._marshaled_dispatch(request_text)
response = response.encode(self.encoding)
print("Content-Type:", self.json_config.content_type)
print("Content-Length:", len(response))
print()
sys.stdout.flush()
writer.write(response)
writer.flush()

# XML-RPC alias
handle_xmlrpc = handle_jsonrpc
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
license = "Apache-2.0"
license-files = ["LICENSE"]
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,6 @@
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
],
)
2 changes: 1 addition & 1 deletion tests/cgi-bin/cgi_server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python
"""
Sample CGI server
Sample CGI server. Won't work with Python 3.15 and later
"""

import os
Expand Down
12 changes: 10 additions & 2 deletions tests/test_cgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@
# Standard library
import os
import random
import socket
import threading
import sys
import unittest


if sys.version_info >= (3, 15):
raise unittest.SkipTest("CGI support has been removed in Python 3.15")

try:
from http.server import HTTPServer, CGIHTTPRequestHandler
except ImportError:
Expand All @@ -25,6 +31,8 @@

# ------------------------------------------------------------------------------

HOST = socket.gethostbyname("localhost")


class CGIHandlerTests(unittest.TestCase):
"""
Expand All @@ -40,7 +48,7 @@ def test_server(self):
try:
# Setup server
os.chdir(os.path.dirname(__file__))
server = HTTPServer(("localhost", 0), CGIHTTPRequestHandler)
server = HTTPServer((HOST, 0), CGIHTTPRequestHandler)

# Serve in a thread
thread = threading.Thread(target=server.serve_forever)
Expand All @@ -52,7 +60,7 @@ def test_server(self):

# Make the client
client = ServerProxy(
"http://localhost:{0}/cgi-bin/cgi_server.py".format(port)
"http://{0}:{1}/cgi-bin/cgi_server.py".format(HOST, port)
)

# Check call
Expand Down
47 changes: 26 additions & 21 deletions tests/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# Standard library
import contextlib
import re
import socket
import sys
import unittest
import jsonrpclib
Expand All @@ -29,6 +30,8 @@

# ------------------------------------------------------------------------------

HOST = socket.gethostbyname("localhost")


class HeadersTests(unittest.TestCase):
"""
Expand Down Expand Up @@ -85,18 +88,20 @@ def captured_headers(self, check_duplicates=True):

# Extract headers
raw_headers = request_line.splitlines()[1:-1]
raw_headers = map(lambda h: re.split(r":\s?", h, 1), raw_headers)
raw_headers = map(
lambda h: re.split(r":\s?", h, maxsplit=1), raw_headers
)
for header, value in raw_headers:
header = header.lower()
if check_duplicates and header in headers:
raise KeyError("Header defined twice: {0}".format(header))
headers[header] = value

def test_should_extract_headers(self):
""" Check client headers capture """
"""Check client headers capture"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port), verbose=1
"http://{0}:{1}".format(HOST, self.port), verbose=1
)

# when
Expand All @@ -110,10 +115,10 @@ def test_should_extract_headers(self):
self.assertEqual(headers["content-type"], "application/json-rpc")

def test_should_add_additional_headers(self):
""" Check sending of custom headers """
"""Check sending of custom headers"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port),
"http://{0}:{1}".format(HOST, self.port),
verbose=1,
headers={"X-My-Header": "Test"},
)
Expand All @@ -128,10 +133,10 @@ def test_should_add_additional_headers(self):
self.assertEqual(headers["x-my-header"], "Test")

def test_should_add_additional_headers_to_notifications(self):
""" Check custom headers on notifications """
"""Check custom headers on notifications"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port),
"http://{0}:{1}".format(HOST, self.port),
verbose=1,
headers={"X-My-Header": "Test"},
)
Expand All @@ -145,10 +150,10 @@ def test_should_add_additional_headers_to_notifications(self):
self.assertEqual(headers["x-my-header"], "Test")

def test_should_override_headers(self):
""" Custom headers must override default ones """
"""Custom headers must override default ones"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port),
"http://{0}:{1}".format(HOST, self.port),
verbose=1,
headers={"User-Agent": "jsonrpclib test", "Host": "example.com"},
)
Expand All @@ -163,10 +168,10 @@ def test_should_override_headers(self):
self.assertEqual(headers["host"], "example.com")

def test_should_not_override_content_length(self):
""" Custom headers can't override Content-Length """
"""Custom headers can't override Content-Length"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port),
"http://{0}:{1}".format(HOST, self.port),
verbose=1,
headers={"Content-Length": "invalid value"},
)
Expand All @@ -181,10 +186,10 @@ def test_should_not_override_content_length(self):
self.assertNotEqual(headers["content-length"], "invalid value")

def test_should_convert_header_values_to_basestring(self):
""" Custom headers values should be converted to str """
"""Custom headers values should be converted to str"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port),
"http://{0}:{1}".format(HOST, self.port),
verbose=1,
headers={"X-Test": 123},
)
Expand All @@ -199,10 +204,10 @@ def test_should_convert_header_values_to_basestring(self):
self.assertEqual(headers["x-test"], "123")

def test_should_add_custom_headers_to_methods(self):
""" Check method-based custom headers """
"""Check method-based custom headers"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port), verbose=1
"http://{0}:{1}".format(HOST, self.port), verbose=1
)

# when
Expand All @@ -217,10 +222,10 @@ def test_should_add_custom_headers_to_methods(self):
self.assertEqual(headers["x-method"], "Method")

def test_should_override_global_headers(self):
""" Method-based custom headers override context ones """
"""Method-based custom headers override context ones"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port),
"http://{0}:{1}".format(HOST, self.port),
verbose=1,
headers={"X-Test": "Global"},
)
Expand All @@ -236,10 +241,10 @@ def test_should_override_global_headers(self):
self.assertEqual(headers["x-test"], "Method")

def test_should_restore_global_headers(self):
""" Check custom headers context clean up """
"""Check custom headers context clean up"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port),
"http://{0}:{1}".format(HOST, self.port),
verbose=1,
headers={"X-Test": "Global"},
)
Expand All @@ -262,10 +267,10 @@ def test_should_restore_global_headers(self):
self.assertEqual(headers["x-test"], "Global")

def test_should_allow_to_nest_additional_header_blocks(self):
""" Check nested additional headers """
"""Check nested additional headers"""
# given
client = jsonrpclib.ServerProxy(
"http://localhost:{0}".format(self.port), verbose=1
"http://{0}:{1}".format(HOST, self.port), verbose=1
)

# when
Expand Down
Loading