Example #1
0
    def test_mailto_url_with_one_broken_link(self):
        """Test that the mailto: URLs are formed correctly when one broken link.

        """
        user = factories.User()
        config.authorized_users = [user["name"]]

        dataset = custom_factories.Dataset(
            maintainer_email="*****@*****.**")
        resource_1 = custom_factories.Resource(package_id=dataset["id"])
        resource_2 = custom_factories.Resource(package_id=dataset["id"])
        resource_3 = custom_factories.Resource(package_id=dataset["id"])
        custom_helpers.make_broken((resource_1, resource_2, resource_3), user)

        result = helpers.call_action(
            "ckanext_deadoralive_broken_links_by_email")

        assert len(result) == 1
        result = result[0]

        subject = "You have a dataset with broken links on CKAN"
        body = "This dataset contains a broken link:%0A%0A{title}%0A{url}"
        url = "http://test.ckan.net/dataset/" + dataset["name"]
        body = body.format(title=dataset["title"], url=url)
        expected_mailto = "mailto:{email}?subject={subject}&body={body}".format(
            email=dataset["maintainer_email"], subject=subject, body=body)
        assert result["mailto"] == expected_mailto
    def test_mailto_url_with_one_broken_link(self):
        """Test that the mailto: URLs are formed correctly when one broken link.

        """
        user = factories.User()
        config.authorized_users = [user["name"]]

        dataset = custom_factories.Dataset(
            maintainer_email="*****@*****.**")
        resource_1 = custom_factories.Resource(package_id=dataset["id"])
        resource_2 = custom_factories.Resource(package_id=dataset["id"])
        resource_3 = custom_factories.Resource(package_id=dataset["id"])
        custom_helpers.make_broken((resource_1, resource_2, resource_3), user)

        result = helpers.call_action(
            "ckanext_deadoralive_broken_links_by_email")

        assert len(result) == 1
        result = result[0]

        subject = "You have a dataset with broken links on CKAN"
        body = "This dataset contains a broken link:%0A%0A{title}%0A{url}"
        url = "http://test.ckan.net/dataset/" + dataset["name"]
        body = body.format(title=dataset["title"], url=url)
        expected_mailto = "mailto:{email}?subject={subject}&body={body}".format(
            email=dataset["maintainer_email"], subject=subject, body=body)
        assert result["mailto"] == expected_mailto
    def test_mix_of_broken_and_working_links(self):
        user = factories.User()
        config.authorized_users = [user["name"]]

        maintainer_1 = "*****@*****.**"
        dataset_1 = custom_factories.Dataset(
            maintainer_email=maintainer_1)
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"])
        dataset_2 = custom_factories.Dataset(
            maintainer_email=maintainer_1)
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"])

        maintainer_2 = "*****@*****.**"
        dataset_3 = custom_factories.Dataset(
            maintainer_email=maintainer_2)
        resource_3 = custom_factories.Resource(package_id=dataset_3["id"])
        resource_4 = custom_factories.Resource(package_id=dataset_3["id"])

        maintainer_3 = "*****@*****.**"
        dataset_4 = custom_factories.Dataset(
            maintainer_email=maintainer_3)
        resource_5 = custom_factories.Resource(package_id=dataset_4["id"])
        dataset_5 = custom_factories.Dataset(
            maintainer_email=maintainer_3)
        resource_6 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_7 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_8 = custom_factories.Resource(package_id=dataset_5["id"])

        custom_helpers.make_broken((resource_1, resource_3, resource_4,
                                    resource_5, resource_6, resource_7), user)
        custom_helpers.make_working((resource_2, resource_8), user)

        report = helpers.call_action(
            "ckanext_deadoralive_broken_links_by_email")

        assert len(report) == 3, ("There should be 3 emails listed in the "
                                  "report")

        assert [item["email"] for item in report] == [
            maintainer_3, maintainer_2, maintainer_1], (
                "The items should be sorted most broken datasets first")

        # Check that the num_broken_links for each item is correct.
        assert report[0]["num_broken_links"] == 3
        assert report[1]["num_broken_links"] == 2
        assert report[2]["num_broken_links"] == 1

        # Check maintainer_3's report in detail.
        maintainer_3_report = report[0]
        assert len(maintainer_3_report["datasets_with_broken_links"]) == 2
        broken_datasets = maintainer_3_report["datasets_with_broken_links"]
        assert [dataset["name"] for dataset in broken_datasets] == [
            dataset_5["name"], dataset_4["name"]]
        dataset_5_report = broken_datasets[0]
        assert dataset_5_report["num_broken_links"] == 2
        assert len(dataset_5_report["resources_with_broken_links"]) == 2
        assert [resource_id for resource_id
                in dataset_5_report["resources_with_broken_links"]] == [
                    resource_6["id"], resource_7["id"]]
Example #4
0
    def test_mix_of_broken_and_working_links(self):
        user = factories.User()
        config.authorized_users = [user["name"]]

        org_1 = factories.Organization()
        dataset_1 = custom_factories.Dataset(owner_org=org_1["id"])
        dataset_2 = custom_factories.Dataset(owner_org=org_1["id"])
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"])
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"])

        org_2 = factories.Organization()
        dataset_3 = custom_factories.Dataset(owner_org=org_2["id"])
        resource_3 = custom_factories.Resource(package_id=dataset_3["id"])
        resource_4 = custom_factories.Resource(package_id=dataset_3["id"])

        org_3 = factories.Organization()
        dataset_4 = custom_factories.Dataset(owner_org=org_3["id"])
        resource_5 = custom_factories.Resource(package_id=dataset_4["id"])
        dataset_5 = custom_factories.Dataset(owner_org=org_3["id"])
        resource_6 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_7 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_8 = custom_factories.Resource(package_id=dataset_5["id"])

        custom_helpers.make_broken((resource_1, resource_3, resource_4,
                                    resource_5, resource_6, resource_7),
                                   user)
        custom_helpers.make_working((resource_2, resource_8), user)

        report = helpers.call_action(
            "ckanext_deadoralive_broken_links_by_organization")

        assert len(report) == 3, ("There should be 3 organizations listed in "
                                  "the report")

        assert [org["name"] for org in report] == [
            org_3["name"], org_2["name"], org_1["name"]], (
                "The organizations should be sorted most broken datasets first")

        # Check that the num_broken_links for each org is correct.
        assert report[0]["num_broken_links"] == 3
        assert report[1]["num_broken_links"] == 2
        assert report[2]["num_broken_links"] == 1

        # Check org_3's report in detail.
        org_3_report = report[0]
        assert len(org_3_report["datasets_with_broken_links"]) == 2
        org_3_broken_datasets = org_3_report["datasets_with_broken_links"]
        assert [dataset["name"] for dataset in org_3_broken_datasets] == [
            dataset_5["name"], dataset_4["name"]]
        dataset_5_report = org_3_broken_datasets[0]
        assert dataset_5_report["num_broken_links"] == 2
        assert len(dataset_5_report["resources_with_broken_links"]) == 2
        assert [resource_id for resource_id
                in dataset_5_report["resources_with_broken_links"]] == [
                    resource_6["id"], resource_7["id"]]
    def test_mix_of_broken_and_working_links(self):
        user = factories.User()
        config.authorized_users = [user["name"]]

        org_1 = factories.Organization()
        dataset_1 = custom_factories.Dataset(owner_org=org_1["id"])
        dataset_2 = custom_factories.Dataset(owner_org=org_1["id"])
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"])
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"])

        org_2 = factories.Organization()
        dataset_3 = custom_factories.Dataset(owner_org=org_2["id"])
        resource_3 = custom_factories.Resource(package_id=dataset_3["id"])
        resource_4 = custom_factories.Resource(package_id=dataset_3["id"])

        org_3 = factories.Organization()
        dataset_4 = custom_factories.Dataset(owner_org=org_3["id"])
        resource_5 = custom_factories.Resource(package_id=dataset_4["id"])
        dataset_5 = custom_factories.Dataset(owner_org=org_3["id"])
        resource_6 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_7 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_8 = custom_factories.Resource(package_id=dataset_5["id"])

        custom_helpers.make_broken((resource_1, resource_3, resource_4,
                                    resource_5, resource_6, resource_7), user)
        custom_helpers.make_working((resource_2, resource_8), user)

        report = helpers.call_action(
            "ckanext_deadoralive_broken_links_by_organization")

        assert len(report) == 3, ("There should be 3 organizations listed in "
                                  "the report")

        assert [org["name"] for org in report] == [
            org_3["name"], org_2["name"], org_1["name"]
        ], ("The organizations should be sorted most broken datasets first")

        # Check that the num_broken_links for each org is correct.
        assert report[0]["num_broken_links"] == 3
        assert report[1]["num_broken_links"] == 2
        assert report[2]["num_broken_links"] == 1

        # Check org_3's report in detail.
        org_3_report = report[0]
        assert len(org_3_report["datasets_with_broken_links"]) == 2
        org_3_broken_datasets = org_3_report["datasets_with_broken_links"]
        assert [dataset["name"] for dataset in org_3_broken_datasets
                ] == [dataset_5["name"], dataset_4["name"]]
        dataset_5_report = org_3_broken_datasets[0]
        assert dataset_5_report["num_broken_links"] == 2
        assert len(dataset_5_report["resources_with_broken_links"]) == 2
        assert [
            resource_id
            for resource_id in dataset_5_report["resources_with_broken_links"]
        ] == [resource_6["id"], resource_7["id"]]
    def test_unicode(self):
        """Test that it doesn't crash with non-ASCII characters in the input."""
        user = factories.User()
        config.authorized_users = [user["name"]]
        org = factories.Organization(title=u'Test Örgänißation')
        dataset = custom_factories.Dataset(owner_org=org['id'],
                                           title=u'Test Dätaßet')
        resource = custom_factories.Resource(package_id=dataset["id"],
                                             name=u'Test Rëßource',
                                             url=u'http://bröken_link')
        custom_helpers.make_broken((resource, ), user)

        helpers.call_action("ckanext_deadoralive_broken_links_by_organization")
Example #7
0
    def test_unicode(self):
        """Test that it doesn't crash with non-ASCII characters in the input."""
        user = factories.User()
        config.authorized_users = [user["name"]]
        org = factories.Organization(title=u'Test Örgänißation')
        dataset = custom_factories.Dataset(owner_org=org['id'],
                                           title=u'Test Dätaßet')
        resource = custom_factories.Resource(package_id=dataset["id"],
                                             name=u'Test Rëßource',
                                             url=u'http://bröken_link')
        custom_helpers.make_broken((resource,), user)

        helpers.call_action("ckanext_deadoralive_broken_links_by_organization")
    def test_unicode(self):
        """Test that it doesn't crash on non-ASCII characters."""
        user = factories.User()
        config.authorized_users = [user["name"]]

        dataset = custom_factories.Dataset(
            title=u"ötåeåst", maintainer_email=u"ötåeå[email protected]",
            maintainer=u"ötåeåst_maintainer")

        resource = custom_factories.Resource(package_id=dataset["id"])
        custom_helpers.make_broken((resource,), user)

        helpers.call_action("ckanext_deadoralive_broken_links_by_email")
    def test_broken_links_by_organization_with_unicode(self):
        user = factories.User()
        config.authorized_users = [user["name"]]

        org = factories.Organization(title=u"Test Örganißation")
        dataset = custom_factories.Dataset(owner_org=org["id"],
                                           title=u"Test Dätaßet")
        resource = custom_factories.Resource(package_id=dataset["id"],
                                             name=u"Test Rëßource",
                                             url=u"http://bröken_link")

        custom_helpers.make_broken((resource,), user=user)

        self.app.get("/organization/broken_links")
Example #10
0
    def test_broken_links_by_organization_with_unicode(self):
        user = factories.User()
        config.authorized_users = [user["name"]]

        org = factories.Organization(title=u"Test Örganißation")
        dataset = custom_factories.Dataset(owner_org=org["id"],
                                           title=u"Test Dätaßet")
        resource = custom_factories.Resource(package_id=dataset["id"],
                                             name=u"Test Rëßource",
                                             url=u"http://bröken_link")

        custom_helpers.make_broken((resource, ), user=user)

        self.app.get("/organization/broken_links")
    def test_broken_links_by_email(self):
        sysadmin = custom_factories.Sysadmin()
        extra_environ = {'REMOTE_USER': str(sysadmin["name"])}

        maintainer_1 = "*****@*****.**"
        dataset_1 = custom_factories.Dataset(
            maintainer_email=maintainer_1)
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"])
        dataset_2 = custom_factories.Dataset(
            maintainer_email=maintainer_1)
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"])

        maintainer_2 = "*****@*****.**"
        dataset_3 = custom_factories.Dataset(
            maintainer_email=maintainer_2)
        resource_3 = custom_factories.Resource(package_id=dataset_3["id"])
        resource_4 = custom_factories.Resource(package_id=dataset_3["id"])

        maintainer_3 = "*****@*****.**"
        dataset_4 = custom_factories.Dataset(
            maintainer_email=maintainer_3)
        resource_5 = custom_factories.Resource(package_id=dataset_4["id"])
        dataset_5 = custom_factories.Dataset(
            maintainer_email=maintainer_3)
        resource_6 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_7 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_8 = custom_factories.Resource(package_id=dataset_5["id"])

        custom_helpers.make_broken((resource_1, resource_3, resource_4,
                                    resource_5, resource_6, resource_7),
                                   user=sysadmin)
        custom_helpers.make_working((resource_2, resource_8), user=sysadmin)

        response = self.app.get("/ckan-admin/broken_links",
                                extra_environ=extra_environ)

        assert maintainer_1 in response
        assert maintainer_2 in response
        assert maintainer_3 in response
        assert dataset_1["name"] in response
        assert dataset_2["name"] not in response
        assert dataset_3["name"] in response
        assert dataset_4["name"] in response
        assert dataset_5["name"] in response
    def test_broken_links_by_email_with_unicode(self):
        sysadmin = custom_factories.Sysadmin()
        extra_environ = {'REMOTE_USER': str(sysadmin["name"])}
        maintainer = u"Mäintainer"
        maintainer_email = u"mä[email protected]"
        author = u"Aüthör"
        author_email = u"aüthö[email protected]"
        dataset_1 = custom_factories.Dataset(
            title=u"Test Dätaßet", maintainer=maintainer,
            maintainer_email=maintainer_email)
        dataset_2 = custom_factories.Dataset(
            title=u"Test Dätaßet", author=author, author_email=author_email)
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"],
                                               name=u"Test Rësourße",
                                               url=u"http://bröken_link")
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"],
                                               name=u"Test Rësourße",
                                               url=u"http://bröken_link")

        custom_helpers.make_broken((resource_1, resource_2), user=sysadmin)

        self.app.get("/ckan-admin/broken_links", extra_environ=extra_environ)
Example #13
0
    def test_broken_links_by_email(self):
        sysadmin = custom_factories.Sysadmin()
        extra_environ = {'REMOTE_USER': str(sysadmin["name"])}

        maintainer_1 = "*****@*****.**"
        dataset_1 = custom_factories.Dataset(maintainer_email=maintainer_1)
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"])
        dataset_2 = custom_factories.Dataset(maintainer_email=maintainer_1)
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"])

        maintainer_2 = "*****@*****.**"
        dataset_3 = custom_factories.Dataset(maintainer_email=maintainer_2)
        resource_3 = custom_factories.Resource(package_id=dataset_3["id"])
        resource_4 = custom_factories.Resource(package_id=dataset_3["id"])

        maintainer_3 = "*****@*****.**"
        dataset_4 = custom_factories.Dataset(maintainer_email=maintainer_3)
        resource_5 = custom_factories.Resource(package_id=dataset_4["id"])
        dataset_5 = custom_factories.Dataset(maintainer_email=maintainer_3)
        resource_6 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_7 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_8 = custom_factories.Resource(package_id=dataset_5["id"])

        custom_helpers.make_broken((resource_1, resource_3, resource_4,
                                    resource_5, resource_6, resource_7),
                                   user=sysadmin)
        custom_helpers.make_working((resource_2, resource_8), user=sysadmin)

        response = self.app.get("/ckan-admin/broken_links",
                                extra_environ=extra_environ)

        assert maintainer_1 in response
        assert maintainer_2 in response
        assert maintainer_3 in response
        assert dataset_1["name"] in response
        assert dataset_2["name"] not in response
        assert dataset_3["name"] in response
        assert dataset_4["name"] in response
        assert dataset_5["name"] in response
Example #14
0
    def test_broken_links_by_organization(self):
        user = factories.User()
        config.authorized_users = [user["name"]]

        org_1 = factories.Organization()
        dataset_1 = custom_factories.Dataset(owner_org=org_1["id"])
        dataset_2 = custom_factories.Dataset(owner_org=org_1["id"])
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"])
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"])

        org_2 = factories.Organization()
        dataset_3 = custom_factories.Dataset(owner_org=org_2["id"])
        resource_3 = custom_factories.Resource(package_id=dataset_3["id"])
        resource_4 = custom_factories.Resource(package_id=dataset_3["id"])

        org_3 = factories.Organization()
        dataset_4 = custom_factories.Dataset(owner_org=org_3["id"])
        resource_5 = custom_factories.Resource(package_id=dataset_4["id"])
        dataset_5 = custom_factories.Dataset(owner_org=org_3["id"])
        resource_6 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_7 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_8 = custom_factories.Resource(package_id=dataset_5["id"])

        custom_helpers.make_broken((resource_1, resource_3, resource_4,
                                    resource_5, resource_6, resource_7),
                                   user=user)
        custom_helpers.make_working((resource_2, resource_8), user=user)

        response = self.app.get("/organization/broken_links")

        assert org_1["name"] in response
        assert org_2["name"] in response
        assert org_3["name"] in response
        assert dataset_1["name"] in response
        assert dataset_2["name"] not in response
        assert dataset_3["name"] in response
        assert dataset_4["name"] in response
        assert dataset_5["name"] in response
    def test_dataset_with_no_email(self):
        """Datasets with no email should get email: None.

        All datasets that have broken links but no maintainer or author email
        should be grouped into a single email: None dict in the report.

        """
        user = factories.User()
        config.authorized_users = [user["name"]]

        # Create 3 datasets with no authors or maintainers.
        dataset_1 = custom_factories.Dataset()
        dataset_2 = custom_factories.Dataset()
        dataset_3 = custom_factories.Dataset()

        # Each of the datasets needs to have a resource with a broken link,
        # so that they show up in the report.
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"])
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"])
        resource_3 = custom_factories.Resource(package_id=dataset_3["id"])
        custom_helpers.make_broken((resource_1, resource_2, resource_3),
                                   user=user)

        result = helpers.call_action(
            "ckanext_deadoralive_broken_links_by_email")

        assert len(result) == 1
        result = result[0]

        assert result["email"] is None
        assert result["num_broken_links"] == 3

        datasets = result["datasets_with_broken_links"]
        assert len(datasets) == 3

        dataset_names = [dataset["name"] for dataset in datasets]
        for dataset in (dataset_1, dataset_2, dataset_3):
            assert dataset["name"] in dataset_names
    def test_broken_links_by_organization(self):
        user = factories.User()
        config.authorized_users = [user["name"]]

        org_1 = factories.Organization()
        dataset_1 = custom_factories.Dataset(owner_org=org_1["id"])
        dataset_2 = custom_factories.Dataset(owner_org=org_1["id"])
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"])
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"])

        org_2 = factories.Organization()
        dataset_3 = custom_factories.Dataset(owner_org=org_2["id"])
        resource_3 = custom_factories.Resource(package_id=dataset_3["id"])
        resource_4 = custom_factories.Resource(package_id=dataset_3["id"])

        org_3 = factories.Organization()
        dataset_4 = custom_factories.Dataset(owner_org=org_3["id"])
        resource_5 = custom_factories.Resource(package_id=dataset_4["id"])
        dataset_5 = custom_factories.Dataset(owner_org=org_3["id"])
        resource_6 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_7 = custom_factories.Resource(package_id=dataset_5["id"])
        resource_8 = custom_factories.Resource(package_id=dataset_5["id"])

        custom_helpers.make_broken((resource_1, resource_3, resource_4,
                                    resource_5, resource_6, resource_7),
                                   user=user)
        custom_helpers.make_working((resource_2, resource_8), user=user)

        response = self.app.get("/organization/broken_links")

        assert org_1["name"] in response
        assert org_2["name"] in response
        assert org_3["name"] in response
        assert dataset_1["name"] in response
        assert dataset_2["name"] not in response
        assert dataset_3["name"] in response
        assert dataset_4["name"] in response
        assert dataset_5["name"] in response
Example #17
0
    def test_dataset_with_no_email(self):
        """Datasets with no email should get email: None.

        All datasets that have broken links but no maintainer or author email
        should be grouped into a single email: None dict in the report.

        """
        user = factories.User()
        config.authorized_users = [user["name"]]

        # Create 3 datasets with no authors or maintainers.
        dataset_1 = custom_factories.Dataset()
        dataset_2 = custom_factories.Dataset()
        dataset_3 = custom_factories.Dataset()

        # Each of the datasets needs to have a resource with a broken link,
        # so that they show up in the report.
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"])
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"])
        resource_3 = custom_factories.Resource(package_id=dataset_3["id"])
        custom_helpers.make_broken((resource_1, resource_2, resource_3),
                                   user=user)

        result = helpers.call_action(
            "ckanext_deadoralive_broken_links_by_email")

        assert len(result) == 1
        result = result[0]

        assert result["email"] is None
        assert result["num_broken_links"] == 3

        datasets = result["datasets_with_broken_links"]
        assert len(datasets) == 3

        dataset_names = [dataset["name"] for dataset in datasets]
        for dataset in (dataset_1, dataset_2, dataset_3):
            assert dataset["name"] in dataset_names
Example #18
0
    def test_broken_links_by_email_with_unicode(self):
        sysadmin = custom_factories.Sysadmin()
        extra_environ = {'REMOTE_USER': str(sysadmin["name"])}
        maintainer = u"Mäintainer"
        maintainer_email = u"mä[email protected]"
        author = u"Aüthör"
        author_email = u"aüthö[email protected]"
        dataset_1 = custom_factories.Dataset(title=u"Test Dätaßet",
                                             maintainer=maintainer,
                                             maintainer_email=maintainer_email)
        dataset_2 = custom_factories.Dataset(title=u"Test Dätaßet",
                                             author=author,
                                             author_email=author_email)
        resource_1 = custom_factories.Resource(package_id=dataset_1["id"],
                                               name=u"Test Rësourße",
                                               url=u"http://bröken_link")
        resource_2 = custom_factories.Resource(package_id=dataset_2["id"],
                                               name=u"Test Rësourße",
                                               url=u"http://bröken_link")

        custom_helpers.make_broken((resource_1, resource_2), user=sysadmin)

        self.app.get("/ckan-admin/broken_links", extra_environ=extra_environ)