Colin Watson has proposed merging ~cjwatson/launchpad:remove-simplejson into launchpad:master.
Commit message: Remove simplejson dependency Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/431994 The only things that `simplejson` still gave us compared with the standard library's `json` were more consistently accepting both bytes and text input across all supported Python versions. It isn't really worth a whole dependency just for that. I normalized how `json.dumps` and `json.loads` are spelled; this makes the diff longer (sorry), but it makes it easier to grep for certain patterns. -- Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:remove-simplejson into launchpad:master.
diff --git a/lib/lp/answers/browser/questionsubscription.py b/lib/lp/answers/browser/questionsubscription.py index cc2ea58..990e1a3 100644 --- a/lib/lp/answers/browser/questionsubscription.py +++ b/lib/lp/answers/browser/questionsubscription.py @@ -7,9 +7,10 @@ __all__ = [ "QuestionPortletSubscribersWithDetails", ] +import json + from lazr.delegates import delegate_to from lazr.restful.interfaces import IWebServiceClientRequest -from simplejson import dumps from zope.traversing.browser import absoluteURL from lp.answers.interfaces.question import IQuestion @@ -78,7 +79,7 @@ class QuestionPortletSubscribersWithDetails(LaunchpadView): "subscription_level": "Indirect", } data.append(record) - return dumps(data) + return json.dumps(data) def render(self): """Override the default render() to return only JSON.""" diff --git a/lib/lp/answers/browser/questiontarget.py b/lib/lp/answers/browser/questiontarget.py index bbb6d26..8682cf9 100644 --- a/lib/lp/answers/browser/questiontarget.py +++ b/lib/lp/answers/browser/questiontarget.py @@ -18,11 +18,11 @@ __all__ = [ "UserSupportLanguagesMixin", ] +import json from operator import attrgetter from urllib.parse import urlencode from lazr.restful.interfaces import IJSONRequestCache, IWebServiceClientRequest -from simplejson import dumps from zope.browserpage import ViewPageTemplateFile from zope.component import getMultiAdapter, getUtility, queryMultiAdapter from zope.formlib import form @@ -965,7 +965,7 @@ class QuestionTargetPortletAnswerContactsWithDetails(LaunchpadView): """Return subscriber_ids in a form suitable for JavaScript use.""" questiontarget = IQuestionTarget(self.context) data = self.answercontact_data(questiontarget) - return dumps(data) + return json.dumps(data) def render(self): """Override the default render() to return only JSON.""" diff --git a/lib/lp/answers/model/questionjob.py b/lib/lp/answers/model/questionjob.py index 2f2d794..3a7277c 100644 --- a/lib/lp/answers/model/questionjob.py +++ b/lib/lp/answers/model/questionjob.py @@ -7,7 +7,8 @@ __all__ = [ "QuestionJob", ] -import simplejson +import json + import six from lazr.delegates import delegate_to from storm.expr import And @@ -70,7 +71,7 @@ class QuestionJob(StormBase): self.job = Job() self.job_type = job_type self.question = question - json_data = simplejson.dumps(metadata) + json_data = json.dumps(metadata) self._json_data = six.ensure_text(json_data) def __repr__(self): @@ -82,7 +83,7 @@ class QuestionJob(StormBase): @property def metadata(self): """See `IQuestionJob`.""" - return simplejson.loads(self._json_data) + return json.loads(self._json_data) def makeDerived(self): if self.job_type != QuestionJobType.EMAIL: diff --git a/lib/lp/answers/tests/test_question_webservice.py b/lib/lp/answers/tests/test_question_webservice.py index 2acee73..1d92cc7 100644 --- a/lib/lp/answers/tests/test_question_webservice.py +++ b/lib/lp/answers/tests/test_question_webservice.py @@ -3,10 +3,10 @@ """Webservice unit tests related to Launchpad Questions.""" +import json from datetime import datetime, timedelta import pytz -from simplejson import dumps from testtools.matchers import EndsWith from lp.answers.enums import QuestionStatus @@ -132,7 +132,7 @@ class TestQuestionRepresentation(TestCaseWithFactory): response = self.webservice.patch( question_json["self_link"], "application/json", - dumps(dict(title=new_title)), + json.dumps(dict(title=new_title)), headers=dict(accept="application/xhtml+xml"), ) diff --git a/lib/lp/app/browser/launchpadform.py b/lib/lp/app/browser/launchpadform.py index 01ed055..126eb3f 100644 --- a/lib/lp/app/browser/launchpadform.py +++ b/lib/lp/app/browser/launchpadform.py @@ -14,9 +14,9 @@ __all__ = [ "safe_action", ] +import json from typing import List, Optional, Type -import simplejson import transaction from lazr.lifecycle.event import ObjectModifiedEvent from lazr.lifecycle.snapshot import Snapshot @@ -140,7 +140,7 @@ class LaunchpadFormView(LaunchpadView): ] if notifications: request.response.setHeader( - "X-Lazr-Notifications", simplejson.dumps(notifications) + "X-Lazr-Notifications", json.dumps(notifications) ) def render(self): @@ -387,7 +387,7 @@ class LaunchpadFormView(LaunchpadView): errors=errors, error_summary=self.error_count, ) - return simplejson.dumps(return_data) + return json.dumps(return_data) def validate(self, data): """Validate the form. diff --git a/lib/lp/app/browser/lazrjs.py b/lib/lp/app/browser/lazrjs.py index 23a6bc0..2d3a6f3 100644 --- a/lib/lp/app/browser/lazrjs.py +++ b/lib/lp/app/browser/lazrjs.py @@ -15,7 +15,8 @@ __all__ = [ "vocabulary_to_choice_edit_items", ] -import simplejson +import json + from lazr.enum import IEnumeratedType from lazr.restful.declarations import LAZR_WEBSERVICE_EXPORTED from lazr.restful.utils import get_current_browser_request @@ -81,7 +82,7 @@ class WidgetBase: if mutator_info is not None: mutator_method, mutator_extra = mutator_info self.mutator_method_name = mutator_method.__name__ - self.json_attribute = simplejson.dumps(self.api_attribute) + self.json_attribute = json.dumps(self.api_attribute) @property def resource_uri(self): @@ -95,7 +96,7 @@ class WidgetBase: @property def json_resource_uri(self): - return simplejson.dumps(self.resource_uri) + return json.dumps(self.resource_uri) @property def can_write(self): @@ -132,13 +133,13 @@ class TextWidgetBase(WidgetBase): edit_url, edit_title, ) - self.accept_empty = simplejson.dumps(self.optional_field) + self.accept_empty = json.dumps(self.optional_field) self.title = title - self.widget_css_selector = simplejson.dumps("#" + self.content_box_id) + self.widget_css_selector = json.dumps("#" + self.content_box_id) @property def json_attribute_uri(self): - return simplejson.dumps(self.resource_uri + "/" + self.api_attribute) + return json.dumps(self.resource_uri + "/" + self.api_attribute) class DefinedTagMixin: @@ -221,8 +222,8 @@ class TextLineEditorWidget(TextWidgetBase, DefinedTagMixin): self.max_width = max_width self.truncate_lines = truncate_lines self.default_text = default_text - self.initial_value_override = simplejson.dumps(initial_value_override) - self.width = simplejson.dumps(width) + self.initial_value_override = json.dumps(initial_value_override) + self.width = json.dumps(width) @property def value(self): @@ -364,9 +365,9 @@ class InlineEditPickerWidget(WidgetBase): self.help_link = help_link # JSON encoded attributes. - self.json_content_box_id = simplejson.dumps(self.content_box_id) - self.json_attribute = simplejson.dumps(self.api_attribute + "_link") - self.json_vocabulary_name = simplejson.dumps( + self.json_content_box_id = json.dumps(self.content_box_id) + self.json_attribute = json.dumps(self.api_attribute + "_link") + self.json_vocabulary_name = json.dumps( self.exported_field.vocabularyName ) @@ -409,7 +410,7 @@ class InlineEditPickerWidget(WidgetBase): @property def json_config(self): - return simplejson.dumps(self.config) + return json.dumps(self.config) @cachedproperty def vocabulary(self): @@ -628,13 +629,11 @@ class InlineMultiCheckboxWidget(WidgetBase): self.has_choices = len(items) # JSON encoded attributes. - self.json_content_box_id = simplejson.dumps(self.content_box_id) - self.json_attribute = simplejson.dumps(self.api_attribute) - self.json_attribute_type = simplejson.dumps(attribute_type) - self.json_items = simplejson.dumps(items) - self.json_description = simplejson.dumps( - self.exported_field.description - ) + self.json_content_box_id = json.dumps(self.content_box_id) + self.json_attribute = json.dumps(self.api_attribute) + self.json_attribute_type = json.dumps(attribute_type) + self.json_items = json.dumps(items) + self.json_description = json.dumps(self.exported_field.description) @property def config(self): @@ -644,7 +643,7 @@ class InlineMultiCheckboxWidget(WidgetBase): @property def json_config(self): - return simplejson.dumps(self.config) + return json.dumps(self.config) def vocabulary_to_choice_edit_items( @@ -708,7 +707,7 @@ def vocabulary_to_choice_edit_items( items.append(new_item) if as_json: - return simplejson.dumps(items) + return json.dumps(items) else: return items @@ -812,7 +811,7 @@ class BooleanChoiceWidget(WidgetBase, DefinedTagMixin): @property def json_config(self): - return simplejson.dumps(self.config) + return json.dumps(self.config) class EnumChoiceWidget(WidgetBase): @@ -883,4 +882,4 @@ class EnumChoiceWidget(WidgetBase): @property def json_config(self): - return simplejson.dumps(self.config) + return json.dumps(self.config) diff --git a/lib/lp/app/browser/linkchecker.py b/lib/lp/app/browser/linkchecker.py index 2ad22eb..5eac333 100644 --- a/lib/lp/app/browser/linkchecker.py +++ b/lib/lp/app/browser/linkchecker.py @@ -5,7 +5,8 @@ __all__ = [ "LinkCheckerAPI", ] -import simplejson +import json + from zope.component import getUtility from lp.app.errors import NotFoundError @@ -56,8 +57,8 @@ class LinkCheckerAPI(LaunchpadView): result = {} links_to_check_data = self.request.get("link_hrefs") if links_to_check_data is None: - return simplejson.dumps(result) - links_to_check = simplejson.loads(links_to_check_data) + return json.dumps(result) + links_to_check = json.loads(links_to_check_data) for link_type in links_to_check: links = links_to_check[link_type] @@ -65,7 +66,7 @@ class LinkCheckerAPI(LaunchpadView): result[link_type] = link_info self.request.response.setHeader("Content-type", "application/json") - return simplejson.dumps(result) + return json.dumps(result) def check_branch_links(self, links): """Check links of the form /+code/foo/bar""" diff --git a/lib/lp/app/browser/tests/test_inlinemulticheckboxwidget.py b/lib/lp/app/browser/tests/test_inlinemulticheckboxwidget.py index 2e0af72..3222fa4 100644 --- a/lib/lp/app/browser/tests/test_inlinemulticheckboxwidget.py +++ b/lib/lp/app/browser/tests/test_inlinemulticheckboxwidget.py @@ -3,7 +3,8 @@ """Tests for the InlineMultiCheckboxWidget.""" -import simplejson +import json + from lazr.enum import EnumeratedType, Item from zope.interface import Interface from zope.schema import List @@ -58,18 +59,18 @@ class TestInlineMultiCheckboxWidget(TestCaseWithFactory): item.value, force_local_path=True ) expected_items = self._makeExpectedItems(vocab, value_fn=value_fn) - self.assertEqual(simplejson.dumps(expected_items), widget.json_items) + self.assertEqual(json.dumps(expected_items), widget.json_items) def test_items_for_custom_vocabulary(self): widget = self._getWidget(vocabulary=Alphabet) expected_items = self._makeExpectedItems(Alphabet) - self.assertEqual(simplejson.dumps(expected_items), widget.json_items) + self.assertEqual(json.dumps(expected_items), widget.json_items) def test_items_for_custom_vocabulary_name(self): widget = self._getWidget(vocabulary="CountryName") vocab = getVocabularyRegistry().get(None, "CountryName") expected_items = self._makeExpectedItems(vocab) - self.assertEqual(simplejson.dumps(expected_items), widget.json_items) + self.assertEqual(json.dumps(expected_items), widget.json_items) def test_selected_items_checked(self): widget = self._getWidget( @@ -78,4 +79,4 @@ class TestInlineMultiCheckboxWidget(TestCaseWithFactory): expected_items = self._makeExpectedItems( Alphabet, selected=[Alphabet.A] ) - self.assertEqual(simplejson.dumps(expected_items), widget.json_items) + self.assertEqual(json.dumps(expected_items), widget.json_items) diff --git a/lib/lp/app/browser/tests/test_launchpadform.py b/lib/lp/app/browser/tests/test_launchpadform.py index 56f7f91..7ca3863 100644 --- a/lib/lp/app/browser/tests/test_launchpadform.py +++ b/lib/lp/app/browser/tests/test_launchpadform.py @@ -3,9 +3,9 @@ """Tests for the lp.app.browser.launchpadform module.""" +import json from os.path import dirname, join -import simplejson from lxml import html from testtools.content import text_content from zope.browserpage import ViewPageTemplateFile @@ -246,7 +246,7 @@ class TestAjaxValidator(TestCase): "errors": {"field.single_line": "An error occurred"}, "form_wide_errors": ["A form error"], }, - simplejson.loads(view.form_result), + json.loads(view.form_result), ) def test_non_ajax_failure_handler(self): @@ -293,5 +293,5 @@ class TestAjaxValidator(TestCase): "errors": {}, "form_wide_errors": ["An action error"], }, - simplejson.loads(view.form_result), + json.loads(view.form_result), ) diff --git a/lib/lp/app/browser/tests/test_linkchecker.py b/lib/lp/app/browser/tests/test_linkchecker.py index 9393a2e..0215c41 100644 --- a/lib/lp/app/browser/tests/test_linkchecker.py +++ b/lib/lp/app/browser/tests/test_linkchecker.py @@ -3,9 +3,9 @@ """Unit tests for the LinkCheckerAPI.""" +import json from random import shuffle -import simplejson from zope.security.proxy import removeSecurityProxy from lp.app.browser.linkchecker import LinkCheckerAPI @@ -22,13 +22,13 @@ class TestLinkCheckerAPI(TestCaseWithFactory): BRANCH_URL_TEMPLATE = "/+code/%s" def check_invalid_links(self, result_json, link_type, invalid_links): - link_dict = simplejson.loads(result_json) + link_dict = json.loads(result_json) links_to_check = link_dict[link_type]["invalid"] self.assertEqual(len(invalid_links), len(links_to_check)) self.assertEqual(set(invalid_links), set(links_to_check)) def check_valid_links(self, result_json, link_type, valid_links): - link_dict = simplejson.loads(result_json) + link_dict = json.loads(result_json) links_to_check = link_dict[link_type]["valid"] self.assertEqual(len(valid_links), len(links_to_check)) self.assertEqual(set(valid_links), set(links_to_check)) @@ -109,7 +109,7 @@ class TestLinkCheckerAPI(TestCaseWithFactory): shuffle(bug_urls) links_to_check = dict(branch_links=branch_urls, bug_links=bug_urls) - link_json = simplejson.dumps(links_to_check) + link_json = json.dumps(links_to_check) request = LaunchpadTestRequest(link_hrefs=link_json) link_checker = LinkCheckerAPI(object(), request) @@ -124,7 +124,7 @@ class TestLinkCheckerAPI(TestCaseWithFactory): request = LaunchpadTestRequest() link_checker = LinkCheckerAPI(object(), request) result_json = link_checker() - link_dict = simplejson.loads(result_json) + link_dict = json.loads(result_json) self.assertEqual(link_dict, {}) def test_only_valid_links(self): diff --git a/lib/lp/app/browser/tests/test_vocabulary.py b/lib/lp/app/browser/tests/test_vocabulary.py index 35676e0..4b81a7e 100644 --- a/lib/lp/app/browser/tests/test_vocabulary.py +++ b/lib/lp/app/browser/tests/test_vocabulary.py @@ -3,12 +3,12 @@ """Test vocabulary adapters.""" +import json from datetime import datetime from typing import List from urllib.parse import urlencode import pytz -import simplejson from zope.component import getSiteManager, getUtility from zope.formlib.interfaces import MissingInputError from zope.interface import implementer @@ -609,7 +609,7 @@ class HugeVocabularyJSONViewTestCase(TestCaseWithFactory): bugtask = self.factory.makeBugTask(target=product) form = dict(name="TestPerson", search_text="xpting") view = self.create_vocabulary_view(form, context=bugtask) - result = simplejson.loads(view()) + result = json.loads(view()) expected = [ { "alt_title": team.name, @@ -669,7 +669,7 @@ class HugeVocabularyJSONViewTestCase(TestCaseWithFactory): name="TestPerson", search_text="xpting", search_filter=vocab_filter ) view = self.create_vocabulary_view(form, context=product) - result = simplejson.loads(view()) + result = json.loads(view()) entries = result["entries"] self.assertEqual(1, len(entries)) self.assertEqual("xpting-person", entries[0]["value"]) @@ -684,7 +684,7 @@ class HugeVocabularyJSONViewTestCase(TestCaseWithFactory): login_person(person) form = dict(name="TestPerson", search_text="pting-n") view = self.create_vocabulary_view(form) - result = simplejson.loads(view()) + result = json.loads(view()) expected = email[: MAX_DESCRIPTION_LENGTH - 3] + "..." self.assertEqual("pting-n", result["entries"][0]["value"]) self.assertEqual(expected, result["entries"][0]["description"]) @@ -693,7 +693,7 @@ class HugeVocabularyJSONViewTestCase(TestCaseWithFactory): # The results are batched. form = dict(name="TestPerson", search_text="pting") view = self.create_vocabulary_view(form) - result = simplejson.loads(view()) + result = json.loads(view()) total_size = result["total_size"] entries = len(result["entries"]) self.assertTrue( @@ -707,7 +707,7 @@ class HugeVocabularyJSONViewTestCase(TestCaseWithFactory): name="TestPerson", search_text="pting", start="0", batch="1" ) view = self.create_vocabulary_view(form) - result = simplejson.loads(view()) + result = json.loads(view()) self.assertEqual(6, result["total_size"]) self.assertEqual(1, len(result["entries"])) @@ -717,7 +717,7 @@ class HugeVocabularyJSONViewTestCase(TestCaseWithFactory): name="TestPerson", search_text="pting", start="1", batch="1" ) view = self.create_vocabulary_view(form) - result = simplejson.loads(view()) + result = json.loads(view()) self.assertEqual(6, result["total_size"]) self.assertEqual(1, len(result["entries"])) self.assertEqual("pting-2", result["entries"][0]["value"]) diff --git a/lib/lp/app/browser/vocabulary.py b/lib/lp/app/browser/vocabulary.py index 126881c..04d0599 100644 --- a/lib/lp/app/browser/vocabulary.py +++ b/lib/lp/app/browser/vocabulary.py @@ -10,10 +10,11 @@ __all__ = [ "vocabulary_filters", ] +import json + # This registers the registry. import zope.vocabularyregistry.registry # noqa: F401 # isort: split -import simplejson from lazr.restful.interfaces import IWebServiceClientRequest from zope.component import adapter, getUtility from zope.formlib.interfaces import MissingInputError @@ -533,7 +534,7 @@ class HugeVocabularyJSONView: result.append(entry) self.request.response.setHeader("Content-type", "application/json") - return simplejson.dumps(dict(total_size=total_size, entries=result)) + return json.dumps(dict(total_size=total_size, entries=result)) def vocabulary_filters(vocabulary): diff --git a/lib/lp/app/widgets/popup.py b/lib/lp/app/widgets/popup.py index 3b879ee..b2524ef 100644 --- a/lib/lp/app/widgets/popup.py +++ b/lib/lp/app/widgets/popup.py @@ -13,7 +13,8 @@ __all__ = [ "VocabularyPickerWidget", ] -import simplejson +import json + from zope.browserpage import ViewPageTemplateFile from zope.component import getUtility from zope.formlib.interfaces import ConversionError @@ -160,7 +161,7 @@ class VocabularyPickerWidget(SingleDataHelper, ItemsWidgetBase): @property def json_config(self): - return simplejson.dumps(self.config) + return json.dumps(self.config) @property def extra_no_results_message(self): diff --git a/lib/lp/app/widgets/tests/test_popup.py b/lib/lp/app/widgets/tests/test_popup.py index 51b7162..0a4ce99 100644 --- a/lib/lp/app/widgets/tests/test_popup.py +++ b/lib/lp/app/widgets/tests/test_popup.py @@ -1,7 +1,8 @@ # Copyright 2010-2016 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). -import simplejson +import json + from zope.interface import Interface from zope.interface.interface import InterfaceClass from zope.schema import Choice @@ -65,7 +66,7 @@ class TestVocabularyPickerWidget(TestCaseWithFactory): bound_field, self.vocabulary, self.request ) - widget_config = simplejson.loads(picker_widget.json_config) + widget_config = json.loads(picker_widget.json_config) self.assertEqual("ValidTeamOwner", picker_widget.vocabulary_name) self.assertEqual( [ @@ -112,7 +113,7 @@ class TestVocabularyPickerWidget(TestCaseWithFactory): bound_field, vocabulary, self.request ) - widget_config = simplejson.loads(picker_widget.json_config) + widget_config = json.loads(picker_widget.json_config) self.assertEqual( [ { diff --git a/lib/lp/blueprints/browser/specificationsubscription.py b/lib/lp/blueprints/browser/specificationsubscription.py index bb93933..c4e15ae 100644 --- a/lib/lp/blueprints/browser/specificationsubscription.py +++ b/lib/lp/blueprints/browser/specificationsubscription.py @@ -9,10 +9,10 @@ __all__ = [ "SpecificationSubscriptionEditView", ] +import json from typing import List from lazr.delegates import delegate_to -from simplejson import dumps from zope.component import getUtility from lp import _ @@ -211,7 +211,7 @@ class SpecificationPortletSubcribersIds(LaunchpadView): @property def subscriber_ids_js(self): """Return subscriber_ids in a form suitable for JavaScript use.""" - return dumps(self.subscriber_ids) + return json.dumps(self.subscriber_ids) def render(self): """Override the default render() to return only JSON.""" diff --git a/lib/lp/bugs/browser/bugsubscription.py b/lib/lp/bugs/browser/bugsubscription.py index 642b14b..eaf3ea6 100644 --- a/lib/lp/bugs/browser/bugsubscription.py +++ b/lib/lp/bugs/browser/bugsubscription.py @@ -11,11 +11,11 @@ __all__ = [ "BugSubscriptionListView", ] +import json from typing import List from lazr.delegates import delegate_to from lazr.restful.interfaces import IJSONRequestCache, IWebServiceClientRequest -from simplejson import dumps from zope import formlib from zope.formlib.itemswidgets import RadioWidget from zope.formlib.widget import CustomWidgetFactory @@ -663,7 +663,7 @@ class BugPortletSubscribersWithDetails(LaunchpadView): @property def subscriber_data_js(self): - return dumps(self.subscriber_data) + return json.dumps(self.subscriber_data) def render(self): """Override the default render() to return only JSON.""" diff --git a/lib/lp/bugs/browser/bugtarget.py b/lib/lp/bugs/browser/bugtarget.py index e7b3dc6..7c5e70a 100644 --- a/lib/lp/bugs/browser/bugtarget.py +++ b/lib/lp/bugs/browser/bugtarget.py @@ -18,6 +18,7 @@ __all__ = [ ] import http.client +import json from datetime import datetime from functools import partial from io import BytesIO @@ -26,7 +27,6 @@ from urllib.parse import quote, urlencode from lazr.restful.interface import copy_field from lazr.restful.interfaces import IJSONRequestCache from pytz import timezone -from simplejson import dumps from zope.browserpage import ViewPageTemplateFile from zope.component import getUtility from zope.formlib.form import Fields @@ -1361,8 +1361,8 @@ class OfficialBugTagsManageView(LaunchpadEditFormView): @property def tags_js_data(self): """Return the JSON representation of the bug tags.""" - # The model returns dict and list respectively but dumps blows up on - # security proxied objects. + # The model returns dict and list respectively, but json.dumps blows + # up on security proxied objects. used_tags = removeSecurityProxy( self.context.getUsedBugTagsWithOpenCounts(self.user) ) @@ -1373,9 +1373,9 @@ class OfficialBugTagsManageView(LaunchpadEditFormView): var valid_name_pattern = %s; </script> """ % ( - dumps(used_tags), - dumps(official_tags), - dumps(valid_name_pattern.pattern), + json.dumps(used_tags), + json.dumps(official_tags), + json.dumps(valid_name_pattern.pattern), ) @property diff --git a/lib/lp/bugs/browser/bugtask.py b/lib/lp/bugs/browser/bugtask.py index d000562..161e405 100644 --- a/lib/lp/bugs/browser/bugtask.py +++ b/lib/lp/bugs/browser/bugtask.py @@ -25,6 +25,7 @@ __all__ = [ "get_visible_comments", ] +import json import re from collections import defaultdict from datetime import datetime, timedelta @@ -46,7 +47,6 @@ from lazr.restful.interfaces import ( ) from lazr.restful.utils import smartquote from pytz import utc -from simplejson import dumps from zope import formlib from zope.browserpage import ViewPageTemplateFile from zope.component import adapter, getAdapter, getMultiAdapter, getUtility @@ -962,7 +962,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin): # Unwrap the security proxy. - official_tags is a security proxy # wrapped list. available_tags = list(self.context.bug.official_tags) - return "var available_official_tags = %s;" % dumps(available_tags) + return "var available_official_tags = %s;" % json.dumps(available_tags) @property def user_is_admin(self): @@ -1775,7 +1775,7 @@ class BugTaskDeletionView(ReturnToReferrerMixin, LaunchpadFormView): self.request.response.setHeader( "Content-type", "application/json" ) - return dumps(None) + return json.dumps(None) launchbag = getUtility(ILaunchBag) launchbag.add(bug.default_bugtask) # If we are deleting the current highlighted bugtask via ajax, @@ -1788,7 +1788,7 @@ class BugTaskDeletionView(ReturnToReferrerMixin, LaunchpadFormView): self.request.response.setHeader( "Content-type", "application/json" ) - return dumps(dict(bugtask_url=next_url)) + return json.dumps(dict(bugtask_url=next_url)) # No redirect required so return the new bugtask table HTML. view = getMultiAdapter( (bug, self.request), name="+bugtasks-and-nominations-table" diff --git a/lib/lp/bugs/browser/tests/test_bug_views.py b/lib/lp/bugs/browser/tests/test_bug_views.py index 2264af4..927c93c 100644 --- a/lib/lp/bugs/browser/tests/test_bug_views.py +++ b/lib/lp/bugs/browser/tests/test_bug_views.py @@ -3,11 +3,11 @@ """Tests for Bug Views.""" +import json import re from datetime import datetime, timedelta import pytz -import simplejson from soupmatchers import HTMLContains, Tag, Within from storm.store import Store from testtools.matchers import Contains, Equals, MatchesAll, Not @@ -484,7 +484,7 @@ class TestBugSecrecyViews(TestCaseWithFactory): **extra, ) view = self.createInitializedSecrecyView(person, bug, request) - result_data = simplejson.loads(view.render()) + result_data = json.loads(view.render()) cache_data = result_data["cache_data"] self.assertFalse(cache_data["other_subscription_notifications"]) diff --git a/lib/lp/bugs/browser/tests/test_bugtask.py b/lib/lp/bugs/browser/tests/test_bugtask.py index b816cdf..724af22 100644 --- a/lib/lp/bugs/browser/tests/test_bugtask.py +++ b/lib/lp/bugs/browser/tests/test_bugtask.py @@ -1,11 +1,11 @@ # Copyright 2009-2022 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). +import json import re from datetime import datetime, timedelta from urllib.parse import urlencode -import simplejson import soupmatchers import transaction from lazr.restful.interfaces import IJSONRequestCache @@ -1166,9 +1166,9 @@ class TestBugTaskDeleteView(TestCaseWithFactory): principal=bugtask.owner, **extra, ) - result_data = simplejson.loads(view.render()) + result_data = json.loads(view.render()) self.assertEqual([bug.default_bugtask], bug.bugtasks) - notifications = simplejson.loads( + notifications = json.loads( view.request.response.getHeader("X-Lazr-Notifications") ) self.assertEqual(1, len(notifications)) @@ -1198,9 +1198,9 @@ class TestBugTaskDeleteView(TestCaseWithFactory): principal=bug.owner, **extra, ) - result_data = simplejson.loads(view.render()) + result_data = json.loads(view.render()) self.assertEqual([bug.default_bugtask], bug.bugtasks) - notifications = simplejson.loads( + notifications = json.loads( view.request.response.getHeader("X-Lazr-Notifications") ) self.assertEqual(1, len(notifications)) diff --git a/lib/lp/bugs/browser/widgets/bug.py b/lib/lp/bugs/browser/widgets/bug.py index 08dad8e..5c327b1 100644 --- a/lib/lp/bugs/browser/widgets/bug.py +++ b/lib/lp/bugs/browser/widgets/bug.py @@ -8,9 +8,9 @@ __all__ = [ "LargeBugTagsWidget", ] +import json import re -from simplejson import dumps from zope.component import getUtility from zope.formlib.interfaces import ConversionError, WidgetInputError from zope.formlib.textwidgets import IntWidget, TextAreaWidget, TextWidget @@ -168,7 +168,7 @@ class BugTagsWidget(BugTagsWidgetBase, TextWidget): official_tags = list(pillar_target.official_bug_tags) else: official_tags = [] - return "var official_tags = %s;" % dumps(official_tags) + return "var official_tags = %s;" % json.dumps(official_tags) class BugTagsFrozenSetWidget(BugTagsWidget): diff --git a/lib/lp/bugs/model/apportjob.py b/lib/lp/bugs/model/apportjob.py index caf0fbd..a4612d5 100644 --- a/lib/lp/bugs/model/apportjob.py +++ b/lib/lp/bugs/model/apportjob.py @@ -8,9 +8,9 @@ __all__ = [ "ApportJobDerived", ] +import json from io import BytesIO -import simplejson import six from lazr.delegates import delegate_to from storm.expr import And @@ -60,10 +60,10 @@ class ApportJob(StormBase): # only delegates to ApportJob we can't simply directly access the # _json_data property, so we use a getter and setter here instead. def _set_metadata(self, metadata): - self._json_data = six.ensure_text(simplejson.dumps(metadata, "utf-8")) + self._json_data = six.ensure_text(json.dumps(metadata, "utf-8")) def _get_metadata(self): - return simplejson.loads(self._json_data) + return json.loads(self._json_data) metadata = property(_get_metadata, _set_metadata) @@ -76,7 +76,7 @@ class ApportJob(StormBase): dict. """ super().__init__() - json_data = simplejson.dumps(metadata) + json_data = json.dumps(metadata) self.job = Job() self.blob = blob self.job_type = job_type diff --git a/lib/lp/bugs/stories/webservice/xx-bug-target.rst b/lib/lp/bugs/stories/webservice/xx-bug-target.rst index f1fa5f2..8fb47c7 100644 --- a/lib/lp/bugs/stories/webservice/xx-bug-target.rst +++ b/lib/lp/bugs/stories/webservice/xx-bug-target.rst @@ -16,12 +16,12 @@ All bug targets have a read/write bug_reporting_guidelines property. >>> print(product["bug_reporting_guidelines"]) None - >>> from simplejson import dumps + >>> import json >>> patch = { ... "bug_reporting_guidelines": "Please run `ubuntu-bug -p firefox`." ... } >>> response = webservice.patch( - ... product["self_link"], "application/json", dumps(patch) + ... product["self_link"], "application/json", json.dumps(patch) ... ) >>> product = webservice.get(product_url).jsonBody() @@ -36,7 +36,7 @@ Not everyone can modify it however: ... ) ... } >>> response = user_webservice.patch( - ... product["self_link"], "application/json", dumps(patch) + ... product["self_link"], "application/json", json.dumps(patch) ... ) >>> print(response) HTTP/1.1 401 Unauthorized... @@ -109,7 +109,7 @@ The bug supervisor of a product can also add tags. ... webservice.patch( ... "/tags-test-product2", ... "application/json", - ... dumps({"bug_supervisor_link": ws_salgado["self_link"]}), + ... json.dumps({"bug_supervisor_link": ws_salgado["self_link"]}), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -144,7 +144,6 @@ Official tags must conform to the same format as ordinary tags. We can also access official tags as a list. - >>> from simplejson import dumps >>> tags_test_product = webservice.get("/tags-test-product").jsonBody() >>> tags_test_product["official_bug_tags"] [] @@ -152,7 +151,7 @@ We can also access official tags as a list. ... webservice.patch( ... "/tags-test-product", ... "application/json", - ... dumps({"official_bug_tags": ["foo", "bar"]}), + ... json.dumps({"official_bug_tags": ["foo", "bar"]}), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -171,7 +170,7 @@ We can also access official tags as a list. ... webservice.patch( ... "/testix", ... "application/json", - ... dumps({"official_bug_tags": ["foo", "bar"]}), + ... json.dumps({"official_bug_tags": ["foo", "bar"]}), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -189,7 +188,9 @@ We can retrieve or set a person or team as the bug supervisor for projects. ... webservice.patch( ... "/firefox", ... "application/json", - ... dumps({"bug_supervisor_link": firefox_project["owner_link"]}), + ... json.dumps( + ... {"bug_supervisor_link": firefox_project["owner_link"]} + ... ), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -208,7 +209,9 @@ We can also do this for distributions. ... webservice.patch( ... "/ubuntutest", ... "application/json", - ... dumps({"bug_supervisor_link": ubuntutest_dist["owner_link"]}), + ... json.dumps( + ... {"bug_supervisor_link": ubuntutest_dist["owner_link"]} + ... ), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -223,7 +226,7 @@ Setting the bug supervisor is restricted to owners and launchpad admins. ... user_webservice.patch( ... "/ubuntutest", ... "application/json", - ... dumps({"bug_supervisor_link": None}), + ... json.dumps({"bug_supervisor_link": None}), ... ) ... ) HTTP/1.1 401 Unauthorized diff --git a/lib/lp/bugs/stories/webservice/xx-bug.rst b/lib/lp/bugs/stories/webservice/xx-bug.rst index 80c2f24..851aad5 100644 --- a/lib/lp/bugs/stories/webservice/xx-bug.rst +++ b/lib/lp/bugs/stories/webservice/xx-bug.rst @@ -195,7 +195,7 @@ distribution in which the bug exists. To mark a bug as private, we patch the `private` attribute of the bug. - >>> from simplejson import dumps + >>> import json >>> bug_twelve = webservice.get("/bugs/12").jsonBody() >>> bug_twelve["private"] False @@ -203,7 +203,7 @@ To mark a bug as private, we patch the `private` attribute of the bug. ... webservice.patch( ... bug_twelve["self_link"], ... "application/json", - ... dumps(dict(private=True)), + ... json.dumps(dict(private=True)), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -214,7 +214,7 @@ To mark a bug as private, we patch the `private` attribute of the bug. ... webservice.patch( ... bug_twelve["self_link"], ... "application/json", - ... dumps(dict(private=False)), + ... json.dumps(dict(private=False)), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -228,7 +228,7 @@ attribute of the bug. ... webservice.patch( ... bug_twelve["self_link"], ... "application/json", - ... dumps(dict(duplicate_of_link=bug_one["self_link"])), + ... json.dumps(dict(duplicate_of_link=bug_one["self_link"])), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -242,7 +242,7 @@ Now set it back to none: ... webservice.patch( ... bug_twelve["self_link"], ... "application/json", - ... dumps(dict(duplicate_of_link=None)), + ... json.dumps(dict(duplicate_of_link=None)), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -259,7 +259,7 @@ Due to bug #1088358 the error is escaped as if it was HTML. ... webservice.patch( ... dupe_url, ... "application/json", - ... dumps( + ... json.dumps( ... dict( ... duplicate_of_link=webservice.getAbsoluteUrl("/bugs/5") ... ) @@ -272,7 +272,7 @@ Due to bug #1088358 the error is escaped as if it was HTML. ... webservice.patch( ... webservice.getAbsoluteUrl("/bugs/5"), ... "application/json", - ... dumps(dict(duplicate_of_link=dupe_url)), + ... json.dumps(dict(duplicate_of_link=dupe_url)), ... ) ... ) HTTP/1.1 400 Bad Request @@ -285,7 +285,7 @@ Due to bug #1088358 the error is escaped as if it was HTML. ... webservice.patch( ... dupe_url, ... "application/json", - ... dumps(dict(duplicate_of_link=None)), + ... json.dumps(dict(duplicate_of_link=None)), ... ) ... ) HTTP/1.1 209 Content Returned... @@ -444,7 +444,9 @@ It's possible to change the task's assignee. >>> patch = {"assignee_link": webservice.getAbsoluteUrl("/~cprov")} >>> bugtask_path = bug_one_bugtasks[0]["self_link"] >>> print( - ... webservice.patch(bugtask_path, "application/json", dumps(patch)) + ... webservice.patch( + ... bugtask_path, "application/json", json.dumps(patch) + ... ) ... ) HTTP/1.1 209 Content Returned... @@ -460,7 +462,9 @@ The task's importance can be modified directly. >>> patch = {"importance": "High"} >>> print( - ... webservice.patch(bugtask_path, "application/json", dumps(patch)) + ... webservice.patch( + ... bugtask_path, "application/json", json.dumps(patch) + ... ) ... ) HTTP/1.1 209 Content Returned... @@ -489,7 +493,9 @@ The task's status can also be modified directly. >>> patch = {"status": "Fix Committed"} >>> print( - ... webservice.patch(bugtask_path, "application/json", dumps(patch)) + ... webservice.patch( + ... bugtask_path, "application/json", json.dumps(patch) + ... ) ... ) HTTP/1.1 209 Content Returned... @@ -507,7 +513,9 @@ If an error occurs during a request that sets both 'status' and >>> patch = {"importance": "High", "status": "No Such Status"} >>> print( - ... webservice.patch(bugtask_path, "application/json", dumps(patch)) + ... webservice.patch( + ... bugtask_path, "application/json", json.dumps(patch) + ... ) ... ) HTTP/1.1 400 Bad Request... @@ -528,7 +536,9 @@ The milestone can only be set by appropriately privileged users. ... ) ... } >>> print( - ... webservice.patch(bugtask_path, "application/json", dumps(patch)) + ... webservice.patch( + ... bugtask_path, "application/json", json.dumps(patch) + ... ) ... ) HTTP/1.1 209 Content Returned... @@ -546,7 +556,7 @@ unchanged value. ... } >>> print( ... user_webservice.patch( - ... bugtask_path, "application/json", dumps(patch) + ... bugtask_path, "application/json", json.dumps(patch) ... ) ... ) HTTP/1.1 401 Unauthorized... @@ -583,7 +593,7 @@ We can also PATCH the target attribute to accomplish the same thing. ... webservice.patch( ... task["self_link"].replace("mozilla-firefox", "evolution"), ... "application/json", - ... dumps( + ... json.dumps( ... { ... "target_link": webservice.getAbsoluteUrl( ... "/debian/+source/alsa-utils" @@ -636,7 +646,9 @@ target, we reset it in order to avoid data inconsistencies. ... } >>> print( ... webservice.patch( - ... firefox_bugtask["self_link"], "application/json", dumps(patch) + ... firefox_bugtask["self_link"], + ... "application/json", + ... json.dumps(patch), ... ) ... ) HTTP/1.1 209 Content Returned @@ -1104,7 +1116,7 @@ They can also update the subscription's bug_notification_level directly. ... webservice.patch( ... new_subscription["self_link"], ... "application/json", - ... dumps(patch), + ... json.dumps(patch), ... api_version="devel", ... ).jsonBody() ... ) @@ -1347,7 +1359,7 @@ We can modify the remote bug. >>> patch = {"remote_bug": "1234"} >>> response = webservice.patch( - ... bug_watch_2000["self_link"], "application/json", dumps(patch) + ... bug_watch_2000["self_link"], "application/json", json.dumps(patch) ... ) >>> bug_watch = webservice.get(bug_watch_2000["self_link"]).jsonBody() @@ -1358,7 +1370,7 @@ But we can't change other things, like the URL. >>> patch = {"url": "http://www.example.com/"} >>> response = webservice.patch( - ... bug_watch_2000["self_link"], "application/json", dumps(patch) + ... bug_watch_2000["self_link"], "application/json", json.dumps(patch) ... ) >>> print(response) HTTP/1.1 400 Bad Request... @@ -1443,7 +1455,7 @@ We can change various aspects of bug trackers. ... "contact_details": "b...@example.com", ... } >>> response = webservice.patch( - ... bug_tracker["self_link"], "application/json", dumps(patch) + ... bug_tracker["self_link"], "application/json", json.dumps(patch) ... ) >>> print(response) HTTP/1.1 301 Moved Permanently... @@ -1493,7 +1505,7 @@ Non-admins can't disable a bugtracker through the API. ... public_webservice.patch( ... bug_tracker_path, ... "application/json", - ... dumps(dict(active=False)), + ... json.dumps(dict(active=False)), ... ) ... ) HTTP/1.1 401 Unauthorized @@ -1503,7 +1515,9 @@ Non-admins can't disable a bugtracker through the API. Admins can, however. >>> bug_tracker = webservice.patch( - ... bug_tracker_path, "application/json", dumps(dict(active=False)) + ... bug_tracker_path, + ... "application/json", + ... json.dumps(dict(active=False)), ... ).jsonBody() >>> pprint_entry(bug_tracker) active: False... diff --git a/lib/lp/bugs/tests/bug.py b/lib/lp/bugs/tests/bug.py index acfa1a2..3cc9b4b 100644 --- a/lib/lp/bugs/tests/bug.py +++ b/lib/lp/bugs/tests/bug.py @@ -3,6 +3,7 @@ """Helper functions for bug-related doctests and pagetests.""" +import json import re import textwrap from datetime import datetime, timedelta @@ -40,9 +41,7 @@ def print_also_notified(bug_page): def print_subscribers(bug_page, subscription_level=None, reverse=False): """Print the subscribers listed in the subscribers JSON portlet.""" - from simplejson import loads - - details = loads(bug_page) + details = json.loads(bug_page.decode()) if details is None: # No subscribers at all. diff --git a/lib/lp/bugs/tests/test_bugs_webservice.py b/lib/lp/bugs/tests/test_bugs_webservice.py index 92664a4..fcd29c0 100644 --- a/lib/lp/bugs/tests/test_bugs_webservice.py +++ b/lib/lp/bugs/tests/test_bugs_webservice.py @@ -11,7 +11,6 @@ from datetime import datetime, timedelta import pytz import six from lazr.lifecycle.interfaces import IDoNotSnapshot -from simplejson import dumps from storm.store import Store from testtools.matchers import Equals, LessThan from zope.component import getMultiAdapter @@ -121,7 +120,7 @@ class TestBugDescriptionRepresentation(TestCaseWithFactory): response = self.webservice.patch( bug_two_json["self_link"], "application/json", - dumps(dict(description=new_description)), + json.dumps(dict(description=new_description)), headers=dict(accept="application/xhtml+xml"), ) @@ -517,7 +516,7 @@ class TestBugDateLastUpdated(TestCaseWithFactory): date_last_updated = bug.date_last_updated logout() response = webservice.patch( - task_url, "application/json", dumps(dict(status="Invalid")) + task_url, "application/json", json.dumps(dict(status="Invalid")) ) self.assertEqual(209, response.status) with person_logged_in(owner): diff --git a/lib/lp/code/browser/branch.py b/lib/lp/code/browser/branch.py index 1ecf228..5ecce7e 100644 --- a/lib/lp/code/browser/branch.py +++ b/lib/lp/code/browser/branch.py @@ -22,10 +22,10 @@ __all__ = [ "RegisterBranchMergeProposalView", ] +import json from datetime import datetime import pytz -import simplejson from lazr.lifecycle.event import ObjectModifiedEvent from lazr.lifecycle.snapshot import Snapshot from lazr.restful.fields import Reference @@ -1354,7 +1354,7 @@ class RegisterBranchMergeProposalView(LaunchpadFormView): if self.request.is_ajax and len(visible_branches) < 2: self.request.response.setStatus(400, "Branch Visibility") self.request.response.setHeader("Content-Type", "application/json") - return simplejson.dumps( + return json.dumps( { "person_name": visibility_info["person_name"], "branches_to_check": branch_names, diff --git a/lib/lp/code/browser/branchmergeproposal.py b/lib/lp/code/browser/branchmergeproposal.py index a2fe57e..a789afb 100644 --- a/lib/lp/code/browser/branchmergeproposal.py +++ b/lib/lp/code/browser/branchmergeproposal.py @@ -24,11 +24,11 @@ __all__ = [ "latest_proposals_for_each_branch", ] +import json import operator from functools import wraps from urllib.parse import urlsplit, urlunsplit -import simplejson from lazr.delegates import delegate_to from lazr.restful.interface import copy_field from lazr.restful.interfaces import IJSONRequestCache, IWebServiceClientRequest @@ -851,7 +851,7 @@ class BranchMergeProposalView( @property def status_config(self): """The config to configure the ChoiceSource JS widget.""" - return simplejson.dumps( + return json.dumps( { "status_widget_items": vocabulary_to_choice_edit_items( self._createStatusVocabulary(), diff --git a/lib/lp/code/browser/sourcepackagerecipe.py b/lib/lp/code/browser/sourcepackagerecipe.py index be599c4..002520d 100644 --- a/lib/lp/code/browser/sourcepackagerecipe.py +++ b/lib/lp/code/browser/sourcepackagerecipe.py @@ -14,8 +14,8 @@ __all__ = [ ] import itertools +import json -import simplejson from brzbuildrecipe.recipe import ForbiddenInstructionError, RecipeParseError from lazr.lifecycle.event import ObjectModifiedEvent from lazr.lifecycle.snapshot import Snapshot @@ -485,7 +485,7 @@ class SourcePackageRecipeRequestBuildsAjaxView( return_data = dict(builds=builds, errors=errors) if informational: return_data.update(informational) - return simplejson.dumps(return_data) + return json.dumps(return_data) def failure(self, action, data, errors): """Called by the form if validate() finds any errors. diff --git a/lib/lp/code/browser/tests/test_branchmergeproposal.py b/lib/lp/code/browser/tests/test_branchmergeproposal.py index b481374..e624054 100644 --- a/lib/lp/code/browser/tests/test_branchmergeproposal.py +++ b/lib/lp/code/browser/tests/test_branchmergeproposal.py @@ -5,12 +5,12 @@ import doctest import hashlib +import json import re from datetime import datetime, timedelta from difflib import diff_bytes, unified_diff import pytz -import simplejson import transaction from fixtures import FakeLogger from lazr.lifecycle.event import ObjectModifiedEvent @@ -877,7 +877,7 @@ class TestRegisterBranchMergeProposalViewBzr( self.assertEqual( "400 Branch Visibility", view.request.response.getStatusString() ) - self.assertEqual(expected_data, simplejson.loads(result_data)) + self.assertEqual(expected_data, json.loads(result_data)) def test_register_ajax_request_with_validation_errors(self): # Ajax submits where there is a validation error in the submitted data @@ -917,7 +917,7 @@ class TestRegisterBranchMergeProposalViewBzr( }, "form_wide_errors": [], }, - simplejson.loads(view.form_result), + json.loads(view.form_result), ) @@ -1013,7 +1013,7 @@ class TestRegisterBranchMergeProposalViewGit( "400 Repository Visibility", view.request.response.getStatusString(), ) - self.assertEqual(expected_data, simplejson.loads(result_data)) + self.assertEqual(expected_data, json.loads(result_data)) def test_register_ajax_request_with_validation_errors(self): # Ajax submits where there is a validation error in the submitted data @@ -1053,7 +1053,7 @@ class TestRegisterBranchMergeProposalViewGit( }, "form_wide_errors": [], }, - simplejson.loads(view.form_result), + json.loads(view.form_result), ) def test_register_ajax_request_with_missing_target_git_repository(self): @@ -1086,7 +1086,7 @@ class TestRegisterBranchMergeProposalViewGit( }, "form_wide_errors": [], }, - simplejson.loads(view.form_result), + json.loads(view.form_result), ) def test_register_ajax_request_with_missing_target_git_path(self): @@ -1123,7 +1123,7 @@ class TestRegisterBranchMergeProposalViewGit( }, "form_wide_errors": [], }, - simplejson.loads(view.form_result), + json.loads(view.form_result), ) def test_register_ajax_request_with_missing_prerequisite_git_path(self): @@ -1170,7 +1170,7 @@ class TestRegisterBranchMergeProposalViewGit( }, "form_wide_errors": [], }, - simplejson.loads(view.form_result), + json.loads(view.form_result), ) diff --git a/lib/lp/code/model/branchmergeproposaljob.py b/lib/lp/code/model/branchmergeproposaljob.py index d120ebe..35c8bdf 100644 --- a/lib/lp/code/model/branchmergeproposaljob.py +++ b/lib/lp/code/model/branchmergeproposaljob.py @@ -19,11 +19,11 @@ __all__ = [ "UpdatePreviewDiffJob", ] +import json from contextlib import ExitStack from datetime import datetime, timedelta import pytz -import simplejson import six from lazr.delegates import delegate_to from lazr.enum import DBEnumeratedType, DBItem @@ -155,7 +155,7 @@ class BranchMergeProposalJob(StormBase): @property def metadata(self): - return simplejson.loads(self._json_data) + return json.loads(self._json_data) def __init__(self, branch_merge_proposal, job_type, metadata): """Constructor. @@ -166,7 +166,7 @@ class BranchMergeProposalJob(StormBase): dict. """ super().__init__() - json_data = simplejson.dumps(metadata) + json_data = json.dumps(metadata) self.job = Job() self.branch_merge_proposal = branch_merge_proposal self.job_type = job_type diff --git a/lib/lp/code/model/diff.py b/lib/lp/code/model/diff.py index 21e9968..7def5fd 100644 --- a/lib/lp/code/model/diff.py +++ b/lib/lp/code/model/diff.py @@ -10,12 +10,12 @@ __all__ = [ ] import io +import json import sys from contextlib import ExitStack from operator import attrgetter from uuid import uuid1 -import simplejson import six from breezy import trace from breezy.diff import show_diff_trees @@ -60,7 +60,7 @@ class Diff(SQLBase): return None return { key: tuple(value) - for key, value in simplejson.loads(self._diffstat).items() + for key, value in json.loads(self._diffstat).items() } def _set_diffstat(self, diffstat): @@ -69,7 +69,7 @@ class Diff(SQLBase): return # diffstats should be mappings of path to line counts. assert isinstance(diffstat, dict) - self._diffstat = simplejson.dumps(diffstat) + self._diffstat = json.dumps(diffstat) diffstat = property(_get_diffstat, _set_diffstat) diff --git a/lib/lp/registry/browser/team.py b/lib/lp/registry/browser/team.py index 32afa9a..8de222c 100644 --- a/lib/lp/registry/browser/team.py +++ b/lib/lp/registry/browser/team.py @@ -30,13 +30,12 @@ __all__ = [ "TeamReassignmentView", ] - +import json import math from datetime import datetime, timedelta from urllib.parse import unquote import pytz -import simplejson from lazr.restful.interface import copy_field from lazr.restful.interfaces import IJSONRequestCache from lazr.restful.utils import smartquote @@ -1061,7 +1060,7 @@ class TeamMailingListArchiveView(LaunchpadView): # XXX: jcsackett 18-1-2012: This needs to be updated to use the # grackle client, once that is available, instead of returning # an empty list as it does now. - return simplejson.loads("[]") + return json.loads("[]") class TeamAddView(TeamFormMixin, HasRenewalPolicyMixin, LaunchpadFormView): diff --git a/lib/lp/registry/browser/tests/test_pillar_sharing.py b/lib/lp/registry/browser/tests/test_pillar_sharing.py index c295f6d..75abf61 100644 --- a/lib/lp/registry/browser/tests/test_pillar_sharing.py +++ b/lib/lp/registry/browser/tests/test_pillar_sharing.py @@ -3,7 +3,8 @@ """Test views that manage sharing.""" -import simplejson +import json + from fixtures import FakeLogger from lazr.restful.interfaces import IJSONRequestCache from lazr.restful.utils import get_current_web_service_request @@ -307,7 +308,7 @@ class PillarSharingViewTestMixin: def test_picker_config(self): # Test the config passed to the disclosure sharing picker. view = create_view(self.pillar, name="+sharing") - picker_config = simplejson.loads(view.json_sharing_picker_config) + picker_config = json.loads(view.json_sharing_picker_config) self.assertTrue("vocabulary_filters" in picker_config) self.assertEqual("Share project information", picker_config["header"]) self.assertEqual( diff --git a/lib/lp/registry/browser/tests/test_team.py b/lib/lp/registry/browser/tests/test_team.py index fdbef5a..6330233 100644 --- a/lib/lp/registry/browser/tests/test_team.py +++ b/lib/lp/registry/browser/tests/test_team.py @@ -2,8 +2,8 @@ # GNU Affero General Public License version 3 (see the file LICENSE). import contextlib +import json -import simplejson import soupmatchers import transaction from lazr.restful.interfaces import IJSONRequestCache @@ -740,7 +740,7 @@ class TestMailingListArchiveView(TestCaseWithFactory): @contextlib.contextmanager def _override_messages(self, view_class, messages): def _message_shim(self): - return simplejson.loads(messages) + return json.loads(messages) tmp = TeamMailingListArchiveView._get_messages TeamMailingListArchiveView._get_messages = _message_shim diff --git a/lib/lp/registry/model/persontransferjob.py b/lib/lp/registry/model/persontransferjob.py index 14eaa9d..78bf6a9 100644 --- a/lib/lp/registry/model/persontransferjob.py +++ b/lib/lp/registry/model/persontransferjob.py @@ -9,10 +9,10 @@ __all__ = [ "PersonTransferJob", ] +import json from datetime import datetime import pytz -import simplejson import six import transaction from lazr.delegates import delegate_to @@ -84,7 +84,7 @@ class PersonTransferJob(StormBase): @property def metadata(self): - return simplejson.loads(self._json_data) + return json.loads(self._json_data) def __init__( self, minor_person, major_person, job_type, metadata, requester=None @@ -105,7 +105,7 @@ class PersonTransferJob(StormBase): self.major_person = major_person self.minor_person = minor_person - json_data = simplejson.dumps(metadata) + json_data = json.dumps(metadata) # XXX AaronBentley 2009-01-29 bug=322819: This should be a bytestring, # but the DB representation is unicode. self._json_data = six.ensure_text(json_data) diff --git a/lib/lp/registry/model/productjob.py b/lib/lp/registry/model/productjob.py index 5ca685d..107ffac 100644 --- a/lib/lp/registry/model/productjob.py +++ b/lib/lp/registry/model/productjob.py @@ -11,9 +11,9 @@ __all__ = [ "ThirtyDayCommercialExpirationJob", ] +import json from datetime import datetime, timedelta -import simplejson import six from lazr.delegates import delegate_to from pytz import utc @@ -125,7 +125,7 @@ class ProductJob(StormBase): @property def metadata(self): - return simplejson.loads(self._json_data) + return json.loads(self._json_data) def __init__(self, product, job_type, metadata): """Constructor. @@ -138,7 +138,7 @@ class ProductJob(StormBase): self.job = Job() self.product = product self.job_type = job_type - json_data = simplejson.dumps(metadata) + json_data = json.dumps(metadata) self._json_data = six.ensure_text(json_data) diff --git a/lib/lp/registry/model/sharingjob.py b/lib/lp/registry/model/sharingjob.py index bd9ba75..3dc412d 100644 --- a/lib/lp/registry/model/sharingjob.py +++ b/lib/lp/registry/model/sharingjob.py @@ -7,9 +7,9 @@ __all__ = [ "RemoveArtifactSubscriptionsJob", ] +import json import logging -import simplejson import six from lazr.delegates import delegate_to from lazr.enum import DBEnumeratedType, DBItem @@ -124,7 +124,7 @@ class SharingJob(StormBase): @property def metadata(self): - return simplejson.loads(self._json_data) + return json.loads(self._json_data) def __init__(self, job_type, pillar, grantee, metadata): """Constructor. @@ -134,7 +134,7 @@ class SharingJob(StormBase): dict. """ super().__init__() - json_data = simplejson.dumps(metadata) + json_data = json.dumps(metadata) self.job = Job() self.job_type = job_type self.grantee = grantee diff --git a/lib/lp/registry/stories/webservice/xx-distribution-mirror.rst b/lib/lp/registry/stories/webservice/xx-distribution-mirror.rst index 871d46b..057ac11 100644 --- a/lib/lp/registry/stories/webservice/xx-distribution-mirror.rst +++ b/lib/lp/registry/stories/webservice/xx-distribution-mirror.rst @@ -81,11 +81,11 @@ Security checks People who are not mirror listing admins or the mirrors registrar may not change the owner's of mirrors: + >>> import json >>> from zope.component import getUtility >>> from lp.testing.pages import webservice_for_person >>> from lp.services.webapp.interfaces import OAuthPermission >>> from lp.registry.interfaces.person import IPersonSet - >>> from simplejson import dumps >>> login(ANONYMOUS) >>> karl_db = getUtility(IPersonSet).getByName("karl") >>> test_db = getUtility(IPersonSet).getByName("name12") @@ -187,7 +187,9 @@ authorized. >>> karl = webservice.get("/~karl").jsonBody() >>> patch = {"owner_link": karl["self_link"]} >>> response = test_webservice.patch( - ... canonical_archive["self_link"], "application/json", dumps(patch) + ... canonical_archive["self_link"], + ... "application/json", + ... json.dumps(patch), ... ) >>> response.status 401 @@ -196,7 +198,9 @@ But if we use Karl, the mirror listing admin's, webservice, we can update the owner. >>> response = karl_webservice.patch( - ... canonical_archive["self_link"], "application/json", dumps(patch) + ... canonical_archive["self_link"], + ... "application/json", + ... json.dumps(patch), ... ) >>> response.status 209 @@ -216,7 +220,9 @@ Some attributes are read-only via the API: ... "reviewer_link": karl["self_link"], ... } >>> response = karl_webservice.patch( - ... canonical_releases["self_link"], "application/json", dumps(patch) + ... canonical_releases["self_link"], + ... "application/json", + ... json.dumps(patch), ... ) >>> print(response) HTTP/1.1 400 Bad Request @@ -237,13 +243,17 @@ While others can be set with the appropriate authorization: ... "whiteboard": "This mirror is too shiny to be true", ... } >>> response = test_webservice.patch( - ... canonical_releases["self_link"], "application/json", dumps(patch) + ... canonical_releases["self_link"], + ... "application/json", + ... json.dumps(patch), ... ) >>> response.status 401 >>> response = karl_webservice.patch( - ... canonical_releases["self_link"], "application/json", dumps(patch) + ... canonical_releases["self_link"], + ... "application/json", + ... json.dumps(patch), ... ).jsonBody() >>> pprint_entry(response) base_url: 'http://releases.ubuntu.com/' diff --git a/lib/lp/registry/stories/webservice/xx-distribution.rst b/lib/lp/registry/stories/webservice/xx-distribution.rst index aa2b91e..79a1450 100644 --- a/lib/lp/registry/stories/webservice/xx-distribution.rst +++ b/lib/lp/registry/stories/webservice/xx-distribution.rst @@ -197,12 +197,12 @@ returning None if there isn't one. Prepare stuff. + >>> import json >>> from zope.component import getUtility >>> from lp.testing.pages import webservice_for_person >>> from lp.services.webapp.interfaces import OAuthPermission >>> from lp.registry.interfaces.distribution import IDistributionSet >>> from lp.registry.interfaces.person import IPersonSet - >>> from simplejson import dumps >>> login("ad...@canonical.com") >>> ubuntu_distro = getUtility(IDistributionSet).getByName("ubuntu") @@ -235,7 +235,7 @@ Mark new mirror as official and a country mirror. >>> response = karl_webservice.patch( ... antarctica_patch_target["self_link"], ... "application/json", - ... dumps(patch), + ... json.dumps(patch), ... ) >>> antarctica = webservice.get("/+countries/AQ").jsonBody() diff --git a/lib/lp/registry/stories/webservice/xx-person.rst b/lib/lp/registry/stories/webservice/xx-person.rst index 93bcbba..1249d3e 100644 --- a/lib/lp/registry/stories/webservice/xx-person.rst +++ b/lib/lp/registry/stories/webservice/xx-person.rst @@ -576,10 +576,10 @@ to, obviously. Wiki names can be modified. - >>> from simplejson import dumps + >>> import json >>> patch = {"wiki": "http://www.example.com/", "wikiname": "MrExample"} >>> response = webservice.patch( - ... wiki_name["self_link"], "application/json", dumps(patch) + ... wiki_name["self_link"], "application/json", json.dumps(patch) ... ) >>> wiki_name = sorted(webservice.get(wikis_link).jsonBody()["entries"])[ ... 0 @@ -592,7 +592,7 @@ escaped as if it was HTML. >>> patch = {"wiki": "javascript:void/**/", "wikiname": "MrExample"} >>> response = webservice.patch( - ... wiki_name["self_link"], "application/json", dumps(patch) + ... wiki_name["self_link"], "application/json", json.dumps(patch) ... ) >>> print(response) HTTP/1.1 400 Bad Request @@ -969,12 +969,9 @@ Restrictions A team can't be its own owner. - >>> import simplejson >>> doc = {"team_owner_link": webservice.getAbsoluteUrl("/~admins")} >>> print( - ... webservice.patch( - ... "/~admins", "application/json", simplejson.dumps(doc) - ... ) + ... webservice.patch("/~admins", "application/json", json.dumps(doc)) ... ) HTTP/1.1 400 Bad Request ... diff --git a/lib/lp/registry/stories/webservice/xx-private-team.rst b/lib/lp/registry/stories/webservice/xx-private-team.rst index 43e1e16..b69f5c1 100644 --- a/lib/lp/registry/stories/webservice/xx-private-team.rst +++ b/lib/lp/registry/stories/webservice/xx-private-team.rst @@ -130,13 +130,11 @@ Create a webservice object for commercial-admins. A commercial admin may change the visibility. There is no helper method to do it, but it can be changed via a patch. - >>> import simplejson + >>> import json >>> def modify_team(team, representation, method, service): ... "A helper function to send a PUT or PATCH request to a team." ... headers = {"Content-type": "application/json"} - ... return service( - ... team, method, simplejson.dumps(representation), headers - ... ) + ... return service(team, method, json.dumps(representation), headers) ... >>> print( diff --git a/lib/lp/registry/stories/webservice/xx-project-registry.rst b/lib/lp/registry/stories/webservice/xx-project-registry.rst index 4ab95f4..796f357 100644 --- a/lib/lp/registry/stories/webservice/xx-project-registry.rst +++ b/lib/lp/registry/stories/webservice/xx-project-registry.rst @@ -371,7 +371,7 @@ development_focus_link. Attributes can be edited via the webservice.patch() method. - >>> from simplejson import dumps + >>> import json >>> patch = { ... "driver_link": webservice.getAbsoluteUrl("/~mark"), ... "homepage_url": "http://sf.net/firefox", @@ -380,7 +380,11 @@ Attributes can be edited via the webservice.patch() method. ... "/bugs/bugtrackers/mozilla.org" ... ), ... } - >>> print(webservice.patch("/firefox", "application/json", dumps(patch))) + >>> print( + ... webservice.patch( + ... "/firefox", "application/json", json.dumps(patch) + ... ) + ... ) HTTP/1.1 209 Content Returned ... @@ -434,7 +438,7 @@ changed as well. ... } >>> print( ... webservice.patch( - ... "/test-project", "application/json", dumps(patch) + ... "/test-project", "application/json", json.dumps(patch) ... ) ... ) HTTP/1.1 209 Content Returned @@ -450,7 +454,11 @@ webservice.patch() method. >>> patch = { ... "registrant_link": webservice.getAbsoluteUrl("/~mark"), ... } - >>> print(webservice.patch("/firefox", "application/json", dumps(patch))) + >>> print( + ... webservice.patch( + ... "/firefox", "application/json", json.dumps(patch) + ... ) + ... ) HTTP/1.1 400 Bad Request ... registrant_link: You tried to modify a read-only attribute. @@ -463,7 +471,11 @@ Similarly the date_created attribute cannot be modified. >>> original_date_created = firefox["date_created"] >>> patch = {"date_created": "2000-01-01T01:01:01+00:00Z"} - >>> print(webservice.patch("/firefox", "application/json", dumps(patch))) + >>> print( + ... webservice.patch( + ... "/firefox", "application/json", json.dumps(patch) + ... ) + ... ) HTTP/1.1 400 Bad Request ... date_created: You tried to modify a read-only attribute. @@ -478,7 +490,7 @@ hierarchy of series, milestones, and releases. >>> patch = {"status": "Obsolete"} >>> print( ... webservice.patch( - ... "/firefox/trunk", "application/json", dumps(patch) + ... "/firefox/trunk", "application/json", json.dumps(patch) ... ) ... ) HTTP/1.1 209 Content Returned... diff --git a/lib/lp/scripts/garbo.py b/lib/lp/scripts/garbo.py index 348b7a7..3b974a8 100644 --- a/lib/lp/scripts/garbo.py +++ b/lib/lp/scripts/garbo.py @@ -11,6 +11,7 @@ __all__ = [ "save_garbo_job_state", ] +import json import logging import multiprocessing import os @@ -20,7 +21,6 @@ from datetime import datetime, timedelta import iso8601 import pytz -import simplejson import six import transaction from contrib.glock import GlobalLock, LockAlreadyAcquired @@ -168,14 +168,14 @@ def load_garbo_job_state(job_name): .get_one() ) if job_data: - return simplejson.loads(job_data[0]) + return json.loads(job_data[0]) return None def save_garbo_job_state(job_name, job_data): # Save the json state data for the given job name. store = IMasterStore(Person) - json_data = simplejson.dumps(job_data, ensure_ascii=False) + json_data = json.dumps(job_data, ensure_ascii=False) result = store.execute( "UPDATE GarboJobState SET json_data = ? WHERE name = ?", params=(json_data, six.ensure_text(job_name)), diff --git a/lib/lp/services/oauth/browser/__init__.py b/lib/lp/services/oauth/browser/__init__.py index 0b70209..2a67f72 100644 --- a/lib/lp/services/oauth/browser/__init__.py +++ b/lib/lp/services/oauth/browser/__init__.py @@ -8,10 +8,10 @@ __all__ = [ "lookup_oauth_context", ] +import json from datetime import datetime, timedelta import pytz -import simplejson from lazr.restful import HTTPResource from zope.component import getUtility from zope.formlib.form import Action, Actions, expandPrefix @@ -58,7 +58,7 @@ class JSONTokenMixin: ] structure["access_levels"] = access_levels self.request.response.setHeader("Content-Type", HTTPResource.JSON_TYPE) - return simplejson.dumps(structure) + return json.dumps(structure) class OAuthRequestTokenView(LaunchpadFormView, JSONTokenMixin): diff --git a/lib/lp/services/oauth/stories/authorize-token.rst b/lib/lp/services/oauth/stories/authorize-token.rst index f71bd0b..39ce2bc 100644 --- a/lib/lp/services/oauth/stories/authorize-token.rst +++ b/lib/lp/services/oauth/stories/authorize-token.rst @@ -158,7 +158,7 @@ by the user is restricted to things related to that context. A client other than a web browser may request a JSON representation of the list of authentication levels. - >>> import simplejson + >>> import json >>> from lp.testing.pages import setupBrowser >>> json_browser = setupBrowser() @@ -169,7 +169,7 @@ the list of authentication levels. >>> json_browser.open( ... "http://launchpad.test/+authorize-token?%s" % urlencode(params) ... ) - >>> json_token = simplejson.loads(json_browser.contents) + >>> json_token = json.loads(json_browser.contents.decode()) >>> sorted(json_token.keys()) ['access_levels', 'oauth_token', 'oauth_token_consumer'] @@ -190,7 +190,7 @@ the list of authentication levels. ... ) ... % urlencode(params) ... ) - >>> json_token = simplejson.loads(json_browser.contents) + >>> json_token = json.loads(json_browser.contents.decode()) >>> sorted( ... (level["value"], level["title"]) ... for level in json_token["access_levels"] diff --git a/lib/lp/services/oauth/stories/request-token.rst b/lib/lp/services/oauth/stories/request-token.rst index 1496f13..2bc110a 100644 --- a/lib/lp/services/oauth/stories/request-token.rst +++ b/lib/lp/services/oauth/stories/request-token.rst @@ -22,7 +22,7 @@ The consumer can ask for a JSON representation of the request token, which will also include information about the available permission levels. - >>> import simplejson + >>> import json >>> from lp.testing.pages import setupBrowser >>> json_browser = setupBrowser() @@ -30,7 +30,7 @@ levels. >>> json_browser.open( ... "http://launchpad.test/+request-token", data=urlencode(data) ... ) - >>> token = simplejson.loads(json_browser.contents) + >>> token = json.loads(json_browser.contents.decode()) >>> sorted(token.keys()) ['access_levels', 'oauth_token', 'oauth_token_consumer', 'oauth_token_secret'] diff --git a/lib/lp/services/webapp/batching.py b/lib/lp/services/webapp/batching.py index 61fbc6f..35eb313 100644 --- a/lib/lp/services/webapp/batching.py +++ b/lib/lp/services/webapp/batching.py @@ -1,13 +1,13 @@ # Copyright 2011 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). +import json import re from collections.abc import Sequence from datetime import datetime from functools import reduce import lazr.batchnavigator -import simplejson from iso8601 import ParseError, parse_date from lazr.batchnavigator.interfaces import IRangeFactory from storm import Undef @@ -194,7 +194,7 @@ class TableBatchNavigator(BatchNavigator): self.show_column[column_to_show] = True -class DateTimeJSONEncoder(simplejson.JSONEncoder): +class DateTimeJSONEncoder(json.JSONEncoder): """A JSON encoder that understands datetime objects. Datetime objects are formatted according to ISO 1601. @@ -203,7 +203,7 @@ class DateTimeJSONEncoder(simplejson.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() - return simplejson.JSONEncoder.default(self, obj) + return json.JSONEncoder.default(self, obj) class ShadowedList: @@ -381,8 +381,8 @@ class StormRangeFactory: lower = self.getOrderValuesFor(plain_slice[0]) upper = self.getOrderValuesFor(plain_slice[batch.trueSize - 1]) return ( - simplejson.dumps(lower, cls=DateTimeJSONEncoder), - simplejson.dumps(upper, cls=DateTimeJSONEncoder), + json.dumps(lower, cls=DateTimeJSONEncoder), + json.dumps(upper, cls=DateTimeJSONEncoder), ) def reportError(self, message): @@ -404,8 +404,8 @@ class StormRangeFactory: if memo == "": return None try: - parsed_memo = simplejson.loads(memo) - except simplejson.JSONDecodeError: + parsed_memo = json.loads(memo) + except json.JSONDecodeError: self.reportError("memo is not a valid JSON string.") return None if not isinstance(parsed_memo, list): @@ -429,7 +429,7 @@ class StormRangeFactory: except TypeError as error: # A TypeError is raised when the type of value cannot # be used for expression. All expected types are - # properly created by simplejson.loads() above, except + # properly created by json.loads() above, except # time stamps which are represented as strings in # ISO format. If value is a string and if it can be # converted into a datetime object, we have a valid diff --git a/lib/lp/services/webapp/tests/test_batching.py b/lib/lp/services/webapp/tests/test_batching.py index fdbf3f0..bc461db 100644 --- a/lib/lp/services/webapp/tests/test_batching.py +++ b/lib/lp/services/webapp/tests/test_batching.py @@ -1,10 +1,10 @@ # Copyright 2011-2018 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). +import json from datetime import datetime import pytz -import simplejson from lazr.batchnavigator.interfaces import IRangeFactory from storm.expr import Desc, compile from storm.store import EmptyResultSet @@ -177,12 +177,12 @@ class TestStormRangeFactory(TestCaseWithFactory): # where the value is represented in the ISO time format. self.assertEqual( '"2011-07-25T00:00:00"', - simplejson.dumps(datetime(2011, 7, 25), cls=DateTimeJSONEncoder), + json.dumps(datetime(2011, 7, 25), cls=DateTimeJSONEncoder), ) # DateTimeJSONEncoder works for the regular Python types that can # represented as JSON strings. - encoded = simplejson.dumps( + encoded = json.dumps( ("foo", 1, 2.0, [3, 4], {5: "bar"}, datetime(2011, 7, 24)), cls=DateTimeJSONEncoder, ) @@ -199,16 +199,16 @@ class TestStormRangeFactory(TestCaseWithFactory): range_factory = StormRangeFactory(resultset) memo_value = range_factory.getOrderValuesFor(resultset[0]) request = LaunchpadTestRequest( - QUERY_STRING="memo=%s" % simplejson.dumps(memo_value) + QUERY_STRING="memo=%s" % json.dumps(memo_value) ) batchnav = BatchNavigator( resultset, request, size=3, range_factory=range_factory ) first, last = range_factory.getEndpointMemos(batchnav.batch) - expected_first = simplejson.dumps( + expected_first = json.dumps( [resultset[1].name], cls=DateTimeJSONEncoder ) - expected_last = simplejson.dumps( + expected_last = json.dumps( [resultset[3].name], cls=DateTimeJSONEncoder ) self.assertEqual(expected_first, first) @@ -225,11 +225,11 @@ class TestStormRangeFactory(TestCaseWithFactory): resultset, request, size=3, range_factory=range_factory ) first, last = range_factory.getEndpointMemos(batchnav.batch) - expected_first = simplejson.dumps( + expected_first = json.dumps( [resultset.get_plain_result_set()[0][1].id], cls=DateTimeJSONEncoder, ) - expected_last = simplejson.dumps( + expected_last = json.dumps( [resultset.get_plain_result_set()[2][1].id], cls=DateTimeJSONEncoder, ) @@ -260,7 +260,7 @@ class TestStormRangeFactory(TestCaseWithFactory): # parseMemo() accepts only JSON representations of lists. resultset = self.makeStormResultSet() range_factory = StormRangeFactory(resultset, self.logError) - self.assertIs(None, range_factory.parseMemo(simplejson.dumps(1))) + self.assertIs(None, range_factory.parseMemo(json.dumps(1))) self.assertEqual( ["memo must be the JSON representation of a list."], self.error_messages, @@ -273,7 +273,7 @@ class TestStormRangeFactory(TestCaseWithFactory): resultset = self.makeStormResultSet() resultset.order_by(Person.name, Person.id) range_factory = StormRangeFactory(resultset, self.logError) - self.assertIs(None, range_factory.parseMemo(simplejson.dumps([1]))) + self.assertIs(None, range_factory.parseMemo(json.dumps([1]))) expected_message = ( "Invalid number of elements in memo string. Expected: 2, got: 1" ) @@ -286,7 +286,7 @@ class TestStormRangeFactory(TestCaseWithFactory): resultset.order_by(Person.datecreated, Person.name, Person.id) range_factory = StormRangeFactory(resultset, self.logError) invalid_memo = [datetime(2011, 7, 25, 11, 30, 30, 45), "foo", "bar"] - json_data = simplejson.dumps(invalid_memo, cls=DateTimeJSONEncoder) + json_data = json.dumps(invalid_memo, cls=DateTimeJSONEncoder) self.assertIs(None, range_factory.parseMemo(json_data)) self.assertEqual(["Invalid parameter: 'bar'"], self.error_messages) @@ -300,7 +300,7 @@ class TestStormRangeFactory(TestCaseWithFactory): "foo", 1, ] - json_data = simplejson.dumps(valid_memo, cls=DateTimeJSONEncoder) + json_data = json.dumps(valid_memo, cls=DateTimeJSONEncoder) self.assertEqual(valid_memo, range_factory.parseMemo(json_data)) self.assertEqual(0, len(self.error_messages)) @@ -392,7 +392,7 @@ class TestStormRangeFactory(TestCaseWithFactory): resultset = self.makeStormResultSet() resultset.order_by(Desc(Person.id)) range_factory = StormRangeFactory(resultset, self.logError) - self.assertEqual([1], range_factory.parseMemo(simplejson.dumps([1]))) + self.assertEqual([1], range_factory.parseMemo(json.dumps([1]))) def test_reverseSortOrder(self): # reverseSortOrder() wraps a plain PropertyColumn instance into @@ -584,7 +584,7 @@ class TestStormRangeFactory(TestCaseWithFactory): resultset = self.makeStormResultSet() resultset.order_by(Person.name, Person.id) all_results = list(resultset) - memo = simplejson.dumps([all_results[0].name, all_results[0].id]) + memo = json.dumps([all_results[0].name, all_results[0].id]) range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3, memo) self.assertEqual(all_results[1:4], list(sliced_result)) @@ -605,7 +605,7 @@ class TestStormRangeFactory(TestCaseWithFactory): all_results = list(resultset) expected = all_results[1:4] expected.reverse() - memo = simplejson.dumps([all_results[4].name, all_results[4].id]) + memo = json.dumps([all_results[4].name, all_results[4].id]) range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3, memo, forwards=False) self.assertEqual(expected, list(sliced_result)) @@ -646,9 +646,7 @@ class TestStormRangeFactory(TestCaseWithFactory): # for a forward batch, the slice of this btach starts with # the fourth row of the entire result set. memo_lfa = all_results[2].libraryfile - memo = simplejson.dumps( - [memo_lfa.mimetype, memo_lfa.filename, memo_lfa.id] - ) + memo = json.dumps([memo_lfa.mimetype, memo_lfa.filename, memo_lfa.id]) range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3, memo) self.assertEqual(all_results[3:6], list(sliced_result)) @@ -658,7 +656,7 @@ class TestStormRangeFactory(TestCaseWithFactory): resultset.order_by(LibraryFileAlias.id) all_results = list(resultset) plain_results = list(resultset.get_plain_result_set()) - memo = simplejson.dumps([resultset.get_plain_result_set()[0][1].id]) + memo = json.dumps([resultset.get_plain_result_set()[0][1].id]) range_factory = StormRangeFactory(resultset) sliced_result = range_factory.getSlice(3, memo) self.assertEqual(all_results[1:4], list(sliced_result)) @@ -677,7 +675,7 @@ class TestStormRangeFactory(TestCaseWithFactory): resultset = self.makeStormResultSet() resultset.order_by(Person.id) all_results = list(resultset) - memo = simplejson.dumps([all_results[2].id]) + memo = json.dumps([all_results[2].id]) range_factory = StormRangeFactory(resultset) backward_slice = range_factory.getSlice( size=2, endpoint_memo=memo, forwards=False diff --git a/lib/lp/services/webapp/tests/test_view_model.py b/lib/lp/services/webapp/tests/test_view_model.py index 15ce566..f7cc65a 100644 --- a/lib/lp/services/webapp/tests/test_view_model.py +++ b/lib/lp/services/webapp/tests/test_view_model.py @@ -3,9 +3,10 @@ """Tests for the user requested oops using ++oops++ traversal.""" +import json + from lazr.restful.interfaces import IJSONRequestCache from lazr.restful.utils import get_current_browser_request -from simplejson import loads from testtools.matchers import KeysEqual from zope.configuration import xmlconfig @@ -123,7 +124,7 @@ class TestJsonModelView(BrowserTestCase): lp.services.webapp.tests.ProductModelTestView = ProductModelTestView self.configZCML() browser = self.getUserBrowser(self.url) - cache = loads(browser.contents) + cache = json.loads(browser.contents.decode()) self.assertThat(cache, KeysEqual("related_features", "context")) def test_JsonModel_custom_cache(self): @@ -140,7 +141,7 @@ class TestJsonModelView(BrowserTestCase): lp.services.webapp.tests.ProductModelTestView = ProductModelTestView self.configZCML() browser = self.getUserBrowser(self.url) - cache = loads(browser.contents) + cache = json.loads(browser.contents.decode()) self.assertThat( cache, KeysEqual("related_features", "context", "target_info") ) @@ -165,7 +166,7 @@ class TestJsonModelView(BrowserTestCase): lp.services.webapp.tests.ProductModelTestView = ProductModelTestView self.configZCML() browser = self.getUserBrowser(self.url) - cache = loads(browser.contents) + cache = json.loads(browser.contents.decode()) self.assertThat( cache, KeysEqual("related_features", "context", "target_info") ) diff --git a/lib/lp/services/webservice/stories/conditional-write.rst b/lib/lp/services/webservice/stories/conditional-write.rst index 2ee3197..6e25c81 100644 --- a/lib/lp/services/webservice/stories/conditional-write.rst +++ b/lib/lp/services/webservice/stories/conditional-write.rst @@ -70,8 +70,8 @@ modify directly. So long as the second part of the submitted ETag matches, a conditional write will succeed. - >>> import simplejson - >>> data = simplejson.dumps({"title": "New title"}) + >>> import json + >>> data = json.dumps({"title": "New title"}) >>> headers = {"If-Match": old_etag} >>> print( ... webservice.patch(url, "application/json", data, headers=headers) diff --git a/lib/lp/services/webservice/stories/xx-service.rst b/lib/lp/services/webservice/stories/xx-service.rst index 11eef09..8e655d5 100644 --- a/lib/lp/services/webservice/stories/xx-service.rst +++ b/lib/lp/services/webservice/stories/xx-service.rst @@ -86,8 +86,8 @@ Anonymous requests can't access certain data. Anonymous requests can't change the dataset. - >>> import simplejson - >>> data = simplejson.dumps({"display_name": "This won't work"}) + >>> import json + >>> data = json.dumps({"display_name": "This won't work"}) >>> response = anon_webservice.patch( ... root + "/~salgado", "application/json", data ... ) diff --git a/lib/lp/soyuz/model/packagediffjob.py b/lib/lp/soyuz/model/packagediffjob.py index b673c35..f4c20ac 100644 --- a/lib/lp/soyuz/model/packagediffjob.py +++ b/lib/lp/soyuz/model/packagediffjob.py @@ -5,7 +5,8 @@ __all__ = [ "PackageDiffJob", ] -import simplejson +import json + from lazr.delegates import delegate_to from zope.component import getUtility from zope.interface import implementer, provider @@ -38,7 +39,7 @@ class PackageDiffJobDerived(BaseRunnableJob, metaclass=EnumeratedSubclass): job = Job( base_job_type=JobType.GENERATE_PACKAGE_DIFF, requester=packagediff.requester, - base_json_data=simplejson.dumps({"packagediff": packagediff.id}), + base_json_data=json.dumps({"packagediff": packagediff.id}), ) derived = cls(job) derived.celeryRunOnCommit() @@ -75,7 +76,7 @@ class PackageDiffJob(PackageDiffJobDerived): @property def packagediff_id(self): - return simplejson.loads(self.base_json_data)["packagediff"] + return json.loads(self.base_json_data)["packagediff"] @property def packagediff(self): diff --git a/lib/lp/soyuz/stories/webservice/xx-archive.rst b/lib/lp/soyuz/stories/webservice/xx-archive.rst index 0510710..9ea0d65 100644 --- a/lib/lp/soyuz/stories/webservice/xx-archive.rst +++ b/lib/lp/soyuz/stories/webservice/xx-archive.rst @@ -1391,14 +1391,11 @@ Non-virtualized archives Modifying the require_virtualized flag through the API is not allowed except for admins, commercial admins, and PPA admins. - >>> import simplejson + >>> import json >>> def modify_archive(service, archive): ... headers = {"Content-type": "application/json"} ... return service( - ... archive["self_link"], - ... "PUT", - ... simplejson.dumps(archive), - ... headers, + ... archive["self_link"], "PUT", json.dumps(archive), headers ... ) ... diff --git a/lib/lp/soyuz/stories/webservice/xx-archivedependency.rst b/lib/lp/soyuz/stories/webservice/xx-archivedependency.rst index defcf2f..46b5c5e 100644 --- a/lib/lp/soyuz/stories/webservice/xx-archivedependency.rst +++ b/lib/lp/soyuz/stories/webservice/xx-archivedependency.rst @@ -12,7 +12,7 @@ We'll use Celso's PPA, and give it a custom dependency on the primary archive, and then create a private PPA for Celso with a similar custom dependency. - >>> import simplejson + >>> import json >>> from zope.component import getUtility >>> from lp.registry.interfaces.person import IPersonSet >>> from lp.registry.interfaces.pocket import PackagePublishingPocket @@ -95,7 +95,7 @@ But even he can't write to a dependency. ... cprov_webservice.patch( ... "/~cprov/+archive/ubuntu/ppa/+dependency/1", ... "application/json", - ... simplejson.dumps({"archive_link": mark_ppa["self_link"]}), + ... json.dumps({"archive_link": mark_ppa["self_link"]}), ... ) ... ) HTTP/1.1 400 Bad Request @@ -107,7 +107,7 @@ But even he can't write to a dependency. ... cprov_webservice.patch( ... "/~cprov/+archive/ubuntu/ppa/+dependency/1", ... "application/json", - ... simplejson.dumps({"dependency_link": mark_ppa["self_link"]}), + ... json.dumps({"dependency_link": mark_ppa["self_link"]}), ... ) ... ) HTTP/1.1 400 Bad Request @@ -119,7 +119,7 @@ But even he can't write to a dependency. ... cprov_webservice.patch( ... "/~cprov/+archive/ubuntu/ppa/+dependency/1", ... "application/json", - ... simplejson.dumps({"pocket": "Security"}), + ... json.dumps({"pocket": "Security"}), ... ) ... ) HTTP/1.1 400 Bad Request diff --git a/lib/lp/soyuz/stories/webservice/xx-packageset.rst b/lib/lp/soyuz/stories/webservice/xx-packageset.rst index 4ea90ed..b9bb81a 100644 --- a/lib/lp/soyuz/stories/webservice/xx-packageset.rst +++ b/lib/lp/soyuz/stories/webservice/xx-packageset.rst @@ -113,7 +113,7 @@ Let's create another set. We can modify it, and even give it away. - >>> from simplejson import dumps + >>> import json >>> name16 = webservice.get("/~name16").jsonBody() >>> patch = { ... "name": "renamed", @@ -123,7 +123,7 @@ We can modify it, and even give it away. >>> response = webservice.patch( ... "/package-sets/ubuntu/hoary/shortlived", ... "application/json", - ... dumps(patch), + ... json.dumps(patch), ... ) >>> print(response) HTTP/1.1 301 Moved Permanently diff --git a/lib/lp/soyuz/tests/test_binarypackagebuild.py b/lib/lp/soyuz/tests/test_binarypackagebuild.py index 41791bd..ef513d3 100644 --- a/lib/lp/soyuz/tests/test_binarypackagebuild.py +++ b/lib/lp/soyuz/tests/test_binarypackagebuild.py @@ -3,11 +3,11 @@ """Test Build features.""" +import json from datetime import datetime, timedelta import pytz from pymacaroons import Macaroon -from simplejson import dumps from testtools.matchers import Equals, MatchesListwise, MatchesStructure from zope.component import getUtility from zope.publisher.xmlrpc import TestRequest @@ -647,7 +647,7 @@ class TestBinaryPackageBuildWebservice(TestCaseWithFactory): response = webservice.patch( entry["self_link"], "application/json", - dumps({"external_dependencies": "random"}), + json.dumps({"external_dependencies": "random"}), ) self.assertEqual(401, response.status) @@ -660,7 +660,7 @@ class TestBinaryPackageBuildWebservice(TestCaseWithFactory): response = self.webservice.patch( entry["self_link"], "application/json", - dumps({"external_dependencies": "random"}), + json.dumps({"external_dependencies": "random"}), ) self.assertEqual(401, response.status) @@ -678,7 +678,7 @@ class TestBinaryPackageBuildWebservice(TestCaseWithFactory): response = webservice.patch( entry["self_link"], "application/json", - dumps({"external_dependencies": "random"}), + json.dumps({"external_dependencies": "random"}), ) self.assertEqual(400, response.status) self.assertIn(b"Invalid external dependencies", response.body) @@ -698,7 +698,7 @@ class TestBinaryPackageBuildWebservice(TestCaseWithFactory): response = webservice.patch( entry["self_link"], "application/json", - dumps({"external_dependencies": dependencies}), + json.dumps({"external_dependencies": dependencies}), ) self.assertEqual(209, response.status) self.assertEqual( @@ -958,7 +958,7 @@ class TestCalculateScore(TestCaseWithFactory): response = webservice.patch( entry["self_link"], "application/json", - dumps(dict(relative_build_score=100)), + json.dumps(dict(relative_build_score=100)), ) self.assertEqual(401, response.status) new_entry = webservice.get(obj_url, api_version="devel").jsonBody() @@ -976,7 +976,7 @@ class TestCalculateScore(TestCaseWithFactory): response = webservice.patch( entry["self_link"], "application/json", - dumps(dict(relative_build_score=100)), + json.dumps(dict(relative_build_score=100)), ) self.assertEqual(209, response.status) self.assertEqual(100, response.jsonBody()["relative_build_score"]) diff --git a/lib/lp/testing/__init__.py b/lib/lp/testing/__init__.py index 4133a2a..4b78f9a 100644 --- a/lib/lp/testing/__init__.py +++ b/lib/lp/testing/__init__.py @@ -52,6 +52,7 @@ __all__ = [ ] import io +import json import logging import os import re @@ -72,7 +73,6 @@ import fixtures import lp_sitecustomize import oops_datedir_repo.serializer_rfc822 import pytz -import simplejson import six import subunit import testtools @@ -1544,7 +1544,7 @@ def extract_lp_cache(text): match = re.search(r"<script[^>]*>LP.cache = (\{.*\});</script>", text) if match is None: raise ValueError("No JSON cache found.") - return simplejson.loads(match.group(1)) + return json.loads(match.group(1)) def nonblocking_readline(instream, timeout): diff --git a/lib/lp/testing/tests/test_yuixhr.py b/lib/lp/testing/tests/test_yuixhr.py index eea4635..3e93964 100644 --- a/lib/lp/testing/tests/test_yuixhr.py +++ b/lib/lp/testing/tests/test_yuixhr.py @@ -3,6 +3,7 @@ """Tests for the lp.testing.yuixhr.""" +import json import os import re import sys @@ -10,7 +11,6 @@ import tempfile import types from shutil import rmtree -import simplejson import transaction from storm.exceptions import DisconnectionError from testtools.testcase import ExpectedException @@ -207,7 +207,7 @@ class TestYUITestFixtureController(TestCase): method="POST", ) content = view() - self.assertEqual({"hello": "world"}, simplejson.loads(content)) + self.assertEqual({"hello": "world"}, json.loads(content)) self.assertEqual( "application/json", view.request.response.getHeader("Content-Type") ) @@ -218,7 +218,7 @@ class TestYUITestFixtureController(TestCase): form={"action": "setup", "fixtures": "make_product"}, method="POST", ) - data = simplejson.loads(view()) + data = json.loads(view()) # The licenses is just an example. self.assertEqual(["GNU GPL v2"], data["product"]["licenses"]) @@ -228,7 +228,7 @@ class TestYUITestFixtureController(TestCase): form={"action": "setup", "fixtures": "make_product"}, method="POST", ) - data = simplejson.loads(view()) + data = json.loads(view()) self.assertEqual( "tag:launchpad.net:2008:redacted", data["product"]["project_reviewed"], @@ -240,7 +240,7 @@ class TestYUITestFixtureController(TestCase): form={"action": "setup", "fixtures": "naughty_make_product"}, method="POST", ) - data = simplejson.loads(view()) + data = json.loads(view()) self.assertEqual( "tag:launchpad.net:2008:redacted", data["product"]["project_reviewed"], @@ -252,7 +252,7 @@ class TestYUITestFixtureController(TestCase): form={"action": "setup", "fixtures": "make_product_loggedin"}, method="POST", ) - data = simplejson.loads(view()) + data = json.loads(view()) self.assertEqual(False, data["product"]["project_reviewed"]) def test_add_cleanup_decorator(self): @@ -288,7 +288,7 @@ class TestYUITestFixtureController(TestCase): form={ "action": "teardown", "fixtures": "baseline", - "data": simplejson.dumps({"bonjour": "monde"}), + "data": json.dumps({"bonjour": "monde"}), }, method="POST", ) @@ -317,7 +317,7 @@ class TestYUITestFixtureController(TestCase): # Committing the transaction makes sure that we are not just seeing # the effect of an abort, below. transaction.commit() - name = simplejson.loads(data)["product"]["name"] + name = json.loads(data)["product"]["name"] products = getUtility(IProductSet) # The new product exists after the setup. self.assertFalse(products.getByName(name) is None) @@ -350,7 +350,7 @@ class TestYUITestFixtureController(TestCase): form={ "action": "teardown", "fixtures": "baseline,second", - "data": simplejson.dumps({"bonjour": "monde"}), + "data": json.dumps({"bonjour": "monde"}), }, method="POST", ) diff --git a/lib/lp/translations/browser/hastranslationimports.py b/lib/lp/translations/browser/hastranslationimports.py index c86d1c3..c1f7623 100644 --- a/lib/lp/translations/browser/hastranslationimports.py +++ b/lib/lp/translations/browser/hastranslationimports.py @@ -8,9 +8,9 @@ __all__ = [ ] import datetime +import json import pytz -import simplejson from zope.browserpage import ViewPageTemplateFile from zope.component import getUtility from zope.formlib import form @@ -379,7 +379,7 @@ class HasTranslationImportsView(LaunchpadFormView): for entry in self.batchnav.batch: if check_permission("launchpad.Edit", entry): confs.append(self.generateChoiceConfForEntry(entry)) - return "var choice_confs = %s;" % simplejson.dumps(confs) + return "var choice_confs = %s;" % json.dumps(confs) def generateChoiceConfForEntry(self, entry): disabled_items = [ diff --git a/lib/lp/translations/stories/webservice/xx-translationfocus.rst b/lib/lp/translations/stories/webservice/xx-translationfocus.rst index fcd393a..110f6a1 100644 --- a/lib/lp/translations/stories/webservice/xx-translationfocus.rst +++ b/lib/lp/translations/stories/webservice/xx-translationfocus.rst @@ -15,12 +15,12 @@ outside the scope of this test. It's possible to set the translation focus through the API if you're an admin. The translation focus should be a project series. - >>> from simplejson import dumps + >>> import json >>> print( ... webservice.patch( ... evolution["self_link"], ... "application/json", - ... dumps( + ... json.dumps( ... { ... "translation_focus_link": evolution[ ... "development_focus_link" @@ -43,7 +43,7 @@ Unprivileged users cannot set the translation focus. ... user_webservice.patch( ... evolution["self_link"], ... "application/json", - ... dumps({"translation_focus_link": None}), + ... json.dumps({"translation_focus_link": None}), ... ) ... ) HTTP... 401 Unauthorized diff --git a/lib/lp/translations/stories/webservice/xx-translationimportqueue.rst b/lib/lp/translations/stories/webservice/xx-translationimportqueue.rst index 63339d0..a17875a 100644 --- a/lib/lp/translations/stories/webservice/xx-translationimportqueue.rst +++ b/lib/lp/translations/stories/webservice/xx-translationimportqueue.rst @@ -85,7 +85,7 @@ Entry fields Most of the fields in a translation import queue entry are immutable from the web service's point of view. - >>> from simplejson import dumps + >>> import json Path @@ -96,7 +96,9 @@ An entry's file path can be changed by the entry's owner or an admin. >>> first_entry = queue["entries"][0]["self_link"] >>> print( ... webservice.patch( - ... first_entry, "application/json", dumps({"path": "foo.pot"}) + ... first_entry, + ... "application/json", + ... json.dumps({"path": "foo.pot"}), ... ) ... ) HTTP/1.1 209 Content Returned @@ -110,7 +112,9 @@ A regular user is not allowed to make this change. >>> first_entry = queue["entries"][0]["self_link"] >>> print( ... user_webservice.patch( - ... first_entry, "application/json", dumps({"path": "bar.pot"}) + ... first_entry, + ... "application/json", + ... json.dumps({"path": "bar.pot"}), ... ) ... ) HTTP... Unauthorized @@ -125,7 +129,9 @@ For now, it is not possible to set an entry's status through the API. >>> first_entry = queue["entries"][0]["self_link"] >>> print( ... webservice.patch( - ... first_entry, "application/json", dumps({"status": "Approved"}) + ... first_entry, + ... "application/json", + ... json.dumps({"status": "Approved"}), ... ) ... ) HTTP... Bad Request diff --git a/requirements/launchpad.txt b/requirements/launchpad.txt index f1efa07..30b4cfe 100644 --- a/requirements/launchpad.txt +++ b/requirements/launchpad.txt @@ -156,7 +156,6 @@ service-identity==18.1.0 setproctitle==1.1.7 setuptools-git==1.2 setuptools-scm==3.4.3 -simplejson==3.8.2 soupmatchers==0.4 soupsieve==1.9 statsd==3.3.0 diff --git a/requirements/types.txt b/requirements/types.txt index 79b6d5e..752d982 100644 --- a/requirements/types.txt +++ b/requirements/types.txt @@ -4,5 +4,4 @@ types-beautifulsoup4==4.9.0 types-bleach==3.3.1 types-pytz==0.1.0 types-requests==0.1.13 -types-simplejson==0.1.0 types-six==0.1.9 diff --git a/setup.cfg b/setup.cfg index e69cbc5..a13cb50 100644 --- a/setup.cfg +++ b/setup.cfg @@ -100,7 +100,6 @@ install_requires = selenium setproctitle setuptools - simplejson six soupmatchers Sphinx
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp