diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 8b4950b0e..705875232 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9, '3.10', '3.11'] + python-version: ['3.10', '3.11', '3.12'] steps: - name: Checkout the repository @@ -26,7 +26,7 @@ jobs: - name: Run pre-checks run: | flake8 guillotina --config=setup.cfg - isort -c -rc guillotina/ + isort --check-only guillotina/ black --check --verbose guillotina # Job to run tests tests: @@ -34,7 +34,7 @@ jobs: strategy: matrix: - python-version: [3.8, 3.9, '3.10', '3.11'] + python-version: ['3.10', '3.11', '3.12'] database: ["DUMMY", "postgres", "cockroachdb"] db_schema: ["custom", "public"] exclude: diff --git a/.isort.cfg b/.isort.cfg index e4aa14023..ba909f814 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,5 +1,5 @@ [settings] -force_single_line=True -sections=THIRDPARTY,FIRSTPARTY,LOCALFOLDER,STDLIB -no_lines_before=LOCALFOLDER,THIRDPARTY,FIRSTPARTY,STDLIB -force_alphabetical_sort=True +profile=black +lines_after_imports=2 +line_length=110 +skip_glob=*.pyi diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..2bb39ab88 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,41 @@ +# AGENTS.md + +## Project Overview +- Purpose: Guillotina core framework and official contrib addons. +- Main stack: Python async API server (ASGI), PostgreSQL, optional Redis. +- Key paths: + - `guillotina/` core framework and contrib packages + - `guillotina/tests/` test suite + - `docs/source/` documentation + +## Development Commands +- Setup (local venv expected at repo root): + - `python3 -m venv .venv` + - `source .venv/bin/activate` + - `pip install -r requirements.txt` + - `pip install -r contrib-requirements.txt` + - `pip install -e '.[test]'` +- Run local server: + - `g` (uses `config.yaml` by default) +- Run tests: + - `.venv/bin/pytest guillotina/tests` + - Targeted: `.venv/bin/pytest guillotina/tests/` + +## Validation +- For contrib changes, run focused tests under the touched contrib test folder. +- For API/service changes, verify status codes and response payload contracts. +- Keep docs updated under `docs/source/contrib/` when adding contrib features. + +## Deployment Notes +- This repo is a framework/library; no direct client deployment from this repo by default. +- Build/release lifecycle should follow package versioning (`VERSION`, `CHANGELOG.rst`). + +## Constraints / Gotchas +- Keep compatibility with repository formatting (`black` line length 110). +- Avoid wrapper layers when task explicitly requires low-level protocol primitives. +- Never commit credentials or local environment files. + +## Task Closeout Notes +- Update `CHANGELOG.rst` for notable changes. +- Record branch name, commit hash, validation output, and task evidence in Ops Tracker. + diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c6d9d1ced..fca3beb22 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,17 @@ CHANGELOG 7.0.7 (unreleased) ------------------ +- REAKING CHANGE: Drop support for Python 3.8 and 3.9. Guillotina is + now tested and supported on Python 3.10, 3.11, and 3.12. + [nilbacardit26] +- Add `guillotina.contrib.mcp` with low-level MCP server integration + (`mcp.server.lowlevel`), tool registry utility, MCP services, + cache invalidation subscribers, and tests/docs coverage. +- Optimize MCP `list_children` tool to prefer catalog queries and + fallback to `async_items` when catalog is unavailable. +- Upgrade the pytest stack so the CI test environment stays compatible + with the optional MCP SDK and its AnyIO pytest plugin on Python 3.10+. + [finalchaz, nilbacardit26] - Docs: Update documentation and configuration settings - Chore: Update sphinx-guillotina-theme version to 1.0.9 [rboixaderg] diff --git a/conftest.py b/conftest.py new file mode 100644 index 000000000..2bfc72bdd --- /dev/null +++ b/conftest.py @@ -0,0 +1 @@ +pytest_plugins = ["guillotina.tests.fixtures", "pytest_docker_fixtures"] diff --git a/contrib-requirements.txt b/contrib-requirements.txt index b7260ebc9..0f720c6ee 100644 --- a/contrib-requirements.txt +++ b/contrib-requirements.txt @@ -3,16 +3,17 @@ html2text==2019.8.11 aiosmtplib<4.0.0; python_version <= "3.8" aiosmtplib>=4.0.0; python_version >= "3.9" pre-commit==1.18.2 -flake8==5.0.4 +flake8==6.1.0 codecov==2.1.13 mypy-zope==1.0.11 -black==22.3.0 -isort==4.3.21 +black==24.10.0 +isort==5.13.2 jinja2==2.11.3 MarkupSafe<2.1.0 pytz==2020.1 emcache==0.6.0; python_version < '3.10' pymemcache==3.4.0; python_version < '3.10' +mcp>=1.0.0; python_version >= '3.10' # Conditional Pillow versions pillow==10.4.0; python_version < '3.11' diff --git a/docs/source/contrib/index.rst b/docs/source/contrib/index.rst index 2af6a4ea0..89224182b 100644 --- a/docs/source/contrib/index.rst +++ b/docs/source/contrib/index.rst @@ -17,3 +17,4 @@ Contents: swagger mailer dbusers + mcp diff --git a/docs/source/contrib/mcp.md b/docs/source/contrib/mcp.md new file mode 100644 index 000000000..58cf16a2f --- /dev/null +++ b/docs/source/contrib/mcp.md @@ -0,0 +1,44 @@ +# MCP + +`guillotina.contrib.mcp` provides a low-level MCP integration layer built on +the official `mcp.server.lowlevel` primitives (no FastMCP wrapper). + +## Installation + +```bash +pip install "guillotina[mcp]" +``` + +## Configuration + +```yaml +applications: + - guillotina + - guillotina.contrib.mcp + +mcp: + enabled: true + server_name: guillotina-mcp + default_child_limit: 50 +``` + +## Runtime endpoints + +- `GET /@mcp`: registry metadata and registered tools. +- `GET /@mcp/tools`: tool list and schemas. +- `POST /@mcp/tools/invoke`: executes one tool with payload + `{ "tool": "", "arguments": { ... } }`. +- `GET /@mcp/server/status`: validates low-level SDK availability. + +## Built-in tools + +- `resolve_path`: resolve a path and return basic metadata. +- `list_children`: list child resources from a folder-like resource. +- `serialize_resource`: execute Guillotina serialization adapters. +- `notify_modified`: emit an `ObjectModifiedEvent`. + +The tool registry is implemented as a Guillotina utility and cache invalidation +is handled by subscribers on object add/modify/remove events. + +`list_children` prefers catalog-backed lookup when a catalog utility is +available and falls back to `async_items()` iteration when it is not. diff --git a/guillotina/__init__.py b/guillotina/__init__.py index 8cedb03a2..52f51f0f9 100644 --- a/guillotina/__init__.py +++ b/guillotina/__init__.py @@ -1,4 +1,10 @@ # load the patch before anything else. +import os +from importlib.metadata import PackageNotFoundError, version +from pathlib import Path + +from zope.interface import Interface # noqa + from guillotina import glogging from guillotina._cache import BEHAVIOR_CACHE # noqa from guillotina._cache import FACTORY_CACHE # noqa @@ -6,13 +12,16 @@ from guillotina._cache import SCHEMA_CACHE # noqa from guillotina._settings import app_settings # noqa from guillotina.i18n import default_message_factory as _ # noqa -from zope.interface import Interface # noqa -import os -import pkg_resources + +def _resolve_version(): + try: + return version("guillotina") + except PackageNotFoundError: + return (Path(__file__).resolve().parents[1] / "VERSION").read_text().strip() -__version__ = pkg_resources.get_distribution("guillotina").version +__version__ = _resolve_version() # create logging @@ -21,9 +30,10 @@ if os.environ.get("GDEBUG", "").lower() in ("true", "t", "1"): # pragma: no cover # patches for extra debugging.... - import asyncpg import time + import asyncpg + original_execute = asyncpg.connection.Connection._do_execute logger.warning("RUNNING IN DEBUG MODE") diff --git a/guillotina/_settings.py b/guillotina/_settings.py index be8b05a5b..5949d6297 100644 --- a/guillotina/_settings.py +++ b/guillotina/_settings.py @@ -1,11 +1,10 @@ -from guillotina import interfaces -from guillotina.db.uid import generate_uid -from typing import Any -from typing import Dict - import copy import pickle import string +from typing import Any, Dict + +from guillotina import interfaces +from guillotina.db.uid import generate_uid app_settings: Dict[str, Any] = { diff --git a/guillotina/addons.py b/guillotina/addons.py index 7a81d439d..c651c5d0d 100644 --- a/guillotina/addons.py +++ b/guillotina/addons.py @@ -1,10 +1,9 @@ +from zope.interface import implementer + from guillotina import task_vars from guillotina._settings import app_settings -from guillotina.interfaces import IAddOn -from guillotina.interfaces import IAddons -from guillotina.utils import apply_coroutine -from guillotina.utils import get_current_request -from zope.interface import implementer +from guillotina.interfaces import IAddOn, IAddons +from guillotina.utils import apply_coroutine, get_current_request @implementer(IAddOn) diff --git a/guillotina/annotations.py b/guillotina/annotations.py index 6db0257d9..3753b628f 100644 --- a/guillotina/annotations.py +++ b/guillotina/annotations.py @@ -1,16 +1,14 @@ +import logging from collections import UserDict + +from zope.interface import implementer + from guillotina import configure from guillotina.db.interfaces import ITransaction from guillotina.db.orm.base import BaseObject from guillotina.exceptions import TransactionNotFound -from guillotina.interfaces import IAnnotationData -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import IRegistry -from guillotina.interfaces import IResource +from guillotina.interfaces import IAnnotationData, IAnnotations, IRegistry, IResource from guillotina.transactions import get_transaction -from zope.interface import implementer - -import logging logger = logging.getLogger("guillotina") diff --git a/guillotina/api/__init__.py b/guillotina/api/__init__.py index a3a990c70..749d3bb08 100644 --- a/guillotina/api/__init__.py +++ b/guillotina/api/__init__.py @@ -1,4 +1,6 @@ # these imports are done to force loading services +from guillotina.json import definitions # noqa + from . import addons # noqa from . import aggregation # noqa from . import app # noqa @@ -16,4 +18,3 @@ from . import user # noqa from . import vocabularies # noqa from . import ws # noqa -from guillotina.json import definitions # noqa diff --git a/guillotina/api/addons.py b/guillotina/api/addons.py index 380237b60..871f5e419 100644 --- a/guillotina/api/addons.py +++ b/guillotina/api/addons.py @@ -1,10 +1,7 @@ -from guillotina import addons -from guillotina import configure -from guillotina import error_reasons +from guillotina import addons, configure, error_reasons from guillotina._settings import app_settings from guillotina.i18n import MessageFactory -from guillotina.interfaces import IAddons -from guillotina.interfaces import IContainer +from guillotina.interfaces import IAddons, IContainer from guillotina.response import ErrorResponse from guillotina.utils import get_registry diff --git a/guillotina/api/aggregation.py b/guillotina/api/aggregation.py index 21e4544b8..590d82fbe 100644 --- a/guillotina/api/aggregation.py +++ b/guillotina/api/aggregation.py @@ -1,8 +1,8 @@ from collections import Counter + from guillotina import configure from guillotina.component import query_utility -from guillotina.interfaces import ICatalogUtility -from guillotina.interfaces import IResource +from guillotina.interfaces import ICatalogUtility, IResource from guillotina.response import HTTPServiceUnavailable diff --git a/guillotina/api/app.py b/guillotina/api/app.py index af3cddd70..d05855aa8 100644 --- a/guillotina/api/app.py +++ b/guillotina/api/app.py @@ -1,9 +1,7 @@ -from guillotina import component -from guillotina import configure +from guillotina import component, configure from guillotina._settings import app_settings from guillotina.component import get_multi_adapter -from guillotina.interfaces import IApplication -from guillotina.interfaces import IResourceSerializeToJson +from guillotina.interfaces import IApplication, IResourceSerializeToJson from guillotina.utils import get_dotted_name diff --git a/guillotina/api/behaviors.py b/guillotina/api/behaviors.py index 15e25b80c..ade95f0d2 100644 --- a/guillotina/api/behaviors.py +++ b/guillotina/api/behaviors.py @@ -1,11 +1,7 @@ from guillotina import configure -from guillotina.component import get_multi_adapter -from guillotina.component import get_utilities_for -from guillotina.component import query_adapter +from guillotina.component import get_multi_adapter, get_utilities_for, query_adapter from guillotina.content import get_cached_factory -from guillotina.interfaces import IBehavior -from guillotina.interfaces import IResource -from guillotina.interfaces import ISchemaSerializeToJson +from guillotina.interfaces import IBehavior, IResource, ISchemaSerializeToJson from guillotina.response import Response from guillotina.utils import resolve_dotted_name @@ -50,7 +46,7 @@ async def default_patch(context, request): permission="guillotina.ModifyContent", name="@behaviors/{behavior}", summary="Remove behavior from resource", - parameters=[{"in": "path", "name": "key", "required": True, "schema": {"type": "string"}}], + parameters=[{"in": "path", "name": "behavior", "required": True, "schema": {"type": "string"}}], requestBody={ "required": True, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Behavior"}}}, diff --git a/guillotina/api/container.py b/guillotina/api/container.py index d9a32675e..5009b0657 100644 --- a/guillotina/api/container.py +++ b/guillotina/api/container.py @@ -1,32 +1,25 @@ -from guillotina import addons -from guillotina import app_settings -from guillotina import configure -from guillotina import error_reasons -from guillotina import task_vars +import posixpath +from typing import Optional + +from guillotina import addons, app_settings, configure, error_reasons, task_vars from guillotina.api import content from guillotina.api.service import Service -from guillotina.component import get_adapter -from guillotina.component import get_multi_adapter +from guillotina.component import get_adapter, get_multi_adapter from guillotina.content import create_content from guillotina.event import notify from guillotina.events import ObjectAddedEvent -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import IApplication -from guillotina.interfaces import IContainer -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IPrincipalRoleManager -from guillotina.interfaces import IResourceSerializeToJson +from guillotina.interfaces import ( + IAnnotations, + IApplication, + IContainer, + IDatabase, + IPrincipalRoleManager, + IResourceSerializeToJson, +) from guillotina.interfaces.content import IGetOwner from guillotina.registry import REGISTRY_DATA_KEY -from guillotina.response import ErrorResponse -from guillotina.response import HTTPConflict -from guillotina.response import HTTPNotFound -from guillotina.response import HTTPNotImplemented -from guillotina.response import Response +from guillotina.response import ErrorResponse, HTTPConflict, HTTPNotFound, HTTPNotImplemented, Response from guillotina.utils import get_authenticated_user_id -from typing import Optional - -import posixpath @configure.service( diff --git a/guillotina/api/content.py b/guillotina/api/content.py index 9a23c2960..23751f822 100644 --- a/guillotina/api/content.py +++ b/guillotina/api/content.py @@ -1,68 +1,70 @@ -from guillotina import configure -from guillotina import content -from guillotina import error_reasons -from guillotina import security +from guillotina import configure, content, error_reasons, security from guillotina._cache import FACTORY_CACHE from guillotina._settings import app_settings from guillotina.api.service import Service -from guillotina.component import get_adapter -from guillotina.component import get_multi_adapter -from guillotina.component import query_adapter -from guillotina.component import query_multi_adapter -from guillotina.content import create_content_in_container -from guillotina.content import get_all_behavior_interfaces -from guillotina.content import get_all_behaviors -from guillotina.content import get_cached_factory -from guillotina.directives import merged_tagged_value_dict -from guillotina.directives import read_permission +from guillotina.component import get_adapter, get_multi_adapter, query_adapter, query_multi_adapter +from guillotina.content import ( + create_content_in_container, + get_all_behavior_interfaces, + get_all_behaviors, + get_cached_factory, +) +from guillotina.directives import merged_tagged_value_dict, read_permission from guillotina.event import notify -from guillotina.events import BeforeObjectModifiedEvent -from guillotina.events import BeforeObjectRemovedEvent -from guillotina.events import ObjectAddedEvent -from guillotina.events import ObjectModifiedEvent -from guillotina.events import ObjectPermissionsViewEvent -from guillotina.events import ObjectRemovedEvent -from guillotina.events import ObjectVisitedEvent -from guillotina.exceptions import ComponentLookupError -from guillotina.exceptions import PreconditionFailed +from guillotina.events import ( + BeforeObjectModifiedEvent, + BeforeObjectRemovedEvent, + ObjectAddedEvent, + ObjectModifiedEvent, + ObjectPermissionsViewEvent, + ObjectRemovedEvent, + ObjectVisitedEvent, +) +from guillotina.exceptions import ComponentLookupError, PreconditionFailed from guillotina.i18n import default_message_factory as _ -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import IAsyncContainer -from guillotina.interfaces import IConstrainTypes -from guillotina.interfaces import IContainer -from guillotina.interfaces import IFieldValueRenderer -from guillotina.interfaces import IFolder -from guillotina.interfaces import IGetOwner -from guillotina.interfaces import IIDChecker -from guillotina.interfaces import IIDGenerator -from guillotina.interfaces import IPrincipalPermissionMap -from guillotina.interfaces import IPrincipalRoleManager -from guillotina.interfaces import IPrincipalRoleMap -from guillotina.interfaces import IResource -from guillotina.interfaces import IResourceDeserializeFromJson -from guillotina.interfaces import IResourceSerializeToJson -from guillotina.interfaces import IResourceSerializeToJsonSummary -from guillotina.interfaces import IResponse -from guillotina.interfaces import IRolePermissionMap +from guillotina.interfaces import ( + IAnnotations, + IAsyncContainer, + IConstrainTypes, + IContainer, + IFieldValueRenderer, + IFolder, + IGetOwner, + IIDChecker, + IIDGenerator, + IPrincipalPermissionMap, + IPrincipalRoleManager, + IPrincipalRoleMap, + IResource, + IResourceDeserializeFromJson, + IResourceSerializeToJson, + IResourceSerializeToJsonSummary, + IResponse, + IRolePermissionMap, +) from guillotina.json.utils import convert_interfaces_to_schema from guillotina.profile import profilable -from guillotina.response import ErrorResponse -from guillotina.response import HTTPMethodNotAllowed -from guillotina.response import HTTPMovedPermanently -from guillotina.response import HTTPNotFound -from guillotina.response import HTTPPreconditionFailed -from guillotina.response import HTTPUnauthorized -from guillotina.response import Response +from guillotina.response import ( + ErrorResponse, + HTTPMethodNotAllowed, + HTTPMovedPermanently, + HTTPNotFound, + HTTPPreconditionFailed, + HTTPUnauthorized, + Response, +) from guillotina.security.utils import apply_sharing from guillotina.transactions import get_transaction -from guillotina.utils import apply_coroutine -from guillotina.utils import get_authenticated_user_id -from guillotina.utils import get_behavior -from guillotina.utils import get_object_by_uid -from guillotina.utils import get_object_url -from guillotina.utils import get_security_policy -from guillotina.utils import iter_parents -from guillotina.utils import resolve_dotted_name +from guillotina.utils import ( + apply_coroutine, + get_authenticated_user_id, + get_behavior, + get_object_by_uid, + get_object_url, + get_security_policy, + iter_parents, + resolve_dotted_name, +) def get_content_json_schema_responses(content): @@ -606,6 +608,7 @@ async def move(context, request): "content": { "application/json": { "schema": { + "type": "object", "properties": { "destination": { "type": "string", @@ -613,7 +616,7 @@ async def move(context, request): }, "new_id": {"type": "string", "description": "Optional new id to assign object"}, "reset_acl": { - "type": "bool", + "type": "boolean", "description": "Remove users and roles from acl, except for the request user", "default": False, }, diff --git a/guillotina/api/files.py b/guillotina/api/files.py index a024867ec..3126b4fa7 100644 --- a/guillotina/api/files.py +++ b/guillotina/api/files.py @@ -1,21 +1,15 @@ +import mimetypes + from guillotina import configure from guillotina._settings import app_settings from guillotina.api.content import DefaultOPTIONS -from guillotina.api.service import DownloadService -from guillotina.api.service import TraversableFieldService +from guillotina.api.service import DownloadService, TraversableFieldService from guillotina.component import get_multi_adapter from guillotina.event import notify from guillotina.events import ObjectModifiedEvent from guillotina.exceptions import FileNotFoundException -from guillotina.interfaces import IAsyncBehavior -from guillotina.interfaces import IFileManager -from guillotina.interfaces import IResource -from guillotina.interfaces import IStaticDirectory -from guillotina.interfaces import IStaticFile -from guillotina.response import HTTPNotFound -from guillotina.response import Response - -import mimetypes +from guillotina.interfaces import IAsyncBehavior, IFileManager, IResource, IStaticDirectory, IStaticFile +from guillotina.response import HTTPNotFound, Response def _traversed_file_doc(summary, parameters=None, responses=None): @@ -42,14 +36,15 @@ def _traversed_file_doc(summary, parameters=None, responses=None): TUS_PARAMETERS = [ - {"name": "Upload-Offset", "in": "headers", "required": True, "schema": {"type": "integer"}}, - {"name": "UPLOAD-LENGTH", "in": "headers", "required": True, "schema": {"type": "integer"}}, - {"name": "UPLOAD-MD5", "in": "headers", "required": False, "schema": {"type": "string"}}, - {"name": "UPLOAD-EXTENSION", "in": "headers", "required": False, "schema": {"type": "string"}}, - {"name": "TUS-RESUMABLE", "in": "headers", "required": True, "schema": {"type": "string"}}, - {"name": "UPLOAD-METADATA", "in": "headers", "required": False, "schema": {"type": "string"}}, + {"name": "Upload-Offset", "in": "header", "required": True, "schema": {"type": "integer"}}, + {"name": "UPLOAD-LENGTH", "in": "header", "required": True, "schema": {"type": "integer"}}, + {"name": "UPLOAD-MD5", "in": "header", "required": False, "schema": {"type": "string"}}, + {"name": "UPLOAD-EXTENSION", "in": "header", "required": False, "schema": {"type": "string"}}, + {"name": "TUS-RESUMABLE", "in": "header", "required": True, "schema": {"type": "string"}}, + {"name": "UPLOAD-METADATA", "in": "header", "required": False, "schema": {"type": "string"}}, ] + # Static File @configure.service(context=IStaticFile, method="GET", permission="guillotina.AccessContent") class FileGET(DownloadService): @@ -316,8 +311,8 @@ async def __call__(self): **_traversed_file_doc( "TUS endpoint", parameters=[ - {"name": "Upload-Offset", "in": "headers", "required": True, "schema": {"type": "integer"}}, - {"name": "CONTENT-LENGTH", "in": "headers", "required": True, "schema": {"type": "integer"}}, + {"name": "Upload-Offset", "in": "header", "required": True, "schema": {"type": "integer"}}, + {"name": "CONTENT-LENGTH", "in": "header", "required": True, "schema": {"type": "integer"}}, ], responses={ "204": { @@ -335,8 +330,8 @@ async def __call__(self): **_traversed_file_doc( "TUS endpoint", parameters=[ - {"name": "Upload-Offset", "in": "headers", "required": True, "schema": {"type": "integer"}}, - {"name": "CONTENT-LENGTH", "in": "headers", "required": True, "schema": {"type": "integer"}}, + {"name": "Upload-Offset", "in": "header", "required": True, "schema": {"type": "integer"}}, + {"name": "CONTENT-LENGTH", "in": "header", "required": True, "schema": {"type": "integer"}}, {"name": "file_key", "in": "path", "required": True, "schema": {"type": "string"}}, ], responses={ diff --git a/guillotina/api/login.py b/guillotina/api/login.py index f0dbebdef..7fbc1319e 100644 --- a/guillotina/api/login.py +++ b/guillotina/api/login.py @@ -1,29 +1,21 @@ # -*- encoding: utf-8 -*- -from datetime import datetime -from datetime import timedelta -from guillotina import app_settings -from guillotina import configure +import json +from datetime import datetime, timedelta +from json.decoder import JSONDecodeError + +import jwt + +from guillotina import app_settings, configure from guillotina.api.service import Service from guillotina.auth import authenticate_user from guillotina.auth.recaptcha import RecaptchaValidator from guillotina.auth.utils import find_user -from guillotina.component import get_utility -from guillotina.component import query_utility +from guillotina.component import get_utility, query_utility from guillotina.event import notify -from guillotina.events import UserLogin -from guillotina.events import UserRefreshToken -from guillotina.interfaces import IApplication -from guillotina.interfaces import IAuthValidationUtility -from guillotina.interfaces import IContainer -from guillotina.interfaces import ISessionManagerUtility -from guillotina.response import HTTPNotAcceptable -from guillotina.response import HTTPPreconditionFailed -from guillotina.response import HTTPUnauthorized +from guillotina.events import UserLogin, UserRefreshToken +from guillotina.interfaces import IApplication, IAuthValidationUtility, IContainer, ISessionManagerUtility +from guillotina.response import HTTPNotAcceptable, HTTPPreconditionFailed, HTTPUnauthorized from guillotina.utils import get_authenticated_user -from json.decoder import JSONDecodeError - -import json -import jwt @configure.service( diff --git a/guillotina/api/metadata.py b/guillotina/api/metadata.py index 7de693791..a723954b0 100644 --- a/guillotina/api/metadata.py +++ b/guillotina/api/metadata.py @@ -1,11 +1,8 @@ from guillotina import configure from guillotina._cache import FACTORY_CACHE from guillotina.component import get_utilities_for -from guillotina.directives import index -from guillotina.directives import merged_tagged_value_dict -from guillotina.interfaces import IAbsoluteURL -from guillotina.interfaces import IBehavior -from guillotina.interfaces import IContainer +from guillotina.directives import index, merged_tagged_value_dict +from guillotina.interfaces import IAbsoluteURL, IBehavior, IContainer @configure.service( diff --git a/guillotina/api/registry.py b/guillotina/api/registry.py index b820f423b..4648488fd 100644 --- a/guillotina/api/registry.py +++ b/guillotina/api/registry.py @@ -5,17 +5,12 @@ from guillotina.events import RegistryEditedEvent from guillotina.exceptions import ComponentLookupError from guillotina.i18n import MessageFactory -from guillotina.interfaces import IContainer -from guillotina.interfaces import IJSONToValue +from guillotina.interfaces import IContainer, IJSONToValue from guillotina.json.serialize_value import json_compatible -from guillotina.response import ErrorResponse -from guillotina.response import HTTPNotFound -from guillotina.response import Response +from guillotina.response import ErrorResponse, HTTPNotFound, Response from guillotina.schema import get_fields from guillotina.schema.exceptions import ValidationError -from guillotina.utils import get_registry -from guillotina.utils import import_class -from guillotina.utils import resolve_dotted_name +from guillotina.utils import get_registry, import_class, resolve_dotted_name _ = MessageFactory("guillotina") @@ -149,7 +144,7 @@ async def __call__(self): name="@registry/{dotted_name}", summary="Update registry setting", validate=True, - parameters=[{"in": "path", "name": "dotter_name", "required": True, "schema": {"type": "string"}}], + parameters=[{"in": "path", "name": "dotted_name", "required": True, "schema": {"type": "string"}}], requestBody={ "required": True, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateRegistry"}}}, diff --git a/guillotina/api/search.py b/guillotina/api/search.py index 98fae926c..3748ed45e 100644 --- a/guillotina/api/search.py +++ b/guillotina/api/search.py @@ -1,13 +1,12 @@ +import logging + from guillotina import configure from guillotina.api.service import Service from guillotina.catalog.utils import reindex_in_future from guillotina.component import query_utility -from guillotina.interfaces import ICatalogUtility -from guillotina.interfaces import IResource +from guillotina.interfaces import ICatalogUtility, IResource from guillotina.response import HTTPServiceUnavailable -import logging - logger = logging.getLogger("guillotina") diff --git a/guillotina/api/service.py b/guillotina/api/service.py index b42f062a8..8d32042d4 100644 --- a/guillotina/api/service.py +++ b/guillotina/api/service.py @@ -1,3 +1,11 @@ +import json +from typing import Any +from typing import Dict as TDict +from typing import List as TList +from typing import Union + +import jsonschema + from guillotina import glogging from guillotina._cache import BEHAVIOR_CACHE from guillotina._settings import app_settings @@ -5,20 +13,10 @@ from guillotina.component import query_utility from guillotina.component.interfaces import IFactory from guillotina.fields import CloudFileField -from guillotina.interfaces import IAsyncBehavior -from guillotina.interfaces import ICloudFileField -from guillotina.response import HTTPNotFound -from guillotina.response import HTTPPreconditionFailed +from guillotina.interfaces import IAsyncBehavior, ICloudFileField +from guillotina.response import HTTPNotFound, HTTPPreconditionFailed from guillotina.schema.interfaces import IDict -from guillotina.utils import get_schema_validator -from guillotina.utils import JSONSchemaRefResolver -from typing import Any -from typing import Dict as TDict -from typing import List as TList -from typing import Union - -import json -import jsonschema +from guillotina.utils import JSONSchemaRefResolver, get_schema_validator logger = glogging.getLogger("guillotina") diff --git a/guillotina/api/storage.py b/guillotina/api/storage.py index 035269ba7..6052fa4ee 100644 --- a/guillotina/api/storage.py +++ b/guillotina/api/storage.py @@ -1,3 +1,5 @@ +import re + from guillotina import configure from guillotina._settings import app_settings from guillotina.component import get_adapter @@ -6,8 +8,6 @@ from guillotina.response import HTTPNotFound from guillotina.utils import list_or_dict_items -import re - @configure.service(context=IApplication, method="GET", permission="guillotina.GetDatabases", name="@storages") async def storages_get(context, request): diff --git a/guillotina/api/types.py b/guillotina/api/types.py index 161b70b4c..d17c0f1a4 100644 --- a/guillotina/api/types.py +++ b/guillotina/api/types.py @@ -1,11 +1,7 @@ from guillotina import configure from guillotina.api.service import Service -from guillotina.component import get_multi_adapter -from guillotina.component import get_utilities_for -from guillotina.component import query_utility -from guillotina.interfaces import IContainer -from guillotina.interfaces import IFactorySerializeToJson -from guillotina.interfaces import IResourceFactory +from guillotina.component import get_multi_adapter, get_utilities_for, query_utility +from guillotina.interfaces import IContainer, IFactorySerializeToJson, IResourceFactory from guillotina.response import HTTPNotFound diff --git a/guillotina/api/user.py b/guillotina/api/user.py index 842b633e7..d19edfa52 100644 --- a/guillotina/api/user.py +++ b/guillotina/api/user.py @@ -1,8 +1,6 @@ from guillotina import configure from guillotina.component import get_utility -from guillotina.interfaces import IApplication -from guillotina.interfaces import IContainer -from guillotina.interfaces import IGroups +from guillotina.interfaces import IApplication, IContainer, IGroups from guillotina.utils.auth import get_authenticated_user diff --git a/guillotina/api/vocabularies.py b/guillotina/api/vocabularies.py index b5405e390..627abffb5 100644 --- a/guillotina/api/vocabularies.py +++ b/guillotina/api/vocabularies.py @@ -1,10 +1,9 @@ +from os.path import join + from guillotina import configure -from guillotina.interfaces import IAbsoluteURL -from guillotina.interfaces import IResource +from guillotina.interfaces import IAbsoluteURL, IResource from guillotina.response import HTTPNotFound -from guillotina.schema.vocabulary import getVocabularyRegistry -from guillotina.schema.vocabulary import VocabularyRegistryError -from os.path import join +from guillotina.schema.vocabulary import VocabularyRegistryError, getVocabularyRegistry @configure.service( diff --git a/guillotina/api/ws.py b/guillotina/api/ws.py index c8f4c8747..771385710 100644 --- a/guillotina/api/ws.py +++ b/guillotina/api/ws.py @@ -1,27 +1,20 @@ -from guillotina import configure -from guillotina import logger -from guillotina import routes -from guillotina import task_vars +import time +from urllib import parse + +import orjson +from jwcrypto import jwe +from jwcrypto.common import json_encode + +from guillotina import configure, logger, routes, task_vars from guillotina._settings import app_settings from guillotina.api.service import Service from guillotina.auth.extractors import BasicAuthPolicy -from guillotina.component import get_utility -from guillotina.component import query_multi_adapter -from guillotina.interfaces import IApplication -from guillotina.interfaces import IContainer -from guillotina.interfaces import IPermission -from guillotina.interfaces import IResponse +from guillotina.component import get_utility, query_multi_adapter +from guillotina.interfaces import IApplication, IContainer, IPermission, IResponse from guillotina.request import WebSocketJsonDecodeError from guillotina.security.utils import get_view_permission from guillotina.transactions import get_tm -from guillotina.utils import get_jwk_key -from guillotina.utils import get_security_policy -from jwcrypto import jwe -from jwcrypto.common import json_encode -from urllib import parse - -import orjson -import time +from guillotina.utils import get_jwk_key, get_security_policy @configure.service( diff --git a/guillotina/asgi.py b/guillotina/asgi.py index bf3f142ec..88924cf85 100644 --- a/guillotina/asgi.py +++ b/guillotina/asgi.py @@ -1,23 +1,19 @@ -from guillotina import glogging -from guillotina import task_vars +import asyncio +import enum +import traceback +import uuid + +from guillotina import glogging, task_vars from guillotina.browser import View from guillotina.component import query_adapter from guillotina.exc_resp import HTTPConflict -from guillotina.exceptions import ConflictError -from guillotina.exceptions import TIDConflictError +from guillotina.exceptions import ConflictError, TIDConflictError from guillotina.interfaces import IErrorResponseException from guillotina.middlewares import ErrorsMiddleware from guillotina.request import Request from guillotina.response import Response -from guillotina.traversal import apply_cors -from guillotina.traversal import apply_rendering -from guillotina.utils import get_dotted_name -from guillotina.utils import resolve_dotted_name - -import asyncio -import enum -import traceback -import uuid +from guillotina.traversal import apply_cors, apply_rendering +from guillotina.utils import get_dotted_name, resolve_dotted_name logger = glogging.getLogger("guillotina") diff --git a/guillotina/async_util.py b/guillotina/async_util.py index a1dc6878b..1bf6d7b4c 100644 --- a/guillotina/async_util.py +++ b/guillotina/async_util.py @@ -1,20 +1,14 @@ -from guillotina import logger -from guillotina import task_vars +import asyncio +import typing + +from guillotina import logger, task_vars from guillotina.db.transaction import Status -from guillotina.exceptions import ServerClosingException -from guillotina.exceptions import TransactionNotFound +from guillotina.exceptions import ServerClosingException, TransactionNotFound from guillotina.interfaces import IAsyncJobPool # noqa from guillotina.interfaces import IAsyncUtility # noqa from guillotina.interfaces import IQueueUtility # noqa -from guillotina.transactions import get_tm -from guillotina.transactions import get_transaction -from guillotina.transactions import transaction -from guillotina.utils import dump_task_vars -from guillotina.utils import execute -from guillotina.utils import load_task_vars - -import asyncio -import typing +from guillotina.transactions import get_tm, get_transaction, transaction +from guillotina.utils import dump_task_vars, execute, load_task_vars class QueueUtility(object): diff --git a/guillotina/auth/extractors.py b/guillotina/auth/extractors.py index ab24d54c2..35880433c 100644 --- a/guillotina/auth/extractors.py +++ b/guillotina/auth/extractors.py @@ -1,11 +1,12 @@ -from guillotina.utils import get_jwk_key -from jwcrypto import jwe - import base64 import json import logging import time +from jwcrypto import jwe + +from guillotina.utils import get_jwk_key + logger = logging.getLogger("guillotina") diff --git a/guillotina/auth/groups.py b/guillotina/auth/groups.py index e01707455..0fe3c972f 100644 --- a/guillotina/auth/groups.py +++ b/guillotina/auth/groups.py @@ -1,11 +1,9 @@ -from guillotina import app_settings -from guillotina import configure -from guillotina.auth.users import GuillotinaUser -from guillotina.interfaces import IGroups -from guillotina.interfaces import IPrincipal - import typing +from guillotina import app_settings, configure +from guillotina.auth.users import GuillotinaUser +from guillotina.interfaces import IGroups, IPrincipal + class GuillotinaGroup(GuillotinaUser): def __init__(self, ident): diff --git a/guillotina/auth/recaptcha.py b/guillotina/auth/recaptcha.py index 0b76e7726..8b1f180ae 100644 --- a/guillotina/auth/recaptcha.py +++ b/guillotina/auth/recaptcha.py @@ -1,8 +1,8 @@ +import logging + from guillotina import app_settings from guillotina.utils import get_current_request -import logging - logger = logging.getLogger("guillotina") diff --git a/guillotina/auth/role.py b/guillotina/auth/role.py index 1c877b91f..01911f877 100644 --- a/guillotina/auth/role.py +++ b/guillotina/auth/role.py @@ -1,7 +1,8 @@ +from zope.interface import implementer + from guillotina._settings import app_settings from guillotina.component import get_utilities_for from guillotina.interfaces import IRole -from zope.interface import implementer @implementer(IRole) diff --git a/guillotina/auth/users.py b/guillotina/auth/users.py index b4fa65f37..ad571a325 100644 --- a/guillotina/auth/users.py +++ b/guillotina/auth/users.py @@ -1,8 +1,9 @@ -from guillotina.interfaces import Allow -from guillotina.interfaces import IPrincipal from typing import Optional + from zope.interface import implementer +from guillotina.interfaces import Allow, IPrincipal + ROOT_USER_ID = "root" ANONYMOUS_USER_ID = "Anonymous User" diff --git a/guillotina/auth/utils.py b/guillotina/auth/utils.py index 13f82a5d0..9f5da1135 100644 --- a/guillotina/auth/utils.py +++ b/guillotina/auth/utils.py @@ -1,17 +1,15 @@ -from datetime import datetime -from datetime import timedelta +from datetime import datetime, timedelta +from typing import Optional + +import jwt + from guillotina import task_vars from guillotina._settings import app_settings -from guillotina.auth.users import AnonymousUser -from guillotina.auth.users import ROOT_USER_ID +from guillotina.auth.users import ROOT_USER_ID, AnonymousUser from guillotina.component import get_utility -from guillotina.interfaces import IApplication -from guillotina.interfaces import IPrincipal +from guillotina.interfaces import IApplication, IPrincipal from guillotina.profile import profilable from guillotina.utils import get_security_policy -from typing import Optional - -import jwt @profilable diff --git a/guillotina/auth/validators.py b/guillotina/auth/validators.py index 8a4601b74..508c4dec6 100644 --- a/guillotina/auth/validators.py +++ b/guillotina/auth/validators.py @@ -1,23 +1,20 @@ +import asyncio +import hashlib +import logging +import uuid from concurrent.futures import ThreadPoolExecutor from functools import partial + +import argon2 +import jwt +from lru import LRU + from guillotina import configure from guillotina._settings import app_settings from guillotina.auth import find_user -from guillotina.component import get_utility -from guillotina.component import query_utility -from guillotina.interfaces import IApplication -from guillotina.interfaces import IPasswordChecker -from guillotina.interfaces import IPasswordHasher -from guillotina.interfaces import ISessionManagerUtility +from guillotina.component import get_utility, query_utility +from guillotina.interfaces import IApplication, IPasswordChecker, IPasswordHasher, ISessionManagerUtility from guillotina.utils import strings_differ -from lru import LRU - -import argon2 -import asyncio -import hashlib -import jwt -import logging -import uuid ph = argon2.PasswordHasher() diff --git a/guillotina/behaviors/__init__.py b/guillotina/behaviors/__init__.py index 2de1c0d15..02d3498fb 100644 --- a/guillotina/behaviors/__init__.py +++ b/guillotina/behaviors/__init__.py @@ -1,14 +1,13 @@ # so we can scan guillotina.behaviors and load behavior configuration +from zope.interface import alsoProvides, classImplements + +from guillotina.component import get_utilities_for, get_utility +from guillotina.interfaces import IBehavior, IResourceFactory +from guillotina.profile import profilable + from . import attachment # noqa from . import dublincore # noqa from . import dynamic # noqa -from guillotina.component import get_utilities_for -from guillotina.component import get_utility -from guillotina.interfaces import IBehavior -from guillotina.interfaces import IResourceFactory -from guillotina.profile import profilable -from zope.interface import alsoProvides -from zope.interface import classImplements def apply_concrete_behaviors(): diff --git a/guillotina/behaviors/attachment.py b/guillotina/behaviors/attachment.py index 25bff71db..b74086bb8 100644 --- a/guillotina/behaviors/attachment.py +++ b/guillotina/behaviors/attachment.py @@ -1,8 +1,8 @@ +from zope.interface import Interface + from guillotina import configure from guillotina.fields import CloudFileField -from guillotina.schema import Dict -from guillotina.schema import TextLine -from zope.interface import Interface +from guillotina.schema import Dict, TextLine class IAttachmentMarker(Interface): diff --git a/guillotina/behaviors/dublincore.py b/guillotina/behaviors/dublincore.py index 260656251..367c166e4 100644 --- a/guillotina/behaviors/dublincore.py +++ b/guillotina/behaviors/dublincore.py @@ -1,12 +1,13 @@ from datetime import datetime + from dateutil.tz import tzutc -from guillotina import configure -from guillotina import schema +from zope.interface import Interface + +from guillotina import configure, schema from guillotina.behaviors.instance import AnnotationBehavior from guillotina.behaviors.properties import ContextProperty from guillotina.directives import index_field from guillotina.fields.patch import PatchField -from zope.interface import Interface _utc = tzutc() diff --git a/guillotina/behaviors/dynamic.py b/guillotina/behaviors/dynamic.py index 40f509142..21b20ee84 100644 --- a/guillotina/behaviors/dynamic.py +++ b/guillotina/behaviors/dynamic.py @@ -1,11 +1,9 @@ -from guillotina import configure -from guillotina import fields -from guillotina import schema -from guillotina.behaviors.instance import AnnotationBehavior -from guillotina.behaviors.instance import ContextBehavior -from guillotina.interfaces import IContentBehavior from zope.interface import Interface +from guillotina import configure, fields, schema +from guillotina.behaviors.instance import AnnotationBehavior, ContextBehavior +from guillotina.interfaces import IContentBehavior + def get_all_fields(content): _fields = {} diff --git a/guillotina/behaviors/instance.py b/guillotina/behaviors/instance.py index dd3e0d60f..1e04b279a 100644 --- a/guillotina/behaviors/instance.py +++ b/guillotina/behaviors/instance.py @@ -1,12 +1,11 @@ -from guillotina.annotations import AnnotationData -from guillotina.interfaces import IAnnotationData -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import IAsyncBehavior -from guillotina.interfaces import IContentBehavior -from guillotina.schema.utils import get_default_from_schema from typing import Tuple + from zope.interface import implementer +from guillotina.annotations import AnnotationData +from guillotina.interfaces import IAnnotationData, IAnnotations, IAsyncBehavior, IContentBehavior +from guillotina.schema.utils import get_default_from_schema + _default = object() diff --git a/guillotina/blob.py b/guillotina/blob.py index 4b51d1b3d..95a7652da 100644 --- a/guillotina/blob.py +++ b/guillotina/blob.py @@ -1,9 +1,9 @@ +from io import BytesIO +from typing import AsyncIterator, Union + from guillotina._settings import app_settings from guillotina.exceptions import BlobChunkNotFound from guillotina.transactions import get_transaction -from io import BytesIO -from typing import AsyncIterator -from typing import Union class Blob: diff --git a/guillotina/browser.py b/guillotina/browser.py index 30ed9d797..785be6b85 100644 --- a/guillotina/browser.py +++ b/guillotina/browser.py @@ -1,15 +1,10 @@ -from guillotina import configure -from guillotina import task_vars -from guillotina.component import adapter -from guillotina.interfaces import IAbsoluteURL -from guillotina.interfaces import ILocation -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResource -from guillotina.interfaces import IView -from guillotina.utils import get_current_request -from guillotina.utils import get_url from zope.interface import implementer +from guillotina import configure, task_vars +from guillotina.component import adapter +from guillotina.interfaces import IAbsoluteURL, ILocation, IRequest, IResource, IView +from guillotina.utils import get_current_request, get_url + def get_physical_path(context): parts = [context.__name__] diff --git a/guillotina/catalog/__init__.py b/guillotina/catalog/__init__.py index d16b471a5..a3d98a47c 100644 --- a/guillotina/catalog/__init__.py +++ b/guillotina/catalog/__init__.py @@ -2,10 +2,8 @@ from guillotina import directives from guillotina.interfaces import IResource from guillotina.security.security_code import role_permission_manager -from guillotina.security.utils import get_principals_with_access_content -from guillotina.security.utils import get_roles_with_access_content -from guillotina.utils import get_content_depth -from guillotina.utils import get_content_path +from guillotina.security.utils import get_principals_with_access_content, get_roles_with_access_content +from guillotina.utils import get_content_depth, get_content_path global_roles_for_permission = role_permission_manager.get_roles_for_permission diff --git a/guillotina/catalog/catalog.py b/guillotina/catalog/catalog.py index c19d4d946..f97d3175d 100644 --- a/guillotina/catalog/catalog.py +++ b/guillotina/catalog/catalog.py @@ -1,31 +1,27 @@ +import logging +import typing + +from zope.interface import implementer + from guillotina import configure from guillotina.catalog.utils import parse_query from guillotina.component import query_adapter from guillotina.content import iter_schemata from guillotina.db.orm.interfaces import IBaseObject -from guillotina.directives import index -from guillotina.directives import merged_tagged_value_dict -from guillotina.directives import merged_tagged_value_list -from guillotina.directives import metadata +from guillotina.directives import index, merged_tagged_value_dict, merged_tagged_value_list, metadata from guillotina.exceptions import NoIndexField -from guillotina.interfaces import IAsyncBehavior -from guillotina.interfaces import ICatalogDataAdapter -from guillotina.interfaces import ICatalogUtility -from guillotina.interfaces import IContainer -from guillotina.interfaces import IResource -from guillotina.interfaces import ISecurityInfo +from guillotina.interfaces import ( + IAsyncBehavior, + ICatalogDataAdapter, + ICatalogUtility, + IContainer, + IResource, + ISecurityInfo, +) from guillotina.json.serialize_value import json_compatible -from guillotina.security.security_code import principal_permission_manager -from guillotina.security.security_code import role_permission_manager -from guillotina.security.utils import get_principals_with_access_content -from guillotina.security.utils import get_roles_with_access_content -from guillotina.utils import apply_coroutine -from guillotina.utils import get_content_depth -from guillotina.utils import get_content_path -from zope.interface import implementer - -import logging -import typing +from guillotina.security.security_code import principal_permission_manager, role_permission_manager +from guillotina.security.utils import get_principals_with_access_content, get_roles_with_access_content +from guillotina.utils import apply_coroutine, get_content_depth, get_content_path logger = logging.getLogger("guillotina") diff --git a/guillotina/catalog/index.py b/guillotina/catalog/index.py index f21e0bdd8..f0597caf6 100644 --- a/guillotina/catalog/index.py +++ b/guillotina/catalog/index.py @@ -1,21 +1,20 @@ from guillotina import configure from guillotina._settings import app_settings from guillotina.catalog.utils import reindex_in_future -from guillotina.component import query_adapter -from guillotina.component import query_utility -from guillotina.interfaces import ICatalogUtility -from guillotina.interfaces import IContainer -from guillotina.interfaces import IGroupFolder -from guillotina.interfaces import IObjectAddedEvent -from guillotina.interfaces import IObjectModifiedEvent -from guillotina.interfaces import IObjectMovedEvent -from guillotina.interfaces import IObjectPermissionsModifiedEvent -from guillotina.interfaces import IObjectRemovedEvent -from guillotina.interfaces import IResource -from guillotina.interfaces import ISecurityInfo -from guillotina.utils import apply_coroutine -from guillotina.utils import execute -from guillotina.utils import find_container +from guillotina.component import query_adapter, query_utility +from guillotina.interfaces import ( + ICatalogUtility, + IContainer, + IGroupFolder, + IObjectAddedEvent, + IObjectModifiedEvent, + IObjectMovedEvent, + IObjectPermissionsModifiedEvent, + IObjectRemovedEvent, + IResource, + ISecurityInfo, +) +from guillotina.utils import apply_coroutine, execute, find_container class Indexer: diff --git a/guillotina/catalog/parser.py b/guillotina/catalog/parser.py index 6462c72ca..a348435b7 100644 --- a/guillotina/catalog/parser.py +++ b/guillotina/catalog/parser.py @@ -1,10 +1,9 @@ +import typing + from guillotina import app_settings from guillotina.catalog.types import BasicParsedQueryInfo from guillotina.catalog.utils import iter_indexes -from guillotina.utils import get_content_depth -from guillotina.utils import get_content_path - -import typing +from guillotina.utils import get_content_depth, get_content_path def to_list(value: typing.Union[str, list]) -> typing.List[str]: diff --git a/guillotina/catalog/types.py b/guillotina/catalog/types.py index a2ffad62e..9a924beb8 100644 --- a/guillotina/catalog/types.py +++ b/guillotina/catalog/types.py @@ -1,7 +1,7 @@ -from mypy_extensions import TypedDict - import typing +from mypy_extensions import TypedDict + class BasicParsedQueryInfo(TypedDict): sort_on: typing.Optional[str] diff --git a/guillotina/catalog/utils.py b/guillotina/catalog/utils.py index 83ce298da..31e6e35f1 100644 --- a/guillotina/catalog/utils.py +++ b/guillotina/catalog/utils.py @@ -1,23 +1,15 @@ +import logging +import typing + from guillotina import app_settings from guillotina.catalog.types import BasicParsedQueryInfo -from guillotina.component import get_utilities_for -from guillotina.component import get_utility -from guillotina.component import query_multi_adapter -from guillotina.component import query_utility -from guillotina.content import get_all_possible_schemas_for_type -from guillotina.content import IResourceFactory -from guillotina.directives import index_field -from guillotina.directives import merged_tagged_value_dict -from guillotina.directives import merged_tagged_value_list -from guillotina.directives import metadata -from guillotina.interfaces import ICatalogUtility -from guillotina.interfaces import ISearchParser +from guillotina.component import get_utilities_for, get_utility, query_multi_adapter, query_utility +from guillotina.content import IResourceFactory, get_all_possible_schemas_for_type +from guillotina.directives import index_field, merged_tagged_value_dict, merged_tagged_value_list, metadata +from guillotina.interfaces import ICatalogUtility, ISearchParser from guillotina.transactions import transaction from guillotina.utils import execute -import logging -import typing - logger = logging.getLogger("guillotina") diff --git a/guillotina/commands/__init__.py b/guillotina/commands/__init__.py index 8cdb84253..313860327 100644 --- a/guillotina/commands/__init__.py +++ b/guillotina/commands/__init__.py @@ -1,19 +1,18 @@ -from fnmatch import fnmatch -from guillotina import logger -from guillotina import profile -from guillotina._settings import app_settings -from guillotina.factory import make_app -from guillotina.utils import get_dotted_name -from guillotina.utils import resolve_dotted_name - import argparse import asyncio import cProfile import json import os import sys +from fnmatch import fnmatch + import yaml +from guillotina import logger, profile +from guillotina._settings import app_settings +from guillotina.factory import make_app +from guillotina.utils import get_dotted_name, resolve_dotted_name + try: import uvloop # type: ignore diff --git a/guillotina/commands/create.py b/guillotina/commands/create.py index 60cffe14a..180778370 100644 --- a/guillotina/commands/create.py +++ b/guillotina/commands/create.py @@ -1,9 +1,9 @@ -from guillotina.commands import Command - -import guillotina import os import sys +import guillotina +from guillotina.commands import Command + class CreateCommand(Command): description = "Guillotina server runner" diff --git a/guillotina/commands/crypto.py b/guillotina/commands/crypto.py index 1808ac486..2f77e71f8 100644 --- a/guillotina/commands/crypto.py +++ b/guillotina/commands/crypto.py @@ -1,7 +1,8 @@ -from guillotina.commands import Command +import logging + from jwcrypto import jwk -import logging +from guillotina.commands import Command logger = logging.getLogger("guillotina") diff --git a/guillotina/commands/initialize_db.py b/guillotina/commands/initialize_db.py index b7080fa91..58e8dc761 100644 --- a/guillotina/commands/initialize_db.py +++ b/guillotina/commands/initialize_db.py @@ -1,7 +1,6 @@ from guillotina.commands import Command from guillotina.component import get_utility -from guillotina.interfaces import IApplication -from guillotina.interfaces import IDatabase +from guillotina.interfaces import IApplication, IDatabase class DatabaseInitializationCommand(Command): diff --git a/guillotina/commands/migrate.py b/guillotina/commands/migrate.py index 505ba79b8..0d0285347 100644 --- a/guillotina/commands/migrate.py +++ b/guillotina/commands/migrate.py @@ -1,12 +1,12 @@ +import logging from distutils.version import StrictVersion + from guillotina.commands import Command from guillotina.component import get_utilities_for from guillotina.interfaces import IMigration from guillotina.transactions import transaction from guillotina.utils import iter_databases -import logging - logger = logging.getLogger("guillotina") diff --git a/guillotina/commands/run.py b/guillotina/commands/run.py index 22145bbe6..7976abdc0 100644 --- a/guillotina/commands/run.py +++ b/guillotina/commands/run.py @@ -1,13 +1,11 @@ -from guillotina.commands import Command -from guillotina.utils import get_containers -from guillotina.utils import iter_databases -from guillotina.utils import lazy_apply - import importlib.util import inspect import logging import os +from guillotina.commands import Command +from guillotina.utils import get_containers, iter_databases, lazy_apply + logger = logging.getLogger("guillotina") diff --git a/guillotina/commands/serve_reload.py b/guillotina/commands/serve_reload.py index fa5760e28..cc32428e3 100644 --- a/guillotina/commands/serve_reload.py +++ b/guillotina/commands/serve_reload.py @@ -1,11 +1,11 @@ -from guillotina.asgi import AsgiApp -from guillotina.commands import Command -from guillotina.traversal import TraversalRouter - import os import subprocess import sys +from guillotina.asgi import AsgiApp +from guillotina.commands import Command +from guillotina.traversal import TraversalRouter + def create_app(): config_file = os.getenv("GUILLOTINA_CONFIG_FILE", "config.yaml") diff --git a/guillotina/commands/server.py b/guillotina/commands/server.py index ab454de06..b5174d08b 100644 --- a/guillotina/commands/server.py +++ b/guillotina/commands/server.py @@ -23,12 +23,13 @@ async def run(self, arguments, settings, app): from uvicorn import Server # type: ignore from uvicorn.config import LOGGING_CONFIG # type: ignore + uvicorn_settings = {"server_header": False, **app.server_settings.get("uvicorn", {})} config = Config( app, host=host, port=port, log_config=loggers or LOGGING_CONFIG, - **app.server_settings.get("uvicorn", {}), + **uvicorn_settings, ) server = Server(config) await server.serve() diff --git a/guillotina/commands/shell.py b/guillotina/commands/shell.py index 9d91adf8d..1bc27f5a6 100644 --- a/guillotina/commands/shell.py +++ b/guillotina/commands/shell.py @@ -1,14 +1,12 @@ +import asyncio # noqa +import sys + from guillotina import app_settings # noqa -from guillotina import task_vars -from guillotina import utils +from guillotina import task_vars, utils from guillotina.commands import Command from guillotina.component import get_utility from guillotina.interfaces import IApplication -from guillotina.tests.utils import get_mocked_request -from guillotina.tests.utils import login - -import asyncio # noqa -import sys +from guillotina.tests.utils import get_mocked_request, login class ShellHelpers: diff --git a/guillotina/commands/testdata.py b/guillotina/commands/testdata.py index a19acfd86..8594e037c 100644 --- a/guillotina/commands/testdata.py +++ b/guillotina/commands/testdata.py @@ -1,20 +1,18 @@ +import asyncio + +import aiohttp + from guillotina import task_vars from guillotina.behaviors.dublincore import IDublinCore from guillotina.commands import Command from guillotina.component import get_utility -from guillotina.content import create_content -from guillotina.content import create_content_in_container +from guillotina.content import create_content, create_content_in_container from guillotina.event import notify from guillotina.events import ObjectAddedEvent from guillotina.exceptions import ConflictIdOnContainer -from guillotina.interfaces import IApplication -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IPrincipalRoleManager +from guillotina.interfaces import IApplication, IDatabase, IPrincipalRoleManager from guillotina.tests.utils import login -import aiohttp -import asyncio - class AsyncUrlRetriever: """ diff --git a/guillotina/commands/vacuum.py b/guillotina/commands/vacuum.py index 56c72dacb..9c4c9eec3 100644 --- a/guillotina/commands/vacuum.py +++ b/guillotina/commands/vacuum.py @@ -1,10 +1,10 @@ +import logging + from guillotina.commands import Command from guillotina.component import query_adapter from guillotina.db.interfaces import IVacuumProvider from guillotina.utils import iter_databases -import logging - logger = logging.getLogger("guillotina") diff --git a/guillotina/component/__init__.py b/guillotina/component/__init__.py index 9ec59b6a6..79dcc9048 100644 --- a/guillotina/component/__init__.py +++ b/guillotina/component/__init__.py @@ -12,35 +12,40 @@ # ############################################################################## # flake8: noqa -from guillotina.component._api import get_adapter -from guillotina.component._api import get_adapters -from guillotina.component._api import get_all_utilities_registered_for -from guillotina.component._api import get_component_registry -from guillotina.component._api import get_factories_for -from guillotina.component._api import get_factory_interfaces -from guillotina.component._api import get_multi_adapter -from guillotina.component._api import get_utilities_for -from guillotina.component._api import get_utility -from guillotina.component._api import handle -from guillotina.component._api import query_adapter -from guillotina.component._api import query_multi_adapter -from guillotina.component._api import query_utility -from guillotina.component._api import subscribers -from guillotina.component._declaration import adaptedBy -from guillotina.component._declaration import adapter -from guillotina.component._declaration import adapts -from guillotina.component.globalregistry import get_global_components -from guillotina.component.globalregistry import provide_adapter -from guillotina.component.globalregistry import provide_handler -from guillotina.component.globalregistry import provide_subscription_adapter -from guillotina.component.globalregistry import provide_utility -from guillotina.component.interfaces import ComponentLookupError -from guillotina.component.interfaces import IComponentArchitecture -from guillotina.component.interfaces import IComponentLookup -from guillotina.component.interfaces import IComponentRegistrationConvenience -from guillotina.component.interfaces import IFactory from zope.interface import moduleProvides +from guillotina.component._api import ( + get_adapter, + get_adapters, + get_all_utilities_registered_for, + get_component_registry, + get_factories_for, + get_factory_interfaces, + get_multi_adapter, + get_utilities_for, + get_utility, + handle, + query_adapter, + query_multi_adapter, + query_utility, + subscribers, +) +from guillotina.component._declaration import adaptedBy, adapter, adapts +from guillotina.component.globalregistry import ( + get_global_components, + provide_adapter, + provide_handler, + provide_subscription_adapter, + provide_utility, +) +from guillotina.component.interfaces import ( + ComponentLookupError, + IComponentArchitecture, + IComponentLookup, + IComponentRegistrationConvenience, + IFactory, +) + # b/w compat imports. Will be removed in 3.0 getMultiAdapter = get_multi_adapter diff --git a/guillotina/component/_api.py b/guillotina/component/_api.py index da4bc423a..84165a458 100644 --- a/guillotina/component/_api.py +++ b/guillotina/component/_api.py @@ -11,21 +11,16 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## +from typing import Any, Dict, List, Optional + +import zope.interface.interface +from zope.interface import Interface, providedBy + from guillotina.component import globalregistry from guillotina.component._compat import _BLANK from guillotina.component._declaration import adapter # noqa from guillotina.component.hookable import hookable -from guillotina.component.interfaces import ComponentLookupError -from guillotina.component.interfaces import IComponentLookup -from guillotina.component.interfaces import IFactory -from typing import Any -from typing import Dict -from typing import List -from typing import Optional -from zope.interface import Interface -from zope.interface import providedBy - -import zope.interface.interface +from guillotina.component.interfaces import ComponentLookupError, IComponentLookup, IFactory _MISSING = object() @@ -276,7 +271,7 @@ def get_factory_interfaces(name, context=None): def get_factories_for(interface, context=None): """Return info on all factories implementing the given interface.""" utils = get_component_registry(context) - for (name, factory) in utils.getUtilitiesFor(IFactory): + for name, factory in utils.getUtilitiesFor(IFactory): interfaces = factory.get_interfaces() try: if interfaces.isOrExtends(interface): diff --git a/guillotina/component/_declaration.py b/guillotina/component/_declaration.py index 3e5d26e2c..d59fe1db9 100644 --- a/guillotina/component/_declaration.py +++ b/guillotina/component/_declaration.py @@ -12,11 +12,10 @@ # ############################################################################## # flake8: noqa -from guillotina.component._compat import _BLANK -from guillotina.component._compat import CLASS_TYPES - import sys +from guillotina.component._compat import _BLANK, CLASS_TYPES + class adapter(object): def __init__(self, *interfaces): diff --git a/guillotina/component/factory.py b/guillotina/component/factory.py index bccdc0357..c9c287d6f 100644 --- a/guillotina/component/factory.py +++ b/guillotina/component/factory.py @@ -12,11 +12,11 @@ # ############################################################################## # flake8: noqa -from guillotina.component.interfaces import IFactory -from zope.interface import implementedBy -from zope.interface import implementer +from zope.interface import implementedBy, implementer from zope.interface.declarations import Implements +from guillotina.component.interfaces import IFactory + @implementer(IFactory) class Factory(object): diff --git a/guillotina/component/globalregistry.py b/guillotina/component/globalregistry.py index 5e026fb2a..9d28d8cc5 100644 --- a/guillotina/component/globalregistry.py +++ b/guillotina/component/globalregistry.py @@ -11,20 +11,19 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -from guillotina.component._compat import _BLANK -from guillotina.component.interfaces import IComponentLookup -from guillotina.profile import profilable -from typing import Type -from zope.interface import implementer -from zope.interface import providedBy -from zope.interface.adapter import AdapterLookup -from zope.interface.adapter import AdapterRegistry -from zope.interface.registry import Components - import asyncio import logging import os import time +from typing import Type + +from zope.interface import implementer, providedBy +from zope.interface.adapter import AdapterLookup, AdapterRegistry +from zope.interface.registry import Components + +from guillotina.component._compat import _BLANK +from guillotina.component.interfaces import IComponentLookup +from guillotina.profile import profilable profile_logger = logging.getLogger("guillotina.profile") @@ -55,9 +54,9 @@ def subscribers(self, objects, provided): class DebugGuillotinaAdapterLookup(GuillotinaAdapterLookup): # pragma: no cover @profilable async def asubscribers(self, objects, provided): - from guillotina.utils import get_current_request, get_authenticated_user_id, get_dotted_name - from guillotina.exceptions import RequestNotFound from guillotina import task_vars + from guillotina.exceptions import RequestNotFound + from guillotina.utils import get_authenticated_user_id, get_current_request, get_dotted_name if len(objects) > 1: event = get_dotted_name(objects[1]) diff --git a/guillotina/component/interface.py b/guillotina/component/interface.py index f1545efef..e55cd9f7d 100644 --- a/guillotina/component/interface.py +++ b/guillotina/component/interface.py @@ -1,7 +1,8 @@ -from guillotina.component._api import get_component_registry from zope.interface import alsoProvides from zope.interface.interfaces import IInterface +from guillotina.component._api import get_component_registry + def provide_interface(id, interface, iface_type=None, info=""): """Mark 'interface' as a named utilty providing 'iface_type'.""" diff --git a/guillotina/component/interfaces.py b/guillotina/component/interfaces.py index a4a7ebebe..93abaceb1 100644 --- a/guillotina/component/interfaces.py +++ b/guillotina/component/interfaces.py @@ -13,30 +13,33 @@ ############################################################################ # flake8: noqa +from zope.interface import Attribute, Interface + +# BBB 2011-09-09, import interfaces from zope.interface +from zope.interface.interfaces import ( + ComponentLookupError, + IAdapterRegistration, + IComponentLookup, + IComponentRegistry, + IComponents, + IHandlerRegistration, + Invalid, + IObjectEvent, + IRegistered, + IRegistration, + IRegistrationEvent, + ISubscriptionAdapterRegistration, + IUnregistered, + IUtilityRegistration, + ObjectEvent, + Registered, + RegistrationEvent, + Unregistered, + _IBaseAdapterRegistration, +) + # fmt: off from guillotina.component._compat import _BLANK -from zope.interface import Attribute -from zope.interface import Interface -# BBB 2011-09-09, import interfaces from zope.interface -from zope.interface.interfaces import _IBaseAdapterRegistration -from zope.interface.interfaces import ComponentLookupError -from zope.interface.interfaces import IAdapterRegistration -from zope.interface.interfaces import IComponentLookup -from zope.interface.interfaces import IComponentRegistry -from zope.interface.interfaces import IComponents -from zope.interface.interfaces import IHandlerRegistration -from zope.interface.interfaces import Invalid -from zope.interface.interfaces import IObjectEvent -from zope.interface.interfaces import IRegistered -from zope.interface.interfaces import IRegistration -from zope.interface.interfaces import IRegistrationEvent -from zope.interface.interfaces import ISubscriptionAdapterRegistration -from zope.interface.interfaces import IUnregistered -from zope.interface.interfaces import IUtilityRegistration -from zope.interface.interfaces import ObjectEvent -from zope.interface.interfaces import Registered -from zope.interface.interfaces import RegistrationEvent -from zope.interface.interfaces import Unregistered # fmt: on diff --git a/guillotina/component/registry.py b/guillotina/component/registry.py index a024d1211..d9b48c10a 100644 --- a/guillotina/component/registry.py +++ b/guillotina/component/registry.py @@ -13,11 +13,13 @@ ############################################################################## from guillotina.component._api import handle from guillotina.component._declaration import adapter -from guillotina.component.interfaces import IAdapterRegistration -from guillotina.component.interfaces import IHandlerRegistration -from guillotina.component.interfaces import IRegistrationEvent -from guillotina.component.interfaces import ISubscriptionAdapterRegistration -from guillotina.component.interfaces import IUtilityRegistration +from guillotina.component.interfaces import ( + IAdapterRegistration, + IHandlerRegistration, + IRegistrationEvent, + ISubscriptionAdapterRegistration, + IUtilityRegistration, +) @adapter(IUtilityRegistration, IRegistrationEvent) diff --git a/guillotina/component/testfiles/adapter.py b/guillotina/component/testfiles/adapter.py index 1b0663923..acb66f5c7 100644 --- a/guillotina/component/testfiles/adapter.py +++ b/guillotina/component/testfiles/adapter.py @@ -13,10 +13,10 @@ ############################################################################## # flake8: noqa +from zope.interface import Interface, implementer + from guillotina.component import adapter from guillotina.component.testfiles import components -from zope.interface import implementer -from zope.interface import Interface class I1(Interface): diff --git a/guillotina/component/testfiles/components.py b/guillotina/component/testfiles/components.py index 2bcb94d07..6378a2859 100644 --- a/guillotina/component/testfiles/components.py +++ b/guillotina/component/testfiles/components.py @@ -12,11 +12,9 @@ # ############################################################################## # flake8: noqa +from zope.interface import Attribute, Interface, implementer, named + from guillotina.component import adapter -from zope.interface import Attribute -from zope.interface import implementer -from zope.interface import Interface -from zope.interface import named class IAppb(Interface): diff --git a/guillotina/component/testfiles/views.py b/guillotina/component/testfiles/views.py index b93f26b0b..cf75a322d 100644 --- a/guillotina/component/testfiles/views.py +++ b/guillotina/component/testfiles/views.py @@ -13,9 +13,7 @@ ############################################################################## # flake8: noqa -from zope.interface import directlyProvides -from zope.interface import implementer -from zope.interface import Interface +from zope.interface import Interface, directlyProvides, implementer class Request(object): diff --git a/guillotina/component/tests/examples.py b/guillotina/component/tests/examples.py index 87458ef15..5b804d45c 100644 --- a/guillotina/component/tests/examples.py +++ b/guillotina/component/tests/examples.py @@ -12,14 +12,14 @@ # ############################################################################## # flake8: noqa +import sys + +from zope.interface import Interface, implementer +from zope.interface.interfaces import IInterface + from guillotina.component._declaration import adapter from guillotina.component.globalregistry import GuillotinaAdapterRegistry from guillotina.component.testfiles.views import IC -from zope.interface import implementer -from zope.interface import Interface -from zope.interface.interfaces import IInterface - -import sys def write(x): diff --git a/guillotina/component/tests/test___init__.py b/guillotina/component/tests/test___init__.py index 2d8e5dd14..499f8863d 100644 --- a/guillotina/component/tests/test___init__.py +++ b/guillotina/component/tests/test___init__.py @@ -18,15 +18,17 @@ class Test_package(unittest.TestCase): def test_module_conforms_to_IComponentArchitecture(self): from zope.interface.verify import verifyObject - from guillotina.component.interfaces import IComponentArchitecture + import guillotina.component as zc + from guillotina.component.interfaces import IComponentArchitecture verifyObject(IComponentArchitecture, zc) def test_module_conforms_to_IComponentRegistrationConvenience(self): from zope.interface.verify import verifyObject - from guillotina.component.interfaces import IComponentRegistrationConvenience + import guillotina.component as zc + from guillotina.component.interfaces import IComponentRegistrationConvenience verifyObject(IComponentRegistrationConvenience, zc) @@ -53,8 +55,8 @@ class IFoo(Interface): self.assertTrue(IFoo(object(), marker) is marker) def test_hit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -79,8 +81,8 @@ def __init__(self, context): self.assertTrue(adapted.context is bar) def test_hit_registered_for_None(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): diff --git a/guillotina/component/tests/test__api.py b/guillotina/component/tests/test__api.py index 17e6419d3..2957b5c86 100644 --- a/guillotina/component/tests/test__api.py +++ b/guillotina/component/tests/test__api.py @@ -54,6 +54,7 @@ def test_get_component_registry_w_invalid_context_no_adapter(self): def test_get_component_registry_w_invalid_context_w_adapter(self): from zope.interface import Interface + from guillotina.component.globalregistry import get_global_components from guillotina.component.interfaces import IComponentLookup @@ -78,6 +79,7 @@ def _callFUT(self, *args, **kw): def test_anonymous_nonesuch(self): from zope.interface import Interface + from guillotina.component.interfaces import ComponentLookupError class IFoo(Interface): @@ -87,6 +89,7 @@ class IFoo(Interface): def test_named_nonesuch(self): from zope.interface import Interface + from guillotina.component.interfaces import ComponentLookupError class IFoo(Interface): @@ -95,8 +98,8 @@ class IFoo(Interface): self.assertRaises(ComponentLookupError, self._callFUT, object(), IFoo, "bar") def test_anonymous_hit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -121,8 +124,8 @@ def __init__(self, context): self.assertTrue(adapted.context is bar) def test_anonymous_hit_registered_for_None(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -140,8 +143,8 @@ def __init__(self, context): self.assertTrue(adapted.context is ctx) def test_named_hit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -192,8 +195,8 @@ class IFoo(Interface): self.assertEqual(self._callFUT(object(), IFoo, "bar"), None) def test_anonymous_hit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -218,8 +221,8 @@ def __init__(self, context): self.assertTrue(adapted.context is bar) def test_named_hit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -244,9 +247,9 @@ def __init__(self, context): self.assertTrue(adapted.context is bar) def test_nested(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer from zope.interface.registry import Components + from guillotina.component import get_global_components from guillotina.component.tests.examples import ConformsToIComponentLookup @@ -295,6 +298,7 @@ def _callFUT(self, *args, **kw): def test_anonymous_nonesuch(self): from zope.interface import Interface + from guillotina.component.interfaces import ComponentLookupError class IFoo(Interface): @@ -304,6 +308,7 @@ class IFoo(Interface): def test_named_nonesuch(self): from zope.interface import Interface + from guillotina.component.interfaces import ComponentLookupError class IFoo(Interface): @@ -312,8 +317,8 @@ class IFoo(Interface): self.assertRaises(ComponentLookupError, self._callFUT, (object(), object()), IFoo, "bar") def test_anonymous_hit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -347,8 +352,8 @@ def __init__(self, first, second): self.assertTrue(adapted.second is baz) def test_anonymous_hit_registered_for_None(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -378,8 +383,8 @@ def __init__(self, first, second): self.assertTrue(adapted.second is baz) def test_named_hit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -439,8 +444,8 @@ class IFoo(Interface): self.assertEqual(self._callFUT((object(), object()), IFoo, "bar"), None) def test_anonymous_hit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -474,8 +479,8 @@ def __init__(self, first, second): self.assertTrue(adapted.second is baz) def test_named_hit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component import get_global_components class IFoo(Interface): @@ -509,9 +514,9 @@ def __init__(self, first, second): self.assertTrue(adapted.second is baz) def test_nested(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer from zope.interface.registry import Components + from guillotina.component import get_global_components from guillotina.component.tests.examples import ConformsToIComponentLookup @@ -558,8 +563,8 @@ def __init__(self, sm): self.assertTrue(adapted.second is baz) def test_wo_sitemanager(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component.interfaces import ComponentLookupError class IFoo(Interface): @@ -608,6 +613,7 @@ class IFoo(Interface): def test_hit(self): from zope.interface import Interface + from guillotina.component import get_global_components class IFoo(Interface): @@ -631,8 +637,8 @@ def __init__(self, context): self.assertTrue(("bar", "BazAdapter") in names) def test_wo_sitemanager(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component.interfaces import ComponentLookupError class IFoo(Interface): @@ -682,6 +688,7 @@ class IFoo(Interface): def test_hit(self): from zope.interface import Interface + from guillotina.component import get_global_components class IFoo(Interface): @@ -706,6 +713,7 @@ def __init__(self, context): def test_wo_sitemanager(self): from zope.interface import Interface + from guillotina.component.interfaces import ComponentLookupError class IFoo(Interface): @@ -737,9 +745,9 @@ class IFoo(Interface): subscribers = self._callFUT((object,), IFoo) # doesn't raise def test_hit(self): + from zope.interface import Interface, implementer + from guillotina.component import get_global_components - from zope.interface import Interface - from zope.interface import implementer class IFoo(Interface): pass @@ -776,6 +784,7 @@ def _callFUT(self, *args, **kw): def test_anonymous_nonesuch(self): from zope.interface import Interface + from guillotina.component.interfaces import ComponentLookupError class IFoo(Interface): @@ -785,6 +794,7 @@ class IFoo(Interface): def test_named_nonesuch(self): from zope.interface import Interface + from guillotina.component.interfaces import ComponentLookupError class IFoo(Interface): @@ -794,6 +804,7 @@ class IFoo(Interface): def test_anonymous_hit(self): from zope.interface import Interface + from guillotina.component import get_global_components class IFoo(Interface): @@ -805,6 +816,7 @@ class IFoo(Interface): def test_named_hit(self): from zope.interface import Interface + from guillotina.component import get_global_components class IFoo(Interface): @@ -816,6 +828,7 @@ class IFoo(Interface): def test_w_conforming_context(self): from zope.interface import Interface + from guillotina.component import get_global_components from guillotina.component.tests.examples import ConformsToIComponentLookup @@ -882,6 +895,7 @@ class IFoo(Interface): def test_anonymous_hit(self): from zope.interface import Interface + from guillotina.component import get_global_components class IFoo(Interface): @@ -893,6 +907,7 @@ class IFoo(Interface): def test_named_hit(self): from zope.interface import Interface + from guillotina.component import get_global_components class IFoo(Interface): @@ -904,6 +919,7 @@ class IFoo(Interface): def test_w_conforming_context(self): from zope.interface import Interface + from guillotina.component import get_global_components from guillotina.component.tests.examples import ConformsToIComponentLookup @@ -944,6 +960,7 @@ class IFoo(Interface): def test_hit(self): from zope.interface import Interface + from guillotina.component import get_global_components class IFoo(Interface): @@ -978,6 +995,7 @@ class IFoo(Interface): def test_hit(self): from zope.interface import Interface + from guillotina.component import get_global_components class IFoo(Interface): @@ -1014,9 +1032,10 @@ def test_miss(self): self.assertRaises(ComponentLookupError, self._callFUT, "nonesuch") def test_hit(self): - from guillotina.component.interfaces import IFactory from zope.interface import Interface + from guillotina.component.interfaces import IFactory + class IFoo(Interface): pass @@ -1055,9 +1074,8 @@ class IFoo(Interface): self.assertEqual(list(self._callFUT(IFoo)), []) def test_w_factory_returning_spec(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface import providedBy + from zope.interface import Interface, implementer, providedBy + from guillotina.component.interfaces import IFactory class IFoo(Interface): @@ -1086,6 +1104,7 @@ def getUtilitiesFor(self, iface): def test_w_factory_returning_list_of_interfaces(self): from zope.interface import Interface + from guillotina.component.interfaces import IFactory class IFoo(Interface): @@ -1117,8 +1136,8 @@ def getUtilitiesFor(self, iface): def _makeMyUtility(name, sm): global IMyUtility - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component.tests.examples import ConformsToIComponentLookup if IMyUtility is None: diff --git a/guillotina/component/tests/test__declaration.py b/guillotina/component/tests/test__declaration.py index ba6db0751..d62097ef4 100644 --- a/guillotina/component/tests/test__declaration.py +++ b/guillotina/component/tests/test__declaration.py @@ -124,9 +124,11 @@ def _try(): def test_called_from_function(self): import warnings - from guillotina.component._declaration import adapts + from zope.interface import Interface + from guillotina.component._declaration import adapts + class IFoo(Interface): pass @@ -142,10 +144,12 @@ class IFoo(Interface): def test_called_twice_from_class(self): import warnings - from guillotina.component._declaration import adapts + from zope.interface import Interface from zope.interface._compat import PYTHON3 + from guillotina.component._declaration import adapts + class IFoo(Interface): pass @@ -166,9 +170,10 @@ class IBar(Interface): self.fail("Didn't raise TypeError") def test_called_once_from_class(self): - from guillotina.component._declaration import adapts from zope.interface import Interface + from guillotina.component._declaration import adapts + class IFoo(Interface): pass @@ -206,6 +211,7 @@ class Baz(object): def test__call___w_inst_of_decorated_class(self): from zope.interface import Interface + from guillotina.component._declaration import _adapts_descr class IFoo(Interface): diff --git a/guillotina/component/tests/test_event.py b/guillotina/component/tests/test_event.py index e6ae537d4..87e8be0f2 100644 --- a/guillotina/component/tests/test_event.py +++ b/guillotina/component/tests/test_event.py @@ -18,8 +18,9 @@ class Test_dispatch(unittest.TestCase): def test_it(self): from zope.interface import Interface - from guillotina.component.globalregistry import get_global_components + from guillotina.component.event import dispatch + from guillotina.component.globalregistry import get_global_components _adapted = [] diff --git a/guillotina/component/tests/test_factory.py b/guillotina/component/tests/test_factory.py index 46b0fc861..248d8a90a 100644 --- a/guillotina/component/tests/test_factory.py +++ b/guillotina/component/tests/test_factory.py @@ -28,12 +28,14 @@ def _makeOne(self, callable=None, *args, **kw): def test_class_conforms_to_IFactory(self): from zope.interface.verify import verifyClass + from guillotina.component.interfaces import IFactory verifyClass(IFactory, self._getTargetClass()) def test_instance_conforms_to_IFactory(self): from zope.interface.verify import verifyObject + from guillotina.component.interfaces import IFactory verifyObject(IFactory, self._makeOne()) @@ -81,8 +83,7 @@ def _callable(*args, **kw): self.assertEqual(_called, [((), {"foo": "bar"})]) def test_get_interfaces_explicit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer class IFoo(Interface): pass @@ -103,8 +104,7 @@ def _callable(): self.assertEqual(list(spec), [IFoo, IBar]) def test_get_interfaces_implicit(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer class IBaz(Interface): pass diff --git a/guillotina/component/tests/test_globalregistry.py b/guillotina/component/tests/test_globalregistry.py index 3fecc8109..f02912483 100644 --- a/guillotina/component/tests/test_globalregistry.py +++ b/guillotina/component/tests/test_globalregistry.py @@ -43,8 +43,8 @@ def _callFUT(self, *args, **kw): return provide_utility(*args, **kw) def test_anonymous_no_provides(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component.globalregistry import get_global_components class IFoo(Interface): @@ -61,6 +61,7 @@ class Foo(object): def test_named_w_provides(self): from zope.interface import Interface + from guillotina.component.globalregistry import get_global_components class IFoo(Interface): @@ -85,10 +86,10 @@ def _callFUT(self, *args, **kw): return provide_adapter(*args, **kw) def test_anonymous_no_provides_no_adapts(self): - from zope.interface import Interface - from zope.interface import implementer - from guillotina.component.globalregistry import get_global_components + from zope.interface import Interface, implementer + from guillotina.component._declaration import adapter + from guillotina.component.globalregistry import get_global_components class IFoo(Interface): pass @@ -114,8 +115,8 @@ def __init__(self, context): self.assertTrue(adapted.context is foo) def test_named_w_provides_w_adapts(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component.globalregistry import get_global_components class IFoo(Interface): @@ -150,10 +151,10 @@ def _callFUT(self, *args, **kw): return provide_subscription_adapter(*args, **kw) def test_no_provides_no_adapts(self): - from zope.interface import Interface - from zope.interface import implementer - from guillotina.component.globalregistry import get_global_components + from zope.interface import Interface, implementer + from guillotina.component._declaration import adapter + from guillotina.component.globalregistry import get_global_components class IFoo(Interface): pass @@ -180,8 +181,8 @@ def __init__(self, context): self.assertTrue(adapted[0].context is foo) def test_w_provides_w_adapts(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component.globalregistry import get_global_components class IFoo(Interface): @@ -217,11 +218,10 @@ def _callFUT(self, *args, **kw): return provide_handler(*args, **kw) def test_no_adapts(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface import providedBy - from guillotina.component.globalregistry import get_global_components + from zope.interface import Interface, implementer, providedBy + from guillotina.component._declaration import adapter + from guillotina.component.globalregistry import get_global_components class IFoo(Interface): pass @@ -245,6 +245,7 @@ def _handler(context): def test_w_adapts(self): from zope.interface import Interface + from guillotina.component.globalregistry import get_global_components class IFoo(Interface): diff --git a/guillotina/configure/__init__.py b/guillotina/configure/__init__.py index fe5ef715e..2d76ec26e 100644 --- a/guillotina/configure/__init__.py +++ b/guillotina/configure/__init__.py @@ -1,44 +1,42 @@ +import asyncio +import inspect +import logging from collections import OrderedDict +from pprint import pformat +from typing import Any, Dict, Optional, Tuple + +from zope.interface import Interface, classImplements +from zope.interface.interfaces import IInterface + from guillotina import routes from guillotina._settings import app_settings from guillotina.configure import component -from guillotina.configure.behaviors import BehaviorAdapterFactory -from guillotina.configure.behaviors import BehaviorRegistration -from guillotina.exceptions import ConfigurationError -from guillotina.exceptions import ServiceConfigurationError -from guillotina.gtypes import ConfigurationType -from guillotina.gtypes import ResolvableType -from guillotina.interfaces import DEFAULT_ADD_PERMISSION -from guillotina.interfaces import IBehavior -from guillotina.interfaces import IBehaviorSchemaAwareFactory -from guillotina.interfaces import IDefaultLayer -from guillotina.interfaces import IJSONToValue -from guillotina.interfaces import ILanguage -from guillotina.interfaces import IPermission -from guillotina.interfaces import IRenderer -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResource -from guillotina.interfaces import IResourceFactory -from guillotina.interfaces import IRole -from guillotina.interfaces import IValueToJson -from guillotina.interfaces import IView +from guillotina.configure.behaviors import BehaviorAdapterFactory, BehaviorRegistration +from guillotina.exceptions import ConfigurationError, ServiceConfigurationError +from guillotina.gtypes import ConfigurationType, ResolvableType +from guillotina.interfaces import ( + DEFAULT_ADD_PERMISSION, + IBehavior, + IBehaviorSchemaAwareFactory, + IDefaultLayer, + IJSONToValue, + ILanguage, + IPermission, + IRenderer, + IRequest, + IResource, + IResourceFactory, + IRole, + IValueToJson, + IView, +) from guillotina.security.permission import Permission -from guillotina.utils import get_caller_module -from guillotina.utils import get_module_dotted_name -from guillotina.utils import resolve_dotted_name -from guillotina.utils import resolve_module_path -from pprint import pformat -from typing import Any -from typing import Dict -from typing import Optional -from typing import Tuple -from zope.interface import classImplements -from zope.interface import Interface -from zope.interface.interfaces import IInterface - -import asyncio -import inspect -import logging +from guillotina.utils import ( + get_caller_module, + get_module_dotted_name, + resolve_dotted_name, + resolve_module_path, +) _registered_configurations: ConfigurationType = [] @@ -591,9 +589,9 @@ def json_schema_definition(name: str, schema: dict) -> None: def grant_directive(_context, principal=None, role=None, permission=None, permissions=None): - from guillotina.security.security_code import role_permission_manager as role_perm_mgr from guillotina.security.security_code import principal_permission_manager as principal_perm_mgr from guillotina.security.security_code import principal_role_manager as principal_role_mgr + from guillotina.security.security_code import role_permission_manager as role_perm_mgr nspecified = ( (principal is not None) + (role is not None) + (permission is not None) + (permissions is not None) @@ -633,8 +631,7 @@ def grant_directive(_context, principal=None, role=None, permission=None, permis def grantAll_directive(_context, principal=None, role=None): # noqa: N802 """Grant all permissions to a role or principal""" - from guillotina.security.security_code import role_permission_manager - from guillotina.security.security_code import principal_permission_manager + from guillotina.security.security_code import principal_permission_manager, role_permission_manager nspecified = (principal is not None) + (role is not None) diff --git a/guillotina/configure/behaviors.py b/guillotina/configure/behaviors.py index f12d91cd9..99910c855 100644 --- a/guillotina/configure/behaviors.py +++ b/guillotina/configure/behaviors.py @@ -1,7 +1,6 @@ -from guillotina.interfaces import IBehavior -from guillotina.interfaces import IBehaviorAdapterFactory -from zope.interface import implementer -from zope.interface import Interface +from zope.interface import Interface, implementer + +from guillotina.interfaces import IBehavior, IBehaviorAdapterFactory REGISTRATION_REPR = """\ diff --git a/guillotina/configure/component.py b/guillotina/configure/component.py index e95f34018..77bb78a98 100644 --- a/guillotina/configure/component.py +++ b/guillotina/configure/component.py @@ -13,16 +13,14 @@ ############################################################################## """Component Architecture configuration handlers """ +from zope.interface import Interface, implementedBy, providedBy + from guillotina.component._api import get_component_registry from guillotina.component._compat import _BLANK -from guillotina.component._declaration import adaptedBy -from guillotina.component._declaration import getName +from guillotina.component._declaration import adaptedBy, getName from guillotina.component.interface import provide_interface from guillotina.exceptions import ComponentConfigurationError from guillotina.i18n import MessageFactory -from zope.interface import implementedBy -from zope.interface import Interface -from zope.interface import providedBy _ = MessageFactory("guillotina") diff --git a/guillotina/configure/config.py b/guillotina/configure/config.py index 84703b829..565b3b972 100644 --- a/guillotina/configure/config.py +++ b/guillotina/configure/config.py @@ -13,13 +13,14 @@ ############################################################################## """Configuration processor """ -from guillotina.exceptions import ConfigurationError -from guillotina.interfaces.configuration import IConfigurationContext -from zope.interface import implementer - import operator import sys +from zope.interface import implementer + +from guillotina.exceptions import ConfigurationError +from guillotina.interfaces.configuration import IConfigurationContext + def reraise(tp, value, tb=None): if value is None: diff --git a/guillotina/constraintypes.py b/guillotina/constraintypes.py index f793bedd6..2abbef53b 100644 --- a/guillotina/constraintypes.py +++ b/guillotina/constraintypes.py @@ -1,13 +1,11 @@ -from guillotina import app_settings -from guillotina import configure -from guillotina.content import get_cached_factory -from guillotina.interfaces import IConstrainTypes -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IResource -from typing import List -from typing import Optional +from typing import List, Optional + from zope.interface import Interface +from guillotina import app_settings, configure +from guillotina.content import get_cached_factory +from guillotina.interfaces import IConstrainTypes, IDatabase, IResource + @configure.adapter(for_=Interface, provides=IConstrainTypes) class FTIConstrainAllowedTypes: diff --git a/guillotina/content.py b/guillotina/content.py index 710e0b9ea..60506010c 100644 --- a/guillotina/content.py +++ b/guillotina/content.py @@ -1,88 +1,72 @@ +import os +import pathlib from datetime import datetime +from typing import Any, AsyncIterator, Dict, FrozenSet, Iterator, List, Optional, Tuple, Union, cast + from dateutil.tz import tzutc -from guillotina import configure -from guillotina import task_vars -from guillotina._cache import BEHAVIOR_CACHE -from guillotina._cache import FACTORY_CACHE -from guillotina._cache import PERMISSIONS_CACHE -from guillotina._cache import SCHEMA_CACHE +from zope.interface import Interface, alsoProvides, implementer, noLongerProvides +from zope.interface.interfaces import ComponentLookupError + +import guillotina.db.orm.base +from guillotina import configure, task_vars +from guillotina._cache import BEHAVIOR_CACHE, FACTORY_CACHE, PERMISSIONS_CACHE, SCHEMA_CACHE from guillotina._settings import app_settings from guillotina.annotations import AnnotationData -from guillotina.auth.users import ANONYMOUS_USER_ID -from guillotina.auth.users import ROOT_USER_ID +from guillotina.auth.users import ANONYMOUS_USER_ID, ROOT_USER_ID from guillotina.behaviors import apply_markers from guillotina.browser import get_physical_path -from guillotina.component import get_adapter -from guillotina.component import get_utilities_for -from guillotina.component import get_utility -from guillotina.component import query_utility +from guillotina.component import get_adapter, get_utilities_for, get_utility, query_utility from guillotina.component.factory import Factory from guillotina.db import uid from guillotina.db.interfaces import ITransaction from guillotina.db.orm.interfaces import IBaseObject from guillotina.event import notify -from guillotina.events import BeforeObjectAddedEvent -from guillotina.events import BeforeObjectMovedEvent -from guillotina.events import ObjectDuplicatedEvent -from guillotina.events import ObjectLoadedEvent -from guillotina.events import ObjectMovedEvent -from guillotina.exceptions import ConflictIdOnContainer -from guillotina.exceptions import InvalidContentType -from guillotina.exceptions import NoPermissionToAdd -from guillotina.exceptions import NotAllowedContentType -from guillotina.exceptions import PreconditionFailed -from guillotina.exceptions import TransactionNotFound -from guillotina.interfaces import DEFAULT_ADD_PERMISSION -from guillotina.interfaces import IAddons -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import IAsyncBehavior -from guillotina.interfaces import IBehavior -from guillotina.interfaces import IConstrainTypes -from guillotina.interfaces import IContainer -from guillotina.interfaces import IFolder -from guillotina.interfaces import IGetOwner -from guillotina.interfaces import IIDChecker -from guillotina.interfaces import IItem -from guillotina.interfaces import IJavaScriptApplication -from guillotina.interfaces import ILayers -from guillotina.interfaces import IPermission -from guillotina.interfaces import IPrincipalPermissionManager -from guillotina.interfaces import IPrincipalRoleManager -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResource -from guillotina.interfaces import IResourceFactory -from guillotina.interfaces import IStaticDirectory -from guillotina.interfaces import IStaticFile +from guillotina.events import ( + BeforeObjectAddedEvent, + BeforeObjectMovedEvent, + ObjectDuplicatedEvent, + ObjectLoadedEvent, + ObjectMovedEvent, +) +from guillotina.exceptions import ( + ConflictIdOnContainer, + InvalidContentType, + NoPermissionToAdd, + NotAllowedContentType, + PreconditionFailed, + TransactionNotFound, +) +from guillotina.interfaces import ( + DEFAULT_ADD_PERMISSION, + IAddons, + IAnnotations, + IAsyncBehavior, + IBehavior, + IConstrainTypes, + IContainer, + IFolder, + IGetOwner, + IIDChecker, + IItem, + IJavaScriptApplication, + ILayers, + IPermission, + IPrincipalPermissionManager, + IPrincipalRoleManager, + IRequest, + IResource, + IResourceFactory, + IStaticDirectory, + IStaticFile, +) from guillotina.profile import profilable from guillotina.registry import REGISTRY_DATA_KEY from guillotina.response import HTTPConflict from guillotina.schema.utils import get_default_from_schema from guillotina.security.security_code import PrincipalPermissionManager from guillotina.transactions import get_transaction -from guillotina.utils import get_object_by_uid -from guillotina.utils import get_security_policy -from guillotina.utils import navigate_to -from guillotina.utils import valid_id +from guillotina.utils import get_object_by_uid, get_security_policy, navigate_to, valid_id from guillotina.utils.auth import get_authenticated_user_id -from typing import Any -from typing import AsyncIterator -from typing import cast -from typing import Dict -from typing import FrozenSet -from typing import Iterator -from typing import List -from typing import Optional -from typing import Tuple -from typing import Union -from zope.interface import alsoProvides -from zope.interface import implementer -from zope.interface import Interface -from zope.interface import noLongerProvides -from zope.interface.interfaces import ComponentLookupError - -import guillotina.db.orm.base -import os -import pathlib _zone = tzutc() # utz tz is much faster than local tz info diff --git a/guillotina/contentapi.py b/guillotina/contentapi.py index 700d4cc77..148936f58 100644 --- a/guillotina/contentapi.py +++ b/guillotina/contentapi.py @@ -1,21 +1,16 @@ +import typing + +from zope.interface import alsoProvides + from guillotina import task_vars from guillotina._settings import app_settings from guillotina.auth.users import RootUser from guillotina.auth.utils import set_authenticated_user from guillotina.component import get_multi_adapter from guillotina.db.interfaces import ITransaction -from guillotina.interfaces import ACTIVE_LAYERS_KEY -from guillotina.interfaces import IContainer -from guillotina.interfaces import IResource +from guillotina.interfaces import ACTIVE_LAYERS_KEY, IContainer, IResource from guillotina.tests.utils import get_mocked_request -from guillotina.utils import get_authenticated_user -from guillotina.utils import get_object_url -from guillotina.utils import get_registry -from guillotina.utils import import_class -from guillotina.utils import navigate_to -from zope.interface import alsoProvides - -import typing +from guillotina.utils import get_authenticated_user, get_object_url, get_registry, import_class, navigate_to class ContentAPI: diff --git a/guillotina/contrib/cache/api.py b/guillotina/contrib/cache/api.py index 195850d57..6b8a85294 100644 --- a/guillotina/contrib/cache/api.py +++ b/guillotina/contrib/cache/api.py @@ -1,7 +1,6 @@ from guillotina import configure from guillotina.component import get_utility -from guillotina.interfaces import ICacheUtility -from guillotina.interfaces import IContainer +from guillotina.interfaces import ICacheUtility, IContainer @configure.service(context=IContainer, name="@cache-stats", method="GET", permission="guillotina.CacheManage") diff --git a/guillotina/contrib/cache/lru.pyi b/guillotina/contrib/cache/lru.pyi index 021e97be4..a73bb1457 100644 --- a/guillotina/contrib/cache/lru.pyi +++ b/guillotina/contrib/cache/lru.pyi @@ -2,7 +2,6 @@ from typing import Any from typing import Dict from typing import Optional - class LRU(Dict[str, Any]): def __init__(self, size: int): ... def set(self, key: str, value: Any, size: Optional[int] = None) -> None: ... diff --git a/guillotina/contrib/cache/memcache.py b/guillotina/contrib/cache/memcache.py index d3bc1392a..f3369a23c 100644 --- a/guillotina/contrib/cache/memcache.py +++ b/guillotina/contrib/cache/memcache.py @@ -1,6 +1,7 @@ +from typing import Optional + from guillotina import app_settings from guillotina.contrib.cache.lru import LRU -from typing import Optional _lru: Optional[LRU] = None diff --git a/guillotina/contrib/cache/serialize.py b/guillotina/contrib/cache/serialize.py index 98bd07aa8..8e149a0e9 100644 --- a/guillotina/contrib/cache/serialize.py +++ b/guillotina/contrib/cache/serialize.py @@ -1,9 +1,10 @@ -from guillotina.profile import profilable - -import asyncpg import pickle import typing +import asyncpg + +from guillotina.profile import profilable + @profilable def dumps(value: typing.Any) -> bytes: diff --git a/guillotina/contrib/cache/strategy.py b/guillotina/contrib/cache/strategy.py index e2dd7b8e3..aee48d467 100644 --- a/guillotina/contrib/cache/strategy.py +++ b/guillotina/contrib/cache/strategy.py @@ -1,20 +1,15 @@ -from guillotina import app_settings -from guillotina import configure +import asyncio +import logging +from typing import Any, Dict, List + +from guillotina import app_settings, configure from guillotina.component import query_utility from guillotina.db.cache.base import BaseCache -from guillotina.db.interfaces import ITransaction -from guillotina.db.interfaces import ITransactionCache -from guillotina.exceptions import NoChannelConfigured -from guillotina.exceptions import NoPubSubUtility +from guillotina.db.interfaces import ITransaction, ITransactionCache +from guillotina.exceptions import NoChannelConfigured, NoPubSubUtility from guillotina.interfaces import ICacheUtility from guillotina.profile import profilable from guillotina.utils import notice_on_error_internal -from typing import Any -from typing import Dict -from typing import List - -import asyncio -import logging logger = logging.getLogger("guillotina") diff --git a/guillotina/contrib/cache/utility.py b/guillotina/contrib/cache/utility.py index 090ab5f79..679635185 100644 --- a/guillotina/contrib/cache/utility.py +++ b/guillotina/contrib/cache/utility.py @@ -1,22 +1,20 @@ +import asyncio +import logging +import pickle +import uuid +from sys import getsizeof +from typing import List, Optional + +import asyncpg + from guillotina import app_settings from guillotina.component import query_utility -from guillotina.contrib.cache import CACHE_PREFIX -from guillotina.contrib.cache import memcache -from guillotina.contrib.cache import serialize +from guillotina.contrib.cache import CACHE_PREFIX, memcache, serialize from guillotina.contrib.cache.lru import LRU from guillotina.exceptions import NoPubSubUtility from guillotina.interfaces import IPubSubUtility from guillotina.profile import profilable from guillotina.utils import resolve_dotted_name -from sys import getsizeof -from typing import List -from typing import Optional - -import asyncio -import asyncpg -import logging -import pickle -import uuid logger = logging.getLogger("guillotina.contrib.cache") diff --git a/guillotina/contrib/catalog/pg/__init__.py b/guillotina/contrib/catalog/pg/__init__.py index a6f918bec..db141876b 100644 --- a/guillotina/contrib/catalog/pg/__init__.py +++ b/guillotina/contrib/catalog/pg/__init__.py @@ -1,7 +1,7 @@ -from guillotina import configure - import logging +from guillotina import configure + logger = logging.getLogger("guillotina") diff --git a/guillotina/contrib/catalog/pg/indexes.py b/guillotina/contrib/catalog/pg/indexes.py index 00f60737e..20803bdb9 100644 --- a/guillotina/contrib/catalog/pg/indexes.py +++ b/guillotina/contrib/catalog/pg/indexes.py @@ -1,9 +1,9 @@ +import typing + from guillotina.catalog.utils import iter_indexes from guillotina.contrib.catalog.pg.utils import sqlq from guillotina.db.interfaces import IPostgresStorage -import typing - class BasicJsonIndex: operators: typing.List[str] = ["=", "!=", "?", "?|", "is null", "is not null"] diff --git a/guillotina/contrib/catalog/pg/parser.py b/guillotina/contrib/catalog/pg/parser.py index 5bd4d99a7..ecb0ee156 100644 --- a/guillotina/contrib/catalog/pg/parser.py +++ b/guillotina/contrib/catalog/pg/parser.py @@ -1,18 +1,17 @@ +import typing +import urllib + from dateutil.parser import parse + from guillotina import configure -from guillotina.catalog.parser import BaseParser -from guillotina.catalog.parser import to_list +from guillotina.catalog.parser import BaseParser, to_list from guillotina.catalog.types import BasicParsedQueryInfo from guillotina.catalog.utils import get_index_definition from guillotina.contrib.catalog.pg import logger from guillotina.contrib.catalog.pg.indexes import get_pg_index -from guillotina.interfaces import IResource -from guillotina.interfaces import ISearchParser +from guillotina.interfaces import IResource, ISearchParser from guillotina.interfaces.catalog import ICatalogUtility -import typing -import urllib - _type_mapping = {"int": int, "float": float} diff --git a/guillotina/contrib/catalog/pg/utility.py b/guillotina/contrib/catalog/pg/utility.py index 81c01cc6a..493eb6c04 100644 --- a/guillotina/contrib/catalog/pg/utility.py +++ b/guillotina/contrib/catalog/pg/utility.py @@ -1,3 +1,11 @@ +import json +import os +import typing + +import asyncpg.exceptions +import orjson +from zope.interface import implementer + from guillotina.api.content import DefaultGET from guillotina.auth.users import AnonymousUser from guillotina.catalog.catalog import DefaultSearchUtility @@ -5,41 +13,27 @@ from guillotina.component import get_utility from guillotina.const import TRASHED_ID from guillotina.contrib.catalog.pg import logger -from guillotina.contrib.catalog.pg.indexes import BasicJsonIndex -from guillotina.contrib.catalog.pg.indexes import get_pg_index -from guillotina.contrib.catalog.pg.indexes import get_pg_indexes +from guillotina.contrib.catalog.pg.indexes import BasicJsonIndex, get_pg_index, get_pg_indexes from guillotina.contrib.catalog.pg.parser import ParsedQueryInfo from guillotina.contrib.catalog.pg.utils import sqlq -from guillotina.db.interfaces import IPostgresStorage -from guillotina.db.interfaces import ITransaction -from guillotina.db.interfaces import IWriter +from guillotina.db.interfaces import IPostgresStorage, ITransaction, IWriter from guillotina.db.orm.interfaces import IBaseObject from guillotina.db.storages.utils import register_sql from guillotina.db.uid import MAX_UID_LENGTH -from guillotina.exceptions import ContainerNotFound -from guillotina.exceptions import RequestNotFound -from guillotina.exceptions import TransactionNotFound -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IFolder -from guillotina.interfaces import IPGCatalogUtility -from guillotina.interfaces import IResource +from guillotina.exceptions import ContainerNotFound, RequestNotFound, TransactionNotFound +from guillotina.interfaces import IDatabase, IFolder, IPGCatalogUtility, IResource from guillotina.interfaces.content import IApplication from guillotina.response import HTTPNotImplemented from guillotina.transactions import get_transaction -from guillotina.utils import find_container -from guillotina.utils import get_authenticated_user -from guillotina.utils import get_content_path -from guillotina.utils import get_current_request -from guillotina.utils import get_current_transaction -from guillotina.utils import get_object_url -from guillotina.utils import get_roles_principal -from zope.interface import implementer - -import asyncpg.exceptions -import json -import orjson -import os -import typing +from guillotina.utils import ( + find_container, + get_authenticated_user, + get_content_path, + get_current_request, + get_current_transaction, + get_object_url, + get_roles_principal, +) # 2019-06-15T18:37:31.008359+00:00 diff --git a/guillotina/contrib/dbusers/adapters.py b/guillotina/contrib/dbusers/adapters.py index 47ca275ab..9edf84559 100644 --- a/guillotina/contrib/dbusers/adapters.py +++ b/guillotina/contrib/dbusers/adapters.py @@ -1,9 +1,9 @@ -from .content.users import IUserManager -from guillotina import app_settings -from guillotina import configure +import re + +from guillotina import app_settings, configure from guillotina.interfaces import IIDChecker -import re +from .content.users import IUserManager # from https://github.com/theskumar/python-usernames/blob/master/usernames/validators.py diff --git a/guillotina/contrib/dbusers/content/groups.py b/guillotina/contrib/dbusers/content/groups.py index 741eb9041..22bdaa379 100644 --- a/guillotina/contrib/dbusers/content/groups.py +++ b/guillotina/contrib/dbusers/content/groups.py @@ -1,11 +1,10 @@ -from guillotina import configure -from guillotina import schema +from zope.interface import implementer + +from guillotina import configure, schema from guillotina.content import Folder from guillotina.contrib.dbusers import _ from guillotina.directives import index_field -from guillotina.interfaces import IFolder -from guillotina.interfaces import IPrincipal -from zope.interface import implementer +from guillotina.interfaces import IFolder, IPrincipal class IGroupManager(IFolder): diff --git a/guillotina/contrib/dbusers/content/users.py b/guillotina/contrib/dbusers/content/users.py index 2eecf3c4a..b362ba47d 100644 --- a/guillotina/contrib/dbusers/content/users.py +++ b/guillotina/contrib/dbusers/content/users.py @@ -1,15 +1,9 @@ -from guillotina import configure -from guillotina import schema -from guillotina.auth.validators import check_password -from guillotina.auth.validators import hash_password +from guillotina import configure, schema +from guillotina.auth.validators import check_password, hash_password from guillotina.content import Folder from guillotina.contrib.dbusers import _ -from guillotina.directives import index_field -from guillotina.directives import read_permission -from guillotina.directives import write_permission -from guillotina.interfaces import Allow -from guillotina.interfaces import IFolder -from guillotina.interfaces import IPrincipal +from guillotina.directives import index_field, read_permission, write_permission +from guillotina.interfaces import Allow, IFolder, IPrincipal from guillotina.response import HTTPUnauthorized diff --git a/guillotina/contrib/dbusers/install.py b/guillotina/contrib/dbusers/install.py index f32e713b9..3ea41e8cc 100644 --- a/guillotina/contrib/dbusers/install.py +++ b/guillotina/contrib/dbusers/install.py @@ -4,8 +4,7 @@ from guillotina.event import notify from guillotina.events import ObjectAddedEvent from guillotina.interfaces import ILayers -from guillotina.utils import get_authenticated_user_id -from guillotina.utils import get_registry +from guillotina.utils import get_authenticated_user_id, get_registry USERS_LAYER = "guillotina.contrib.dbusers.interfaces.IDBUsersLayer" diff --git a/guillotina/contrib/dbusers/register_user.py b/guillotina/contrib/dbusers/register_user.py index e9e4eb4ce..e3b2705c9 100644 --- a/guillotina/contrib/dbusers/register_user.py +++ b/guillotina/contrib/dbusers/register_user.py @@ -2,8 +2,7 @@ from guillotina.auth import authenticate_user from guillotina.content import create_content_in_container from guillotina.event import notify -from guillotina.events import ObjectAddedEvent -from guillotina.events import UserLogin +from guillotina.events import ObjectAddedEvent, UserLogin from guillotina.utils import get_current_container diff --git a/guillotina/contrib/dbusers/serializers.py b/guillotina/contrib/dbusers/serializers.py index 97d31a323..da72fb389 100644 --- a/guillotina/contrib/dbusers/serializers.py +++ b/guillotina/contrib/dbusers/serializers.py @@ -1,11 +1,10 @@ +from zope.interface import Interface + from guillotina import configure from guillotina.contrib.dbusers.content.groups import IGroup from guillotina.contrib.dbusers.content.users import IUser -from guillotina.interfaces import IPATCH -from guillotina.interfaces import IResourceDeserializeFromJson -from guillotina.interfaces import IResourceSerializeToJsonSummary +from guillotina.interfaces import IPATCH, IResourceDeserializeFromJson, IResourceSerializeToJsonSummary from guillotina.json.serialize_content import DefaultJSONSummarySerializer -from zope.interface import Interface @configure.adapter(for_=(IUser, Interface), provides=IResourceSerializeToJsonSummary) diff --git a/guillotina/contrib/dbusers/services/__init__.py b/guillotina/contrib/dbusers/services/__init__.py index 4e5a9e6d0..3ab5401e0 100644 --- a/guillotina/contrib/dbusers/services/__init__.py +++ b/guillotina/contrib/dbusers/services/__init__.py @@ -1,11 +1,12 @@ -from . import groups # noqa -from . import roles # noqa -from . import users # noqa from guillotina import configure from guillotina.api.content import DefaultPOST from guillotina.contrib.dbusers.content.groups import IGroupManager from guillotina.contrib.dbusers.content.users import IUserManager +from . import groups # noqa +from . import roles # noqa +from . import users # noqa + # override some views... configure.service(context=IGroupManager, method="POST", permission="guillotina.AddGroup", allow_access=True)( diff --git a/guillotina/contrib/dbusers/services/groups.py b/guillotina/contrib/dbusers/services/groups.py index 73257b856..38e0c342b 100644 --- a/guillotina/contrib/dbusers/services/groups.py +++ b/guillotina/contrib/dbusers/services/groups.py @@ -1,6 +1,10 @@ +import logging +import typing + +from zope.interface import alsoProvides + from guillotina import configure -from guillotina.api.content import DefaultDELETE -from guillotina.api.content import DefaultPATCH +from guillotina.api.content import DefaultDELETE, DefaultPATCH from guillotina.api.service import Service from guillotina.component import get_multi_adapter from guillotina.content import create_content_in_container @@ -8,17 +12,9 @@ from guillotina.contrib.dbusers.services.utils import ListGroupsOrUsersService from guillotina.event import notify from guillotina.events import ObjectAddedEvent -from guillotina.interfaces import IContainer -from guillotina.interfaces import IPATCH -from guillotina.interfaces import IResourceSerializeToJsonSummary -from guillotina.response import HTTPNotFound -from guillotina.response import HTTPPreconditionFailed -from guillotina.utils import navigate_to -from guillotina.utils import valid_id -from zope.interface import alsoProvides - -import logging -import typing +from guillotina.interfaces import IPATCH, IContainer, IResourceSerializeToJsonSummary +from guillotina.response import HTTPNotFound, HTTPPreconditionFailed +from guillotina.utils import navigate_to, valid_id logger = logging.getLogger("guillotina.contrib.dbusers") diff --git a/guillotina/contrib/dbusers/services/roles.py b/guillotina/contrib/dbusers/services/roles.py index 89508e702..1928d2280 100644 --- a/guillotina/contrib/dbusers/services/roles.py +++ b/guillotina/contrib/dbusers/services/roles.py @@ -1,7 +1,6 @@ from guillotina import configure from guillotina.api.service import Service -from guillotina.auth.role import global_roles -from guillotina.auth.role import local_roles +from guillotina.auth.role import global_roles, local_roles from guillotina.interfaces import IContainer diff --git a/guillotina/contrib/dbusers/services/users.py b/guillotina/contrib/dbusers/services/users.py index 886a7838d..49f7cdc15 100644 --- a/guillotina/contrib/dbusers/services/users.py +++ b/guillotina/contrib/dbusers/services/users.py @@ -1,21 +1,21 @@ +import typing + +from zope.interface import alsoProvides + from guillotina import configure -from guillotina.api.content import DefaultDELETE -from guillotina.api.content import DefaultPATCH +from guillotina.api.content import DefaultDELETE, DefaultPATCH from guillotina.api.service import Service -from guillotina.component import get_multi_adapter -from guillotina.component import queryMultiAdapter +from guillotina.component import get_multi_adapter, queryMultiAdapter from guillotina.contrib.dbusers.content.users import User from guillotina.contrib.dbusers.services.utils import ListGroupsOrUsersService -from guillotina.interfaces import IContainer -from guillotina.interfaces import IPATCH -from guillotina.interfaces import IResourceSerializeToJson -from guillotina.interfaces import IResourceSerializeToJsonSummary +from guillotina.interfaces import ( + IPATCH, + IContainer, + IResourceSerializeToJson, + IResourceSerializeToJsonSummary, +) from guillotina.response import HTTPNotFound -from guillotina.utils import get_authenticated_user -from guillotina.utils import navigate_to -from zope.interface import alsoProvides - -import typing +from guillotina.utils import get_authenticated_user, navigate_to @configure.service( diff --git a/guillotina/contrib/dbusers/services/utils.py b/guillotina/contrib/dbusers/services/utils.py index 4fbcd6fc4..706ac9d9e 100644 --- a/guillotina/contrib/dbusers/services/utils.py +++ b/guillotina/contrib/dbusers/services/utils.py @@ -1,15 +1,13 @@ +import typing as t + from guillotina.api.service import Service from guillotina.catalog.catalog import DefaultSearchUtility -from guillotina.component import get_multi_adapter -from guillotina.component import query_utility +from guillotina.component import get_multi_adapter, query_utility from guillotina.content import Container -from guillotina.interfaces import IAsyncContainer -from guillotina.interfaces import IResourceSerializeToJsonSummary +from guillotina.interfaces import IAsyncContainer, IResourceSerializeToJsonSummary from guillotina.interfaces.catalog import ICatalogUtility from guillotina.utils import navigate_to -import typing as t - class ListGroupsOrUsersService(Service): type_name: t.Optional[str] = None diff --git a/guillotina/contrib/dbusers/subscribers.py b/guillotina/contrib/dbusers/subscribers.py index 5886d51aa..8e894f3de 100644 --- a/guillotina/contrib/dbusers/subscribers.py +++ b/guillotina/contrib/dbusers/subscribers.py @@ -1,20 +1,20 @@ -from .content.groups import Group -from .content.users import User from guillotina import configure from guillotina.auth.validators import hash_password from guillotina.contrib.dbusers.content.groups import IGroup from guillotina.contrib.dbusers.content.users import IUser from guillotina.event import notify -from guillotina.events import BeforeObjectModifiedEvent -from guillotina.events import NewUserAdded -from guillotina.events import ObjectAddedEvent -from guillotina.interfaces import IBeforeObjectModifiedEvent -from guillotina.interfaces import IBeforeObjectRemovedEvent -from guillotina.interfaces import IObjectAddedEvent -from guillotina.interfaces import IPrincipalRoleManager +from guillotina.events import BeforeObjectModifiedEvent, NewUserAdded, ObjectAddedEvent +from guillotina.interfaces import ( + IBeforeObjectModifiedEvent, + IBeforeObjectRemovedEvent, + IObjectAddedEvent, + IPrincipalRoleManager, +) from guillotina.response import HTTPPreconditionFailed -from guillotina.utils import get_current_container -from guillotina.utils import navigate_to +from guillotina.utils import get_current_container, navigate_to + +from .content.groups import Group +from .content.users import User @configure.subscriber(for_=(IUser, IObjectAddedEvent)) diff --git a/guillotina/contrib/dbusers/users.py b/guillotina/contrib/dbusers/users.py index 5de8c8678..33c483d9a 100644 --- a/guillotina/contrib/dbusers/users.py +++ b/guillotina/contrib/dbusers/users.py @@ -1,15 +1,14 @@ -from .services.utils import NoCatalogException +import typing + from guillotina.component import query_utility from guillotina.contrib.catalog.pg.utility import PGSearchUtility -from guillotina.exceptions import ContainerNotFound -from guillotina.exceptions import TransactionNotFound +from guillotina.exceptions import ContainerNotFound, TransactionNotFound from guillotina.interfaces import IPrincipal from guillotina.interfaces.catalog import ICatalogUtility from guillotina.transactions import get_transaction -from guillotina.utils import get_current_container -from guillotina.utils import navigate_to +from guillotina.utils import get_current_container, navigate_to -import typing +from .services.utils import NoCatalogException class DBUserIdentifier: diff --git a/guillotina/contrib/dyncontent/__init__.py b/guillotina/contrib/dyncontent/__init__.py index f35ca1e7d..1ee75244d 100644 --- a/guillotina/contrib/dyncontent/__init__.py +++ b/guillotina/contrib/dyncontent/__init__.py @@ -1,6 +1,6 @@ +from typing import Any, Dict + from guillotina import configure -from typing import Any -from typing import Dict app_settings: Dict[str, Any] = {} diff --git a/guillotina/contrib/dyncontent/subscriber.py b/guillotina/contrib/dyncontent/subscriber.py index 9811aff3a..868aec0e4 100644 --- a/guillotina/contrib/dyncontent/subscriber.py +++ b/guillotina/contrib/dyncontent/subscriber.py @@ -1,33 +1,20 @@ -from guillotina import app_settings -from guillotina import BEHAVIOR_CACHE -from guillotina import configure -from guillotina import FACTORY_CACHE -from guillotina.component import get_global_components -from guillotina.component import get_utility -from guillotina.component import query_utility -from guillotina.content import get_cached_factory -from guillotina.content import load_cached_schema -from guillotina.contrib.dyncontent import behaviors -from guillotina.contrib.dyncontent import contents -from guillotina.contrib.dyncontent.vocabularies import AppSettingSource -from guillotina.directives import index_field -from guillotina.directives import metadata -from guillotina.directives import read_permission -from guillotina.directives import write_permission -from guillotina.interfaces import IApplication -from guillotina.interfaces import IApplicationInitializedEvent -from guillotina.interfaces import IBehavior -from guillotina.interfaces import IResourceFactory -from guillotina.schema.vocabulary import SimpleTerm -from guillotina.schema.vocabulary import SimpleVocabulary -from guillotina.utils import import_class -from zope.interface import Interface -from zope.interface.interface import InterfaceClass - import json import logging import typing +from zope.interface import Interface +from zope.interface.interface import InterfaceClass + +from guillotina import BEHAVIOR_CACHE, FACTORY_CACHE, app_settings, configure +from guillotina.component import get_global_components, get_utility, query_utility +from guillotina.content import get_cached_factory, load_cached_schema +from guillotina.contrib.dyncontent import behaviors, contents +from guillotina.contrib.dyncontent.vocabularies import AppSettingSource +from guillotina.directives import index_field, metadata, read_permission, write_permission +from guillotina.interfaces import IApplication, IApplicationInitializedEvent, IBehavior, IResourceFactory +from guillotina.schema.vocabulary import SimpleTerm, SimpleVocabulary +from guillotina.utils import import_class + SUPPORTED_DIRECTIVES = { "index": index_field, diff --git a/guillotina/contrib/dyncontent/vocabularies.py b/guillotina/contrib/dyncontent/vocabularies.py index 45a492b98..74b19c935 100644 --- a/guillotina/contrib/dyncontent/vocabularies.py +++ b/guillotina/contrib/dyncontent/vocabularies.py @@ -1,8 +1,8 @@ -from guillotina import app_settings -from guillotina import configure +from zope.interface import implementer + +from guillotina import app_settings, configure from guillotina.contrib.dyncontent.exceptions import VocabularySettingNotFound from guillotina.schema.interfaces import ISource -from zope.interface import implementer @implementer(ISource) diff --git a/guillotina/contrib/email_validation/interfaces.py b/guillotina/contrib/email_validation/interfaces.py index 14764dbc5..05dee89d9 100644 --- a/guillotina/contrib/email_validation/interfaces.py +++ b/guillotina/contrib/email_validation/interfaces.py @@ -1,6 +1,7 @@ -from guillotina import schema from zope.interface import Interface +from guillotina import schema + class IValidationSettings(Interface): diff --git a/guillotina/contrib/email_validation/utility.py b/guillotina/contrib/email_validation/utility.py index fab4bebc5..329ccafbd 100644 --- a/guillotina/contrib/email_validation/utility.py +++ b/guillotina/contrib/email_validation/utility.py @@ -1,22 +1,23 @@ +import logging + +from jsonschema import validate as jsonvalidate +from jsonschema.exceptions import ValidationError + from guillotina import app_settings from guillotina.component import get_utility from guillotina.contrib.email_validation.interfaces import IValidationSettings -from guillotina.contrib.email_validation.utils import extract_validation_token -from guillotina.contrib.email_validation.utils import generate_validation_token +from guillotina.contrib.email_validation.utils import extract_validation_token, generate_validation_token from guillotina.contrib.templates.interfaces import IJinjaUtility from guillotina.event import notify from guillotina.events import ValidationEvent from guillotina.interfaces import IMailer -from guillotina.response import HTTPNotImplemented -from guillotina.response import HTTPPreconditionFailed -from guillotina.response import HTTPServiceUnavailable -from guillotina.response import HTTPUnauthorized -from guillotina.utils import get_registry -from guillotina.utils import resolve_dotted_name -from jsonschema import validate as jsonvalidate -from jsonschema.exceptions import ValidationError - -import logging +from guillotina.response import ( + HTTPNotImplemented, + HTTPPreconditionFailed, + HTTPServiceUnavailable, + HTTPUnauthorized, +) +from guillotina.utils import get_registry, resolve_dotted_name logger = logging.getLogger("guillotina.email_validation") diff --git a/guillotina/contrib/email_validation/utils.py b/guillotina/contrib/email_validation/utils.py index c060ff1f8..91ca9f868 100644 --- a/guillotina/contrib/email_validation/utils.py +++ b/guillotina/contrib/email_validation/utils.py @@ -1,13 +1,14 @@ +import logging +import time from datetime import datetime -from guillotina import app_settings -from guillotina.utils import get_jwk_key -from jwcrypto import jwe -from jwcrypto.common import json_encode -import logging import orjson import pytz -import time +from jwcrypto import jwe +from jwcrypto.common import json_encode + +from guillotina import app_settings +from guillotina.utils import get_jwk_key logger = logging.getLogger("guillotina.email_validation") diff --git a/guillotina/contrib/image/api.py b/guillotina/contrib/image/api.py index 80274ab30..79fc3f620 100644 --- a/guillotina/contrib/image/api.py +++ b/guillotina/contrib/image/api.py @@ -1,7 +1,8 @@ from functools import partial +from io import BytesIO + from guillotina import configure -from guillotina.api.files import _traversed_file_doc -from guillotina.api.files import DownloadFile +from guillotina.api.files import DownloadFile, _traversed_file_doc from guillotina.api.service import TraversableFieldService from guillotina.component import get_multi_adapter from guillotina.contrib.image.interfaces import IImagingSettings @@ -11,12 +12,9 @@ from guillotina.events import ObjectModifiedEvent from guillotina.interfaces import IFileManager from guillotina.interfaces.content import IResource -from guillotina.response import HTTPNoContent -from guillotina.response import HTTPNotFound +from guillotina.response import HTTPNoContent, HTTPNotFound from guillotina.schema.interfaces import IOrderedDict -from guillotina.utils import get_registry -from guillotina.utils import run_async -from io import BytesIO +from guillotina.utils import get_registry, run_async BUFFER = 262144 diff --git a/guillotina/contrib/image/behaviors.py b/guillotina/contrib/image/behaviors.py index 57efcaa67..d504eec50 100644 --- a/guillotina/contrib/image/behaviors.py +++ b/guillotina/contrib/image/behaviors.py @@ -1,10 +1,10 @@ from collections import OrderedDict as NativeOrderedDict + +from zope.interface import Interface + from guillotina import configure from guillotina.contrib.image.image import CloudImageFileField -from guillotina.schema import Dict -from guillotina.schema import OrderedDict -from guillotina.schema import TextLine -from zope.interface import Interface +from guillotina.schema import Dict, OrderedDict, TextLine class IImageAttachmentMarker(Interface): diff --git a/guillotina/contrib/image/image.py b/guillotina/contrib/image/image.py index a6f904120..c1941869a 100644 --- a/guillotina/contrib/image/image.py +++ b/guillotina/contrib/image/image.py @@ -1,7 +1,8 @@ +from zope.interface import implementer + from guillotina.contrib.image.interfaces import IImageFile from guillotina.interfaces.files import ICloudFileField from guillotina.schema import Object -from zope.interface import implementer class ICloudImageFileField(ICloudFileField): diff --git a/guillotina/contrib/image/interfaces.py b/guillotina/contrib/image/interfaces.py index 822b4dabe..848acc3aa 100644 --- a/guillotina/contrib/image/interfaces.py +++ b/guillotina/contrib/image/interfaces.py @@ -1,7 +1,8 @@ +from zope.interface import Interface + from guillotina import schema from guillotina.contrib.image.preview import CloudPreviewImageFileField from guillotina.interfaces import IFile -from zope.interface import Interface class IImageFile(IFile): diff --git a/guillotina/contrib/image/preview.py b/guillotina/contrib/image/preview.py index b73e99f17..3d429780a 100644 --- a/guillotina/contrib/image/preview.py +++ b/guillotina/contrib/image/preview.py @@ -1,8 +1,8 @@ -from guillotina.interfaces.files import ICloudFileField -from guillotina.interfaces.files import IFileField -from guillotina.schema import Object from zope.interface import implementer +from guillotina.interfaces.files import ICloudFileField, IFileField +from guillotina.schema import Object + class ICloudPreviewImageFileField(ICloudFileField): """Preview on the cloud file""" diff --git a/guillotina/contrib/image/scale.py b/guillotina/contrib/image/scale.py index 89f8a38c5..93247dfdc 100644 --- a/guillotina/contrib/image/scale.py +++ b/guillotina/contrib/image/scale.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # Code from plone.scale +import math +import sys +import warnings from io import BytesIO -import math import PIL.Image import PIL.ImageFile -import sys -import warnings def none_as_int(the_int): diff --git a/guillotina/contrib/mailer/__init__.py b/guillotina/contrib/mailer/__init__.py index 300270753..25ae4e623 100644 --- a/guillotina/contrib/mailer/__init__.py +++ b/guillotina/contrib/mailer/__init__.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- +import logging + from guillotina import configure from guillotina.component import provide_utility from guillotina.interfaces import IMailer from guillotina.utils import import_class -import logging - logger = logging.getLogger("guillotina.contrib.mailer") diff --git a/guillotina/contrib/mailer/encoding.py b/guillotina/contrib/mailer/encoding.py index bd5e63aba..215dd10b0 100644 --- a/guillotina/contrib/mailer/encoding.py +++ b/guillotina/contrib/mailer/encoding.py @@ -1,6 +1,5 @@ # pulled out of repoze.sendmail -from email import header -from email import utils +from email import header, utils # From http://tools.ietf.org/html/rfc5322#section-3.6 diff --git a/guillotina/contrib/mailer/utility.py b/guillotina/contrib/mailer/utility.py index a9af02e0e..46a2c6627 100644 --- a/guillotina/contrib/mailer/utility.py +++ b/guillotina/contrib/mailer/utility.py @@ -1,26 +1,21 @@ # -*- coding: utf-8 -*- +import asyncio +import logging +import socket +import time from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import formatdate -from guillotina import app_settings -from guillotina import configure +from typing import Any, List, Optional, Union + +from zope.interface import implementer + +from guillotina import app_settings, configure from guillotina.component import query_utility from guillotina.contrib.mailer import encoding from guillotina.contrib.mailer.exceptions import NoEndpointDefinedException -from guillotina.interfaces import IMailEndpoint -from guillotina.interfaces import IMailer -from guillotina.utils import get_random_string -from guillotina.utils import notice_on_error -from typing import Any -from typing import List -from typing import Optional -from typing import Union -from zope.interface import implementer - -import asyncio -import logging -import socket -import time +from guillotina.interfaces import IMailEndpoint, IMailer +from guillotina.utils import get_random_string, notice_on_error try: diff --git a/guillotina/contrib/mcp/__init__.py b/guillotina/contrib/mcp/__init__.py new file mode 100644 index 000000000..26dfdfb30 --- /dev/null +++ b/guillotina/contrib/mcp/__init__.py @@ -0,0 +1,24 @@ +from guillotina import configure + + +app_settings = { + "mcp": { + "enabled": True, + "server_name": "guillotina-mcp", + "default_child_limit": 50, + }, + "load_utilities": { + "mcp_tool_registry": { + "provides": "guillotina.contrib.mcp.interfaces.IMCPToolRegistry", + "factory": "guillotina.contrib.mcp.backend.MCPToolRegistry", + "settings": {}, + } + }, +} + + +def includeme(root, settings): + configure.scan("guillotina.contrib.mcp.install") + configure.scan("guillotina.contrib.mcp.permissions") + configure.scan("guillotina.contrib.mcp.services") + configure.scan("guillotina.contrib.mcp.subscribers") diff --git a/guillotina/contrib/mcp/backend.py b/guillotina/contrib/mcp/backend.py new file mode 100644 index 000000000..840c9be40 --- /dev/null +++ b/guillotina/contrib/mcp/backend.py @@ -0,0 +1,270 @@ +import asyncio +import hashlib +import json +import logging +from dataclasses import dataclass +from typing import Any, Awaitable, Callable, Dict, List, Optional + +from guillotina import app_settings +from guillotina.contrib.mcp import resources as mcp_resources +from guillotina.contrib.mcp import tools +from guillotina.contrib.redis import get_driver + + +ToolHandler = Callable[[Any, Any, Dict[str, Any]], Awaitable[Dict[str, Any]]] +ResourceHandler = Callable[[Any], Awaitable[Dict[str, Any]]] +logger = logging.getLogger("guillotina.contrib.redis") + + +@dataclass +class MCPTool: + name: str + description: str + input_schema: Dict[str, Any] + handler: ToolHandler + cacheable: bool = False + + +@dataclass +class MCPResource: + name: str + uri: str + description: str + endpoint: str + handler: ResourceHandler + mime_type: str = "application/json" + + +class MCPToolRegistry: + def __init__(self, settings: Optional[Dict[str, Any]] = None): + config = app_settings.get("mcp", {}) + self._settings = settings or {} + self._enabled = bool(config.get("enabled", True)) + self._server_name = str(config.get("server_name", "guillotina-mcp")) + self._default_child_limit = int(config.get("default_child_limit", 50)) + self._tools: Dict[str, MCPTool] = {} + self._resources: Dict[str, MCPResource] = {} + self._register_default_tools() + self._register_default_resources() + self._key_cache_redis_prefix = "mcp_tool_cache:v1" + self._queue_invalidations: asyncio.Queue[str] = asyncio.Queue(maxsize=1) + self._worker_task: asyncio.Task = None + + async def initialize(self, app): + self._cache_disabled = True + self._driver_redis = None + try: + self._driver_redis = await get_driver() + self._cache_disabled = False + except Exception: + logger.info("redis not enabled to cache") + self._worker_task = asyncio.create_task(self.invalidate_cache_worker()) + + async def invalidate_cache_worker(self): + while True: + try: + reason_cache = await self._queue_invalidations.get() + except asyncio.CancelledError: + logger.info("Invalidation cache task cancelled") + return + + try: + await self.invalidate_cache(reason_cache) + except Exception: + logger.error("Error in invalidating cache", exc_info=True) + await asyncio.sleep(0.5) + finally: + self._queue_invalidations.task_done() + + async def finalize(self, app=None): + # Stop invalidate cache task + if self._worker_task is not None: + self._worker_task.cancel() + await asyncio.gather(self._worker_task, return_exceptions=True) + + def schedule_invalidate_cache(self, reason: str = "manual"): + # Keep the existing task running, ignore the new one, drop the + # Queue is size 1 + try: + self._queue_invalidations.put_nowait(reason) + except asyncio.QueueFull: + pass + + def _register_default_tools(self) -> None: + for ( + tool_name, + description, + input_schema, + handler, + cacheable, + ) in tools.default_tools(self._default_child_limit): + self.register_tool( + name=tool_name, + description=description, + input_schema=input_schema, + handler=handler, + cacheable=cacheable, + ) + + def _register_default_resources(self) -> None: + for ( + name, + uri, + description, + endpoint, + handler, + ) in mcp_resources.default_resources(): + self.register_resource( + name=name, + uri=uri, + description=description, + endpoint=endpoint, + handler=handler, + ) + + def is_enabled(self) -> bool: + return self._enabled + + def register_tool( + self, + *, + name: str, + description: str, + input_schema: Dict[str, Any], + handler: ToolHandler, + cacheable: bool = False, + ) -> None: + clean_name = str(name or "").strip() + if not clean_name: + raise ValueError("Tool name is required") + self._tools[clean_name] = MCPTool( + name=clean_name, + description=str(description or "").strip(), + input_schema=input_schema or {"type": "object"}, + handler=handler, + cacheable=cacheable, + ) + + def list_tools(self) -> List[Dict[str, Any]]: + return [ + { + "name": tool.name, + "description": tool.description, + "inputSchema": tool.input_schema, + "cacheable": tool.cacheable, + } + for tool in sorted(self._tools.values(), key=lambda registered: registered.name) + ] + + # ── Resource management ────────────────────────────────────────── + + def register_resource( + self, + *, + name: str, + uri: str, + description: str, + endpoint: str, + handler: ResourceHandler, + mime_type: str = "application/json", + ) -> None: + clean_name = str(name or "").strip() + if not clean_name: + raise ValueError("Resource name is required") + self._resources[clean_name] = MCPResource( + name=clean_name, + uri=str(uri or "").strip(), + description=str(description or "").strip(), + endpoint=str(endpoint or "").strip(), + handler=handler, + mime_type=mime_type, + ) + + def list_resources(self) -> List[Dict[str, Any]]: + return [ + { + "uri": res.uri, + "name": res.name, + "description": res.description, + "endpoint": res.endpoint, + "mimeType": res.mime_type, + } + for res in sorted(self._resources.values(), key=lambda r: r.name) + ] + + async def read_resource(self, resource_name: str, context: Any, request: Any) -> Dict[str, Any]: + clean_name = str(resource_name or "").strip() + if clean_name not in self._resources: + raise ValueError(f"Unknown MCP resource: {resource_name}") + resource = self._resources[clean_name] + return await resource.handler(request) + + def _cache_key(self, tool_name: str, arguments: Dict[str, Any]) -> str: + payload = json.dumps(arguments, sort_keys=True, separators=(",", ":"), default=str) + digest = hashlib.sha256(payload.encode("utf-8")).hexdigest() + return f"{self._key_cache_redis_prefix}:{tool_name}:{digest[:16]}" + + def _serialize_cache_value(self, value: Dict[str, Any]) -> str: + return json.dumps(value, separators=(",", ":"), sort_keys=True) + + def _deserialize_cache_value(self, value: Any) -> Dict[str, Any]: + if isinstance(value, bytes): + value = value.decode("utf-8") + if isinstance(value, str): + return json.loads(value) + return value + + async def invoke( + self, + tool_name: str, + context: Any, + request: Any, + arguments: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + clean_name = str(tool_name or "").strip() + if clean_name not in self._tools: + raise ValueError(f"Unknown MCP tool: {tool_name}") + + clean_arguments = arguments or {} + if not isinstance(clean_arguments, dict): + raise ValueError("Tool arguments must be an object") + + tool = self._tools[clean_name] + cache_key = self._cache_key(clean_name, clean_arguments) + if tool.cacheable and self._cache_disabled is False: + result = await self._driver_redis.get(cache_key) + if result is not None: + return self._deserialize_cache_value(result) + result = await tool.handler(context, request, clean_arguments) + if tool.cacheable and self._cache_disabled is False: + # Expire in 1 hour + await self._driver_redis.set( + key=cache_key, + data=self._serialize_cache_value(result), + expire=3600, + ) + return result + + async def invalidate_cache(self, reason: str = "manual") -> None: + if self._cache_disabled is False: + keys_to_delete = await self._driver_redis.keys_startswith(self._key_cache_redis_prefix) + await self._driver_redis.delete_all(keys_to_delete) + + def metadata(self) -> Dict[str, Any]: + return { + "enabled": self.is_enabled(), + "server_name": self._server_name, + "tool_count": len(self._tools), + "resource_count": len(self._resources), + } + + def create_lowlevel_server(self, context: Any = None, request: Any = None) -> Any: + from guillotina.contrib.mcp.server import LowLevelMCPServer + + adapter = LowLevelMCPServer( + registry=self, + context=context, + request=request, + server_name=self._server_name, + ) + return adapter.build() diff --git a/guillotina/contrib/mcp/install.py b/guillotina/contrib/mcp/install.py new file mode 100644 index 000000000..f694bd2ec --- /dev/null +++ b/guillotina/contrib/mcp/install.py @@ -0,0 +1,16 @@ +from guillotina import configure +from guillotina.addons import Addon +from guillotina.contrib.mcp.interfaces import IMCPSettings +from guillotina.utils import get_registry + + +@configure.addon(name="mcp", title="Guillotina MCP integration") +class MCPAddon(Addon): + @classmethod + async def install(cls, container, request): + registry = await get_registry() + registry.register_interface(IMCPSettings) + + @classmethod + async def uninstall(cls, container, request): + pass diff --git a/guillotina/contrib/mcp/interfaces.py b/guillotina/contrib/mcp/interfaces.py new file mode 100644 index 000000000..957f61cbc --- /dev/null +++ b/guillotina/contrib/mcp/interfaces.py @@ -0,0 +1,32 @@ +from zope.interface import Interface + +from guillotina import schema + + +class IMCPSettings(Interface): + enabled = schema.Bool(title="Enable MCP services", default=True, required=False) + server_name = schema.TextLine(title="Low-level MCP server name", default="guillotina-mcp", required=False) + default_child_limit = schema.Int(title="Default list_children limit", default=50, required=False) + + +class IMCPToolRegistry(Interface): + def list_tools(): + """Return registered MCP tools.""" + + def list_resources(): + """Return registered MCP resources.""" + + async def invoke(tool_name, context, request, arguments=None): + """Execute one tool and return a JSON-serializable response.""" + + async def read_resource(resource_name, context, request): + """Read one resource and return a JSON-serializable response.""" + + def metadata(): + """Return metadata for diagnostics.""" + + async def invalidate_cache(reason="manual"): + """Invalidate cached tool responses.""" + + def create_lowlevel_server(context=None, request=None): + """Build a low-level MCP server object.""" diff --git a/guillotina/contrib/mcp/permissions.py b/guillotina/contrib/mcp/permissions.py new file mode 100644 index 000000000..9e0d23880 --- /dev/null +++ b/guillotina/contrib/mcp/permissions.py @@ -0,0 +1,9 @@ +from guillotina import configure + + +configure.permission("guillotina.MCPView", "View MCP integration services") +configure.permission("guillotina.MCPExecute", "Execute MCP tools") + +configure.grant(permission="guillotina.MCPView", role="guillotina.Manager") +configure.grant(permission="guillotina.MCPView", role="guillotina.Owner") +configure.grant(permission="guillotina.MCPExecute", role="guillotina.Manager") diff --git a/guillotina/contrib/mcp/resources.py b/guillotina/contrib/mcp/resources.py new file mode 100644 index 000000000..059ecc970 --- /dev/null +++ b/guillotina/contrib/mcp/resources.py @@ -0,0 +1,199 @@ +from typing import Any, Dict + +from guillotina import __version__, app_settings +from guillotina.component import query_utility +from guillotina.interfaces.catalog import ICatalogUtility +from guillotina.transactions import get_transaction +from guillotina.utils import get_content_path, get_current_container, navigate_to + + +async def mcp_info_resource(request) -> Dict[str, Any]: + container = get_current_container() + return { + "version": __version__, + "container_id": getattr(container, "id", None) if container else None, + "container_path": get_content_path(container) if container else None, + "enabled_addons": getattr(container, "addons", []), + } + + +async def mcp_health_resource(request) -> Dict[str, Any]: + db_status = "unknown" + try: + txn = get_transaction() + if txn and txn.storage: + conn = await txn.get_connection() + if conn: + db_status = "ok" + except Exception: + db_status = "error" + + return { + "status": "ok" if db_status == "ok" else "degraded", + "db": db_status, + "cache": "ok", # stub - extend if cache utility is available + } + + +async def mcp_config_resource(request) -> Dict[str, Any]: + mcp_settings = app_settings.get("mcp", {}) + return { + "mcp": { + "enabled": mcp_settings.get("enabled", False), + "server_name": mcp_settings.get("server_name", "guillotina-mcp"), + "default_child_limit": mcp_settings.get("default_child_limit", 50), + }, + "applications": app_settings.get("applications", []), + } + + +async def mcp_users_resource(request) -> Dict[str, Any]: + """ + Returns a list of users if dbusers contrib is enabled. + """ + container = get_current_container() + if not container: + return {"error": "No container available"} + + try: + users_folder = await navigate_to(container, "users") + except (KeyError, AttributeError): + return { + "error": "Users folder not found. Ensure guillotina.contrib.dbusers is enabled.", + "users": [], + } + + catalog = query_utility(ICatalogUtility) + if catalog: + try: + query = { + "type_name": "User", + "_metadata": "id,user_name,user_email,user_roles,user_groups", + } + result = await catalog.search(container, query) + users = [] + for hit in result.get("items", []): + users.append( + { + "id": hit.get("id"), + "name": hit.get("user_name"), + "email": hit.get("user_email"), + "roles": hit.get("user_roles", []), + "groups": hit.get("user_groups", []), + } + ) + return {"users": users} + except Exception: + # Catalog search failed, fall back to async_items + pass + + users = [] + try: + async for user_id, user in users_folder.async_items(): + users.append( + { + "id": user_id, + "name": getattr(user, "name", None), + "email": getattr(user, "email", None), + "roles": list(getattr(user, "user_roles", [])), + "groups": list(getattr(user, "user_groups", [])), + } + ) + except Exception as e: + return {"error": f"Failed to list users: {str(e)}", "users": []} + + return {"users": users} + + +async def mcp_catalog_resource(request) -> Dict[str, Any]: + catalog = query_utility(ICatalogUtility) + if catalog is None: + return { + "available": False, + "note": "No catalog utility configured.", + } + + container = get_current_container() + return { + "available": True, + "catalog_type": catalog.__class__.__name__, + "container": getattr(container, "id", None) if container else None, + } + + +async def mcp_summary_resource(request) -> Dict[str, Any]: + path = request.query.get("path", "/") if hasattr(request, "query") else "/" + container = get_current_container() + if not container: + return {"error": "No container available"} + + try: + if path == "/": + resource = container + else: + resource = await navigate_to(container, path) + + summary = { + "path": get_content_path(resource), + "id": getattr(resource, "id", getattr(resource, "__name__", None)), + "@type": getattr(resource, "type_name", resource.__class__.__name__), + "title": getattr(resource, "title", None), + } + + # Add child count if it's a folder-like resource + if hasattr(resource, "async_len"): + try: + summary["children_count"] = await resource.async_len() + except Exception: + pass + + return summary + except (KeyError, AttributeError) as e: + return {"error": f"Resource not found at path: {path}", "details": str(e)} + + +def default_resources(): + return [ + ( + "info", + "guillotina://resources/info", + "Guillotina version, container id and enabled add-ons.", + "@mcp/resources/info", + mcp_info_resource, + ), + ( + "health", + "guillotina://resources/health", + "Database and cache health status.", + "@mcp/resources/health", + mcp_health_resource, + ), + ( + "config", + "guillotina://resources/config", + "MCP settings and loaded applications.", + "@mcp/resources/config", + mcp_config_resource, + ), + ( + "users", + "guillotina://resources/users", + "List users registered in the container (dbusers).", + "@mcp/resources/users", + mcp_users_resource, + ), + ( + "catalog", + "guillotina://resources/catalog", + "Catalog availability and type info.", + "@mcp/resources/catalog", + mcp_catalog_resource, + ), + ( + "summary", + "guillotina://resources/summary", + "Summary of a resource at a given path (accepts ?path=/).", + "@mcp/resources/summary", + mcp_summary_resource, + ), + ] diff --git a/guillotina/contrib/mcp/server.py b/guillotina/contrib/mcp/server.py new file mode 100644 index 000000000..32bc5da70 --- /dev/null +++ b/guillotina/contrib/mcp/server.py @@ -0,0 +1,125 @@ +import importlib +import json +from typing import Any +from urllib.parse import parse_qs, urlparse + + +class _RequestWithUriParams: + """Thin proxy that overlays URI query params on top of the real HTTP request's query dict.""" + + def __init__(self, request: Any, uri_params: dict): + self._request = request + self._uri_params = uri_params + + @property + def query(self): + orig = dict(getattr(self._request, "query", {}) or {}) + return {**orig, **self._uri_params} + + def __getattr__(self, name: str): + return getattr(self._request, name) + + +class LowLevelMCPServer: + def __init__( + self, *, registry: Any, context: Any = None, request: Any = None, server_name: str = "guillotina-mcp" + ): + self.registry = registry + self.context = context + self.request = request + self.server_name = server_name + + def _load_lowlevel_modules(self): + try: + lowlevel = importlib.import_module("mcp.server.lowlevel") + types_module = importlib.import_module("mcp.types") + except ImportError as exc: + raise RuntimeError( + 'Low-level MCP SDK is missing. Install it with `pip install "guillotina[mcp]"`.' + ) from exc + return lowlevel, types_module + + def _build_tool_type(self, types_module: Any, tool_data: Any) -> Any: + try: + return types_module.Tool( + name=tool_data["name"], + description=tool_data["description"], + inputSchema=tool_data["inputSchema"], + ) + except TypeError: + return types_module.Tool( + name=tool_data["name"], + description=tool_data["description"], + input_schema=tool_data["inputSchema"], + ) + + def _build_resource_type(self, types_module: Any, resource_data: Any) -> Any: + from pydantic import AnyUrl + + uri_str = resource_data["uri"] + try: + uri = AnyUrl(uri_str) + except Exception: + uri = uri_str # type: ignore[assignment] + try: + return types_module.Resource( + uri=uri, + name=resource_data["name"], + description=resource_data.get("description", ""), + mimeType=resource_data.get("mimeType", "application/json"), + ) + except TypeError: + return types_module.Resource( + uri=uri, + name=resource_data["name"], + description=resource_data.get("description", ""), + mime_type=resource_data.get("mimeType", "application/json"), + ) + + def _build_text_content_type(self, types_module: Any, text: str) -> Any: + try: + return types_module.TextContent(type="text", text=text) + except TypeError: + return types_module.TextContent(text=text) + + def build(self) -> Any: + lowlevel, types_module = self._load_lowlevel_modules() + server = lowlevel.Server(self.server_name) + + @server.list_tools() + async def handle_list_tools(): + return [ + self._build_tool_type(types_module, tool_data) for tool_data in self.registry.list_tools() + ] + + @server.list_resources() + async def handle_list_resources(): + return [ + self._build_resource_type(types_module, res_data) + for res_data in self.registry.list_resources() + ] + + @server.read_resource() + async def handle_read_resource(uri: Any) -> str: + if self.context is None or self.request is None: + raise ValueError("Context and request are required to read Guillotina MCP resources") + uri_str = str(uri) + parsed = urlparse(uri_str) + base_uri = parsed._replace(query="").geturl() + uri_params = {k: v[0] for k, v in parse_qs(parsed.query).items()} if parsed.query else {} + for res in self.registry.list_resources(): + if res["uri"] == base_uri: + request = _RequestWithUriParams(self.request, uri_params) if uri_params else self.request + data = await self.registry.read_resource(res["name"], self.context, request) + return json.dumps(data, ensure_ascii=True, sort_keys=True, default=str) + raise ValueError(f"Unknown resource URI: {uri}") + + @server.call_tool() + async def handle_call_tool(name: str, arguments: Any): + if self.context is None or self.request is None: + raise ValueError("Context and request are required to execute Guillotina MCP tools") + result = await self.registry.invoke(name, self.context, self.request, arguments or {}) + payload = json.dumps(result, ensure_ascii=True, sort_keys=True, default=str) + return [self._build_text_content_type(types_module, payload)] + + return server diff --git a/guillotina/contrib/mcp/services.py b/guillotina/contrib/mcp/services.py new file mode 100644 index 000000000..40bc0c2db --- /dev/null +++ b/guillotina/contrib/mcp/services.py @@ -0,0 +1,70 @@ +from guillotina import configure +from guillotina.api.service import Service +from guillotina.component import query_utility +from guillotina.contrib.mcp.interfaces import IMCPToolRegistry +from guillotina.interfaces import IResource +from guillotina.response import HTTPNotFound, HTTPServiceUnavailable, Response + + +def _get_registry(): + registry = query_utility(IMCPToolRegistry) + if registry is None: + raise HTTPServiceUnavailable(content={"reason": "MCP registry utility is not available"}) + return registry + + +@configure.service( + method="POST", + context=IResource, + name="@mcp/{action}", + permission="guillotina.MCPExecute", + summary="MCP Streamable HTTP protocol endpoint (JSON-RPC 2.0)", + allow_access=True, +) +class MCPActionPostService(Service): + async def __call__(self): + action = self.request.matchdict.get("action", "") + if action == "protocol": + return await self._handle_protocol() + raise HTTPNotFound(content={"reason": f"Unknown MCP POST action: {action}"}) + + async def _handle_protocol(self): + try: + import anyio + from mcp.server.streamable_http import StreamableHTTPServerTransport + except ImportError as exc: + raise HTTPServiceUnavailable( + content={"reason": 'MCP SDK missing. Install "guillotina[mcp]".'} + ) from exc + + registry = _get_registry() + transport = StreamableHTTPServerTransport( + mcp_session_id=None, + is_json_response_enabled=True, + ) + server_obj = registry.create_lowlevel_server(context=self.context, request=self.request) + init_options = server_obj.create_initialization_options() + + async with transport.connect() as (read_stream, write_stream): + async with anyio.create_task_group() as tg: + tg.start_soon( + server_obj.run, + read_stream, + write_stream, + init_options, + False, # raise_exceptions + True, # stateless — allows each request to be handled independently + ) + await transport.handle_request( + self.request.scope, + self.request.receive, + self.request.send, + ) + tg.cancel_scope.cancel() + + # Response was already written directly to the ASGI socket by the transport. + # Return a pre-marked Response so Guillotina does not write an extra response. + resp = Response(status=200) + resp._prepared = True + resp._eof_sent = True + return resp diff --git a/guillotina/contrib/mcp/subscribers.py b/guillotina/contrib/mcp/subscribers.py new file mode 100644 index 000000000..56aeeabc3 --- /dev/null +++ b/guillotina/contrib/mcp/subscribers.py @@ -0,0 +1,20 @@ +from guillotina import configure +from guillotina.component import query_utility +from guillotina.contrib.mcp.interfaces import IMCPToolRegistry +from guillotina.interfaces import ( + IBeforeObjectRemovedEvent, + IObjectAddedEvent, + IObjectModifiedEvent, + IResource, +) + + +@configure.subscriber(for_=(IResource, IObjectAddedEvent)) +@configure.subscriber(for_=(IResource, IObjectModifiedEvent)) +@configure.subscriber(for_=(IResource, IBeforeObjectRemovedEvent)) +async def invalidate_mcp_cache_on_content_change(obj, event): + registry = query_utility(IMCPToolRegistry) + if registry is None: + return + object_id = getattr(obj, "id", None) or getattr(obj, "__name__", None) or "unknown" + registry.schedule_invalidate_cache(reason=f"{event.__class__.__name__}:{object_id}") diff --git a/guillotina/contrib/mcp/tools.py b/guillotina/contrib/mcp/tools.py new file mode 100644 index 000000000..93ec9ead4 --- /dev/null +++ b/guillotina/contrib/mcp/tools.py @@ -0,0 +1,333 @@ +import functools +from typing import Any, Awaitable, Callable, Dict, List, Tuple + +from guillotina.catalog.catalog import DefaultSearchUtility +from guillotina.component import query_multi_adapter, query_utility +from guillotina.event import notify +from guillotina.events import ObjectModifiedEvent +from guillotina.interfaces import IResourceSerializeToJson, IResourceSerializeToJsonSummary +from guillotina.interfaces.catalog import ICatalogUtility +from guillotina.utils import get_content_path, get_current_container, navigate_to + + +ToolHandler = Callable[[Any, Any, Dict[str, Any]], Awaitable[Dict[str, Any]]] + +RESOLVE_PATH_SCHEMA = { + "type": "object", + "properties": { + "path": {"type": "string", "description": "Absolute or relative Guillotina path", "default": "/"}, + "include_serialized": {"type": "boolean", "default": False}, + }, +} + +LIST_CHILDREN_SCHEMA = { + "type": "object", + "properties": { + "path": {"type": "string", "description": "Absolute or relative Guillotina path", "default": "/"}, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50, + "description": "Number of children to return per page. Hard cap is 200.", + }, + "page": { + "type": "integer", + "minimum": 1, + "default": 1, + "description": "Page number (1-based). Use with limit to paginate.", + }, + "include_serialized": {"type": "boolean", "default": False}, + }, +} + +SERIALIZE_RESOURCE_SCHEMA = { + "type": "object", + "properties": { + "path": {"type": "string", "description": "Absolute or relative Guillotina path", "default": "/"} + }, +} + +NOTIFY_MODIFIED_SCHEMA = { + "type": "object", + "properties": { + "path": {"type": "string", "description": "Absolute or relative Guillotina path"}, + "payload": {"type": "object", "description": "Payload sent to ObjectModifiedEvent", "default": {}}, + }, + "required": ["path"], +} + +SEARCH_SCHEMA = { + "type": "object", + "properties": { + "query": { + "type": "object", + "description": ( + "Guillotina catalog query object. " + "Supports: type_name, creators, tags, path__startswith, creation_date__gte, etc. " + "Use 'b_size' (max 1000) to set page size and 'b_start' to offset for pagination. " + "Use '_metadata' as a comma-separated list of field names to limit returned fields " + "and reduce response size, e.g. '_metadata': 'id,type_name,title,path'." + ), + }, + }, + "required": ["query"], +} + + +def _normalize_path(raw_path: Any) -> str: + clean = str(raw_path or "/").strip() or "/" + if clean.startswith("/"): + return clean + return clean + + +def _coerce_limit(value: Any, default: int) -> int: + try: + limit = int(value) + except (TypeError, ValueError): + return default + return max(1, min(limit, 200)) + + +def _resource_summary(resource: Any, path_hint: str = "") -> Dict[str, Any]: + path = path_hint or get_content_path(resource) + return { + "id": getattr(resource, "id", getattr(resource, "__name__", None)), + "@type": getattr(resource, "type_name", resource.__class__.__name__), + "title": getattr(resource, "title", None), + "path": path or "/", + } + + +async def _serialize_resource(resource: Any, request: Any) -> Dict[str, Any]: + serializer = query_multi_adapter((resource, request), IResourceSerializeToJson) + if serializer is None: + serializer = query_multi_adapter((resource, request), IResourceSerializeToJsonSummary) + if serializer is None: + return _resource_summary(resource) + return await serializer() + + +async def _resolve_target(context: Any, raw_path: Any) -> Tuple[Any, str]: + clean = _normalize_path(raw_path) + if clean in ("", "/"): + container = get_current_container() + if container is None: + raise ValueError("Container is not available in current task vars") + return container, "/" + + if clean.startswith("/"): + container = get_current_container() + if container is None: + raise ValueError("Container is not available in current task vars") + return await navigate_to(container, clean), clean + + return await navigate_to(context, clean), clean + + +async def resolve_path_tool(context: Any, request: Any, arguments: Dict[str, Any]) -> Dict[str, Any]: + target, resolved_path = await _resolve_target(context, arguments.get("path", "/")) + result = { + "path": resolved_path, + "resource": _resource_summary(target, get_content_path(target)), + } + if bool(arguments.get("include_serialized", False)): + result["serialized"] = await _serialize_resource(target, request) + return result + + +async def list_children_tool( + context: Any, request: Any, arguments: Dict[str, Any], default_limit: int = 50 +) -> Dict[str, Any]: + target, resolved_path = await _resolve_target(context, arguments.get("path", "/")) + if not hasattr(target, "async_items"): + raise ValueError("Target path does not point to a folder-like resource") + + limit = _coerce_limit(arguments.get("limit", default_limit), default_limit) + page = max(1, int(arguments.get("page", 1))) + include_serialized = bool(arguments.get("include_serialized", False)) + + catalog_result = await _list_children_from_catalog( + target=target, + request=request, + resolved_path=resolved_path, + limit=limit, + include_serialized=include_serialized, + ) + if catalog_result is not None: + return catalog_result + + return await _list_children_from_async_items( + target=target, + request=request, + resolved_path=resolved_path, + limit=limit, + page=page, + include_serialized=include_serialized, + ) + + +def _get_catalog_utility(): + catalog = query_utility(ICatalogUtility) + if catalog is None or catalog.__class__ == DefaultSearchUtility: + return None + return catalog + + +def _child_prefix(path: str) -> str: + if path in ("", "/"): + return "/" + return path.rstrip("/") + "/" + + +async def _serialize_from_catalog_path(path: str, request: Any) -> Dict[str, Any]: + container = get_current_container() + if container is None: + raise ValueError("Container is not available in current task vars") + resource = await navigate_to(container, path) + return await _serialize_resource(resource, request) + + +def _summary_from_catalog_hit(hit: Dict[str, Any], fallback_path: str = "") -> Dict[str, Any]: + return { + "id": hit.get("id", hit.get("@name")), + "@type": hit.get("type_name", hit.get("@type", "Resource")), + "title": hit.get("title"), + "path": hit.get("path", fallback_path or ""), + } + + +async def _list_children_from_catalog( + *, target: Any, request: Any, resolved_path: str, limit: int, include_serialized: bool +) -> Any: + catalog = _get_catalog_utility() + if catalog is None: + return None + + query = { + "path__starts": _child_prefix(resolved_path), + "depth": "1", + "_size": str(limit + 1), + "_sort_asc": "id", + "_metadata": "id,type_name,title,path", + } + result = await catalog.search(target, query) + hits = list(result.get("items", [])) + truncated = bool(result.get("items_total", len(hits)) > limit or len(hits) > limit) + hits = hits[:limit] + + items: List[Dict[str, Any]] = [] + for hit in hits: + item_summary = _summary_from_catalog_hit(hit) + if include_serialized: + path = item_summary.get("path", "") + if isinstance(path, str) and path: + item_summary["serialized"] = await _serialize_from_catalog_path(path, request) + items.append(item_summary) + + return { + "path": resolved_path, + "limit": limit, + "items_total": len(items), + "truncated": truncated, + "items": items, + } + + +async def _list_children_from_async_items( + *, target: Any, request: Any, resolved_path: str, limit: int, page: int, include_serialized: bool +) -> Dict[str, Any]: + items: List[Dict[str, Any]] = [] + truncated = False + count = 0 + start_index = (page - 1) * limit + end_index = page * limit + + async for _, child in target.async_items(): + if count >= start_index and count < end_index: + item_summary = _resource_summary(child, get_content_path(child)) + if include_serialized: + item_summary["serialized"] = await _serialize_resource(child, request) + items.append(item_summary) + elif count >= end_index: + truncated = True + break + count += 1 + + return { + "path": resolved_path, + "limit": limit, + "page": page, + "items_total": len(items), + "truncated": truncated, + "items": items, + } + + +async def serialize_resource_tool(context: Any, request: Any, arguments: Dict[str, Any]) -> Dict[str, Any]: + target, resolved_path = await _resolve_target(context, arguments.get("path", "/")) + return {"path": resolved_path, "serialized": await _serialize_resource(target, request)} + + +async def notify_modified_tool(context: Any, request: Any, arguments: Dict[str, Any]) -> Dict[str, Any]: + target, resolved_path = await _resolve_target(context, arguments.get("path")) + payload = arguments.get("payload", {}) + if not isinstance(payload, dict): + raise ValueError("Tool argument 'payload' must be an object") + await notify(ObjectModifiedEvent(target, payload=payload)) + return {"path": resolved_path, "notified": True, "payload_keys": sorted(payload.keys())} + + +async def search_tool(context: Any, request: Any, arguments: Dict[str, Any]) -> Dict[str, Any]: + catalog = _get_catalog_utility() + if catalog is None: + raise ValueError("Catalog utility is not available") + query = arguments.get("query", {}) + result = await catalog.search(context, query) + return {"query": query, "result": result} + + +def default_tools(default_child_limit: int = 50) -> List[Tuple[str, str, Dict[str, Any], ToolHandler, bool]]: + return [ + ( + "resolve_path", + "Resolve a Guillotina path and return basic resource metadata.", + RESOLVE_PATH_SCHEMA, + resolve_path_tool, + True, + ), + ( + "list_children", + "List child resources from a folder-like Guillotina resource. Max 200 per page; use 'page' to paginate.", + LIST_CHILDREN_SCHEMA, + functools.partial(list_children_tool, default_limit=default_child_limit), + True, + ), + ( + "serialize_resource", + "Serialize a Guillotina resource using the registered serializers.", + SERIALIZE_RESOURCE_SCHEMA, + serialize_resource_tool, + False, + ), + ( + "notify_modified", + "Emit an ObjectModifiedEvent for a resource path, triggering subscribers.", + NOTIFY_MODIFIED_SCHEMA, + notify_modified_tool, + False, + ), + ( + "search", + ( + "Search for resources using the Guillotina catalog. " + "Supports filtering by type_name, creators, path, dates, and more. " + "Paginate with 'b_start' (offset) and 'b_size' (page size, max 1000). " + "Limit returned fields with '_metadata' (e.g. 'id,type_name,title,path,creators')." + ), + SEARCH_SCHEMA, + search_tool, + True, + ), + ] diff --git a/guillotina/contrib/memcached/__init__.py b/guillotina/contrib/memcached/__init__.py index 4eb18ea73..ffec765ac 100644 --- a/guillotina/contrib/memcached/__init__.py +++ b/guillotina/contrib/memcached/__init__.py @@ -1,7 +1,7 @@ from asyncio import get_running_loop +from typing import Any, Dict + from guillotina.contrib.memcached.driver import MemcachedDriver -from typing import Any -from typing import Dict _driver = None diff --git a/guillotina/contrib/memcached/driver.py b/guillotina/contrib/memcached/driver.py index 8af52f404..83f5d7313 100644 --- a/guillotina/contrib/memcached/driver.py +++ b/guillotina/contrib/memcached/driver.py @@ -4,18 +4,15 @@ print("If you add guillotina.contrib.memcached you need to add emcache on your requirements") raise -from guillotina import app_settings -from guillotina import metrics -from guillotina.contrib.memcached.exceptions import NoMemcachedConfigured -from typing import Any -from typing import Dict -from typing import List -from typing import Optional - import asyncio -import backoff import hashlib import logging +from typing import Any, Dict, List, Optional + +import backoff + +from guillotina import app_settings, metrics +from guillotina.contrib.memcached.exceptions import NoMemcachedConfigured try: @@ -118,7 +115,10 @@ def __init__(self, operation: str): counter=MEMCACHED_OPS, histogram=MEMCACHED_OPS_PROCESSING_TIME, labels={"type": operation}, - error_mappings={"timeout": asyncio.TimeoutError, "cancelled": asyncio.CancelledError}, + error_mappings={ + "timeout": asyncio.TimeoutError, + "cancelled": asyncio.CancelledError, + }, ) except ImportError: @@ -162,11 +162,13 @@ async def initialize(self, loop): if self.initialized is False: try: await self._connect() - self.initialized = True except Exception: # pragma: no cover logger.error("Error initializing memcached driver", exc_info=True) + return - if _SEND_METRICS is True: + self.initialized = True + + if _SEND_METRICS is True and self.client is not None: self._metrics_task = loop.create_task(metrics_probe(self.client)) async def _create_client(self, settings: Dict[str, Any]) -> emcache.Client: @@ -279,7 +281,6 @@ async def metrics_probe(client: emcache.Client, every: int = 30): def update_connection_pool_metrics( client: emcache.Client, last_state: Optional[emcache.ConnectionPoolMetrics] = None ) -> emcache.ConnectionPoolMetrics: - # Every node will have it's own label metrics = client.cluster_managment().connection_pool_metrics() for node, node_metrics in metrics.items(): diff --git a/guillotina/contrib/pubsub/utility.py b/guillotina/contrib/pubsub/utility.py index aaed2974f..76a3fa219 100644 --- a/guillotina/contrib/pubsub/utility.py +++ b/guillotina/contrib/pubsub/utility.py @@ -1,13 +1,13 @@ -from guillotina.contrib.pubsub.exceptions import NoPubSubDriver -from guillotina.profile import profilable -from guillotina.utils import resolve_dotted_name -from typing import Any -from typing import Callable - import asyncio -import backoff import logging import pickle +from typing import Any, Callable + +import backoff + +from guillotina.contrib.pubsub.exceptions import NoPubSubDriver +from guillotina.profile import profilable +from guillotina.utils import resolve_dotted_name logger = logging.getLogger("guillotina") diff --git a/guillotina/contrib/redis/__init__.py b/guillotina/contrib/redis/__init__.py index d014e09dc..3e94e3847 100644 --- a/guillotina/contrib/redis/__init__.py +++ b/guillotina/contrib/redis/__init__.py @@ -1,4 +1,5 @@ from asyncio import get_running_loop + from guillotina import configure from guillotina.contrib.redis.driver import RedisDriver diff --git a/guillotina/contrib/redis/dm.py b/guillotina/contrib/redis/dm.py index 30f1ac2e7..55c45dd55 100644 --- a/guillotina/contrib/redis/dm.py +++ b/guillotina/contrib/redis/dm.py @@ -1,15 +1,14 @@ -from guillotina import configure -from guillotina import metrics +import time + +import orjson + +from guillotina import configure, metrics from guillotina.contrib.redis import get_driver from guillotina.files.adapter import DBDataManager -from guillotina.interfaces import IExternalFileStorageManager -from guillotina.interfaces import IUploadDataManager +from guillotina.interfaces import IExternalFileStorageManager, IUploadDataManager from guillotina.renderers import guillotina_json_default from guillotina.transactions import get_transaction -import orjson -import time - try: import prometheus_client diff --git a/guillotina/contrib/redis/driver.py b/guillotina/contrib/redis/driver.py index 6c6beadc0..9f2611b63 100644 --- a/guillotina/contrib/redis/driver.py +++ b/guillotina/contrib/redis/driver.py @@ -4,18 +4,16 @@ print("If you add guillotina.contrib.redis you need to add redis>4.2.0rc1 on your requirements") raise -from guillotina import app_settings -from guillotina import metrics -from guillotina.contrib.redis.exceptions import NoRedisConfigured +import asyncio +import logging +from typing import Dict, List, Optional + +import backoff from redis.asyncio.client import PubSub from redis.exceptions import ConnectionError -from typing import Dict -from typing import List -from typing import Optional -import asyncio -import backoff -import logging +from guillotina import app_settings, metrics +from guillotina.contrib.redis.exceptions import NoRedisConfigured try: diff --git a/guillotina/contrib/redis_session/utility.py b/guillotina/contrib/redis_session/utility.py index 685cd3818..9e3cc10c0 100644 --- a/guillotina/contrib/redis_session/utility.py +++ b/guillotina/contrib/redis_session/utility.py @@ -1,9 +1,9 @@ -from guillotina import app_settings - import asyncio import logging import uuid +from guillotina import app_settings + logger = logging.getLogger("guillotina") diff --git a/guillotina/contrib/swagger/services.py b/guillotina/contrib/swagger/services.py index 142149229..ceaecaa72 100644 --- a/guillotina/contrib/swagger/services.py +++ b/guillotina/contrib/swagger/services.py @@ -1,19 +1,20 @@ -from guillotina import app_settings -from guillotina import configure -from guillotina.api.service import Service -from guillotina.utils import get_authenticated_user -from guillotina.utils import get_full_content_path -from guillotina.utils import get_request_scheme -from guillotina.utils import get_security_policy -from guillotina.utils import get_url -from guillotina.utils import resolve_dotted_name -from urllib.parse import urlparse -from zope.interface import Interface - import copy import json import os -import pkg_resources +from urllib.parse import urlparse + +from zope.interface import Interface + +from guillotina import __version__, app_settings, configure +from guillotina.api.service import Service +from guillotina.utils import ( + get_authenticated_user, + get_full_content_path, + get_request_scheme, + get_security_policy, + get_url, + resolve_dotted_name, +) here = os.path.dirname(os.path.realpath(__file__)) @@ -163,7 +164,7 @@ async def __call__(self): definition["servers"][0]["url"] = url if "version" not in definition["info"]: - definition["info"]["version"] = pkg_resources.get_distribution("guillotina").version + definition["info"]["version"] = __version__ api_defs = app_settings["api_definition"] diff --git a/guillotina/contrib/templates/interfaces.py b/guillotina/contrib/templates/interfaces.py index 4db7dda9b..00ce46a95 100644 --- a/guillotina/contrib/templates/interfaces.py +++ b/guillotina/contrib/templates/interfaces.py @@ -1,6 +1,5 @@ from guillotina import schema -from guillotina.interfaces import IAsyncUtility -from guillotina.interfaces import IItem +from guillotina.interfaces import IAsyncUtility, IItem class IJinjaUtility(IAsyncUtility): diff --git a/guillotina/contrib/templates/utility.py b/guillotina/contrib/templates/utility.py index c3bf28b59..be67905e9 100644 --- a/guillotina/contrib/templates/utility.py +++ b/guillotina/contrib/templates/utility.py @@ -1,22 +1,30 @@ +import logging from concurrent.futures import ThreadPoolExecutor from functools import partial -from guillotina import app_settings -from guillotina.contrib.templates.interfaces import IJinjaTemplate -from guillotina.utils import get_current_container -from guillotina.utils import navigate_to -from jinja2 import BaseLoader -from jinja2 import Environment -from jinja2 import PackageLoader -from jinja2 import select_autoescape +from importlib import import_module +from pathlib import Path + +from jinja2 import BaseLoader, Environment, FileSystemLoader, select_autoescape from jinja2.exceptions import TemplateNotFound from lru import LRU -import logging +from guillotina import app_settings +from guillotina.contrib.templates.interfaces import IJinjaTemplate +from guillotina.utils import get_current_container, navigate_to logger = logging.getLogger("guillotina") +def _resolve_template_dir(package_name, folder): + module = import_module(package_name) + if getattr(module, "__file__", None): + package_dir = Path(module.__file__).resolve().parent + else: # pragma: no cover + package_dir = Path(next(iter(module.__path__))).resolve() + return package_dir / folder + + class JinjaUtility: def __init__(self, settings): self.envs = [] @@ -35,7 +43,8 @@ async def initialize(self, app=None): for template in self.templates: package, folder = template.split(":") env = Environment( - loader=PackageLoader(package, folder), autoescape=select_autoescape(["html", "xml", "pt"]) + loader=FileSystemLoader(str(_resolve_template_dir(package, folder))), + autoescape=select_autoescape(["html", "xml", "pt"]), ) self.envs.append(env) diff --git a/guillotina/contrib/workflows/__init__.py b/guillotina/contrib/workflows/__init__.py index 42108cae3..4aa066b85 100644 --- a/guillotina/contrib/workflows/__init__.py +++ b/guillotina/contrib/workflows/__init__.py @@ -1,10 +1,11 @@ -from guillotina import configure - import glob import logging import typing + import yaml +from guillotina import configure + logger = logging.getLogger("guillotina.contrib.workflows") diff --git a/guillotina/contrib/workflows/api.py b/guillotina/contrib/workflows/api.py index c12e31da5..75001980f 100644 --- a/guillotina/contrib/workflows/api.py +++ b/guillotina/contrib/workflows/api.py @@ -1,10 +1,8 @@ from guillotina import configure from guillotina.api.service import Service from guillotina.component import query_adapter -from guillotina.contrib.workflows.interfaces import IWorkflow -from guillotina.contrib.workflows.interfaces import IWorkflowBehavior -from guillotina.interfaces import IAbsoluteURL -from guillotina.interfaces import IResource +from guillotina.contrib.workflows.interfaces import IWorkflow, IWorkflowBehavior +from guillotina.interfaces import IAbsoluteURL, IResource class Workflow(object): diff --git a/guillotina/contrib/workflows/events.py b/guillotina/contrib/workflows/events.py index 160140a1d..a28309c4c 100644 --- a/guillotina/contrib/workflows/events.py +++ b/guillotina/contrib/workflows/events.py @@ -1,6 +1,7 @@ +from zope.interface import implementer + from guillotina.contrib.workflows.interfaces import IWorkflowChangedEvent from guillotina.events import ObjectEvent -from zope.interface import implementer @implementer(IWorkflowChangedEvent) diff --git a/guillotina/contrib/workflows/interfaces.py b/guillotina/contrib/workflows/interfaces.py index a475571a3..47bf536ce 100644 --- a/guillotina/contrib/workflows/interfaces.py +++ b/guillotina/contrib/workflows/interfaces.py @@ -1,16 +1,13 @@ +import json +from typing import Optional + +from zope.interface import Attribute, Interface, implementer, interfaces + from guillotina import schema from guillotina.component import query_adapter from guillotina.directives import index_field -from guillotina.interfaces import IAsyncUtility -from guillotina.interfaces import IResource +from guillotina.interfaces import IAsyncUtility, IResource from guillotina.schema.interfaces import IContextAwareDefaultFactory -from typing import Optional -from zope.interface import Attribute -from zope.interface import implementer -from zope.interface import Interface -from zope.interface import interfaces - -import json HISTORY_SCHEMA = json.dumps( diff --git a/guillotina/contrib/workflows/post_serialize.py b/guillotina/contrib/workflows/post_serialize.py index 2667bf850..8b3623017 100644 --- a/guillotina/contrib/workflows/post_serialize.py +++ b/guillotina/contrib/workflows/post_serialize.py @@ -1,7 +1,8 @@ +from typing import Any + from guillotina.component import query_adapter from guillotina.contrib.workflows.interfaces import IWorkflowBehavior from guillotina.interfaces import IResource -from typing import Any def apply_review(context: IResource, result: Any): diff --git a/guillotina/contrib/workflows/subscriber.py b/guillotina/contrib/workflows/subscriber.py index a9f4c680f..46323c2fb 100644 --- a/guillotina/contrib/workflows/subscriber.py +++ b/guillotina/contrib/workflows/subscriber.py @@ -1,14 +1,12 @@ +import datetime + from guillotina import configure from guillotina.component import query_adapter -from guillotina.contrib.workflows.interfaces import IWorkflow -from guillotina.contrib.workflows.interfaces import IWorkflowBehavior -from guillotina.interfaces import IObjectAddedEvent -from guillotina.interfaces import IResource +from guillotina.contrib.workflows.interfaces import IWorkflow, IWorkflowBehavior +from guillotina.interfaces import IObjectAddedEvent, IResource from guillotina.security.utils import apply_sharing from guillotina.utils import get_authenticated_user_id -import datetime - @configure.subscriber(for_=(IResource, IObjectAddedEvent), priority=1001) # after indexing async def workflow_object_added(obj, event): diff --git a/guillotina/contrib/workflows/utility.py b/guillotina/contrib/workflows/utility.py index 96c8fc483..63aea4ff2 100644 --- a/guillotina/contrib/workflows/utility.py +++ b/guillotina/contrib/workflows/utility.py @@ -1,22 +1,15 @@ -from guillotina import app_settings -from guillotina import configure -from guillotina.component import provide_adapter -from guillotina.component import query_adapter +import datetime + +from guillotina import app_settings, configure +from guillotina.component import provide_adapter, query_adapter from guillotina.contrib.workflows import logger from guillotina.contrib.workflows.events import WorkflowChangedEvent -from guillotina.contrib.workflows.interfaces import IWorkflow -from guillotina.contrib.workflows.interfaces import IWorkflowBehavior -from guillotina.contrib.workflows.interfaces import IWorkflowUtility +from guillotina.contrib.workflows.interfaces import IWorkflow, IWorkflowBehavior, IWorkflowUtility from guillotina.event import notify from guillotina.events import ObjectModifiedEvent -from guillotina.response import HTTPPreconditionFailed -from guillotina.response import HTTPUnauthorized +from guillotina.response import HTTPPreconditionFailed, HTTPUnauthorized from guillotina.security.utils import apply_sharing -from guillotina.utils import get_authenticated_user_id -from guillotina.utils import get_security_policy -from guillotina.utils import import_class - -import datetime +from guillotina.utils import get_authenticated_user_id, get_security_policy, import_class def create_workflow_factory(proto_name, proto_definition): diff --git a/guillotina/contrib/workflows/vocabularies.py b/guillotina/contrib/workflows/vocabularies.py index a8bb97e74..8a1e0003f 100644 --- a/guillotina/contrib/workflows/vocabularies.py +++ b/guillotina/contrib/workflows/vocabularies.py @@ -1,8 +1,6 @@ from guillotina import configure -from guillotina.component import get_utility -from guillotina.component import query_adapter -from guillotina.contrib.workflows.interfaces import IWorkflow -from guillotina.contrib.workflows.interfaces import IWorkflowUtility +from guillotina.component import get_utility, query_adapter +from guillotina.contrib.workflows.interfaces import IWorkflow, IWorkflowUtility from guillotina.interfaces import IResource diff --git a/guillotina/cookiecutter/application/{{cookiecutter.package_name}}/setup.py b/guillotina/cookiecutter/application/{{cookiecutter.package_name}}/setup.py index 9742639ae..37e561f49 100644 --- a/guillotina/cookiecutter/application/{{cookiecutter.package_name}}/setup.py +++ b/guillotina/cookiecutter/application/{{cookiecutter.package_name}}/setup.py @@ -1,5 +1,4 @@ -from setuptools import find_packages -from setuptools import setup +from setuptools import find_packages, setup try: diff --git a/guillotina/cookiecutter/application/{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/tests/fixtures.py b/guillotina/cookiecutter/application/{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/tests/fixtures.py index f5fbb45e6..8fc6edf68 100644 --- a/guillotina/cookiecutter/application/{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/tests/fixtures.py +++ b/guillotina/cookiecutter/application/{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/tests/fixtures.py @@ -1,9 +1,10 @@ -from guillotina import testing -from guillotina.tests.fixtures import ContainerRequesterAsyncContextManager - import json + import pytest +from guillotina import testing +from guillotina.tests.fixtures import ContainerRequesterAsyncContextManager + def base_settings_configurator(settings): if 'applications' in settings: diff --git a/guillotina/cors.py b/guillotina/cors.py index efb989545..f02884b94 100644 --- a/guillotina/cors.py +++ b/guillotina/cors.py @@ -1,10 +1,9 @@ -from guillotina import app_settings -from guillotina import glogging +import fnmatch + +from guillotina import app_settings, glogging from guillotina.interfaces import IRequest from guillotina.response import HTTPUnauthorized -import fnmatch - logger = glogging.getLogger("guillotina") diff --git a/guillotina/db/cache/base.py b/guillotina/db/cache/base.py index c02929f4e..a91a7dad0 100644 --- a/guillotina/db/cache/base.py +++ b/guillotina/db/cache/base.py @@ -1,10 +1,8 @@ +import typing +from typing import Any, Dict, List + from guillotina import glogging from guillotina.db.orm.interfaces import IBaseObject -from typing import Any -from typing import Dict -from typing import List - -import typing logger = glogging.getLogger("guillotina") diff --git a/guillotina/db/cache/dummy.py b/guillotina/db/cache/dummy.py index 7e3b89e21..02aa17147 100644 --- a/guillotina/db/cache/dummy.py +++ b/guillotina/db/cache/dummy.py @@ -1,10 +1,8 @@ +from typing import Any, Dict, List + from guillotina import configure from guillotina.db.cache.base import BaseCache -from guillotina.db.interfaces import ITransaction -from guillotina.db.interfaces import ITransactionCache -from typing import Any -from typing import Dict -from typing import List +from guillotina.db.interfaces import ITransaction, ITransactionCache @configure.adapter(for_=ITransaction, provides=ITransactionCache, name="dummy") @@ -15,16 +13,16 @@ async def get(self, **kwargs): async def set( self, value, keyset: List[Dict[str, Any]] = None, oid=None, container=None, id=None, variant=None ): - ... + pass async def clear(self): - ... + pass async def invalidate(self, ob): - ... + pass async def delete(self, key): - ... + pass async def delete_all(self, keys): - ... + pass diff --git a/guillotina/db/db.py b/guillotina/db/db.py index 99eed3351..8be39112a 100644 --- a/guillotina/db/db.py +++ b/guillotina/db/db.py @@ -1,7 +1,8 @@ +from zope.interface import implementer_only + from guillotina.content import Folder from guillotina.db.orm.interfaces import IBaseObject from guillotina.interfaces import IDatabase -from zope.interface import implementer_only @implementer_only(IDatabase, IBaseObject) diff --git a/guillotina/db/events.py b/guillotina/db/events.py index 1600180c1..6b6b81ef2 100644 --- a/guillotina/db/events.py +++ b/guillotina/db/events.py @@ -1,6 +1,7 @@ -from guillotina.db.interfaces import IStorageCreatedEvent from zope.interface import implementer +from guillotina.db.interfaces import IStorageCreatedEvent + @implementer(IStorageCreatedEvent) class StorageCreatedEvent: diff --git a/guillotina/db/factory.py b/guillotina/db/factory.py index 973da7d4c..af8650f8f 100644 --- a/guillotina/db/factory.py +++ b/guillotina/db/factory.py @@ -1,24 +1,21 @@ +import string from copy import deepcopy +from typing import List + +import asyncpg + from guillotina import configure from guillotina.component import get_utility from guillotina.db.interfaces import IDatabaseManager from guillotina.db.storages.cockroach import CockroachStorage -from guillotina.db.storages.dummy import DummyFileStorage -from guillotina.db.storages.dummy import DummyStorage +from guillotina.db.storages.dummy import DummyFileStorage, DummyStorage from guillotina.db.storages.pg import PostgresqlStorage from guillotina.db.transaction_manager import TransactionManager from guillotina.event import notify from guillotina.events import DatabaseInitializedEvent from guillotina.factory.content import Database -from guillotina.interfaces import IApplication -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IDatabaseConfigurationFactory -from guillotina.utils import apply_coroutine -from guillotina.utils import resolve_dotted_name -from typing import List - -import asyncpg -import string +from guillotina.interfaces import IApplication, IDatabase, IDatabaseConfigurationFactory +from guillotina.utils import apply_coroutine, resolve_dotted_name def _get_connection_options(dbconfig): diff --git a/guillotina/db/interfaces.py b/guillotina/db/interfaces.py index 294e4d87e..ffa45e443 100644 --- a/guillotina/db/interfaces.py +++ b/guillotina/db/interfaces.py @@ -1,13 +1,11 @@ -from guillotina.db.orm.interfaces import IBaseObject -from guillotina.interfaces import ICatalogDataAdapter -from guillotina.interfaces import IDatabase -from zope.interface import Attribute -from zope.interface import Interface -from zope.interface import interfaces - import asyncio import typing +from zope.interface import Attribute, Interface, interfaces + +from guillotina.db.orm.interfaces import IBaseObject +from guillotina.interfaces import ICatalogDataAdapter, IDatabase + class IPartition(Interface): """Get the partition of the object""" diff --git a/guillotina/db/orm/base.py b/guillotina/db/orm/base.py index 499f92919..ed709a532 100644 --- a/guillotina/db/orm/base.py +++ b/guillotina/db/orm/base.py @@ -1,16 +1,13 @@ +import weakref +from typing import Any, Dict, Generic, Optional, TypeVar + +from zope.interface import implementer + from guillotina.db.interfaces import ITransaction from guillotina.db.orm.interfaces import IBaseObject from guillotina.exceptions import TransactionNotFound from guillotina.profile import profilable from guillotina.transactions import get_transaction -from typing import Any -from typing import Dict -from typing import Generic -from typing import Optional -from typing import TypeVar -from zope.interface import implementer - -import weakref T = TypeVar("T") diff --git a/guillotina/db/orm/interfaces.py b/guillotina/db/orm/interfaces.py index f04244225..ffa0301cb 100644 --- a/guillotina/db/orm/interfaces.py +++ b/guillotina/db/orm/interfaces.py @@ -1,6 +1,5 @@ # -*- encoding: utf-8 -*- -from zope.interface import Attribute -from zope.interface import Interface +from zope.interface import Attribute, Interface OID_TYPE = SERIAL_TYPE = bytes @@ -62,4 +61,4 @@ def __getstate__(): # type: ignore """ def register(): - ... + """ """ diff --git a/guillotina/db/reader.py b/guillotina/db/reader.py index 0e2d55f20..0c1da3c4d 100644 --- a/guillotina/db/reader.py +++ b/guillotina/db/reader.py @@ -1,8 +1,8 @@ -from guillotina.db.orm.interfaces import IBaseObject - import pickle import typing +from guillotina.db.orm.interfaces import IBaseObject + def reader(result: dict) -> IBaseObject: obj = typing.cast(IBaseObject, pickle.loads(result["state"])) diff --git a/guillotina/db/storages/cockroach.py b/guillotina/db/storages/cockroach.py index 5f3b56f2c..7b8ad4374 100644 --- a/guillotina/db/storages/cockroach.py +++ b/guillotina/db/storages/cockroach.py @@ -1,19 +1,22 @@ +import uuid + +import asyncpg +from zope.interface import implementer + from guillotina import glogging from guillotina.const import TRASHED_ID from guillotina.db.interfaces import ICockroachStorage from guillotina.db.storages import pg from guillotina.db.storages.utils import register_sql from guillotina.db.uid import MAX_UID_LENGTH -from guillotina.exceptions import ConflictError -from guillotina.exceptions import ConflictIdOnContainer -from guillotina.exceptions import RequestNotFound -from guillotina.exceptions import RestartCommit -from guillotina.exceptions import TIDConflictError +from guillotina.exceptions import ( + ConflictError, + ConflictIdOnContainer, + RequestNotFound, + RestartCommit, + TIDConflictError, +) from guillotina.utils import get_current_request -from zope.interface import implementer - -import asyncpg -import uuid logger = glogging.getLogger("guillotina") diff --git a/guillotina/db/storages/dummy.py b/guillotina/db/storages/dummy.py index ddf7aeda0..f74c59608 100644 --- a/guillotina/db/storages/dummy.py +++ b/guillotina/db/storages/dummy.py @@ -1,13 +1,14 @@ -from guillotina.db.interfaces import IStorage -from guillotina.db.storages.base import BaseStorage -from guillotina.exceptions import ConflictIdOnContainer -from zope.interface import implementer - import asyncio import logging import os import pickle +from zope.interface import implementer + +from guillotina.db.interfaces import IStorage +from guillotina.db.storages.base import BaseStorage +from guillotina.exceptions import ConflictIdOnContainer + logger = logging.getLogger("guillotina") diff --git a/guillotina/db/storages/pg.py b/guillotina/db/storages/pg.py index e35ff67cb..4e8dc894a 100644 --- a/guillotina/db/storages/pg.py +++ b/guillotina/db/storages/pg.py @@ -1,31 +1,25 @@ +import asyncio +import concurrent +import time from asyncio import shield -from contextlib import asynccontextmanager -from contextlib import contextmanager -from guillotina import glogging -from guillotina import metrics +from contextlib import asynccontextmanager, contextmanager + +import asyncpg +import asyncpg.connection +import orjson +from zope.interface import implementer + +from guillotina import glogging, metrics from guillotina._settings import app_settings from guillotina.const import TRASHED_ID from guillotina.db.events import StorageCreatedEvent from guillotina.db.interfaces import IPostgresStorage from guillotina.db.storages.base import BaseStorage -from guillotina.db.storages.utils import clear_table_name -from guillotina.db.storages.utils import get_table_definition -from guillotina.db.storages.utils import register_sql -from guillotina.db.storages.utils import SQLStatements +from guillotina.db.storages.utils import SQLStatements, clear_table_name, get_table_definition, register_sql from guillotina.db.uid import MAX_UID_LENGTH from guillotina.event import notify -from guillotina.exceptions import ConflictError -from guillotina.exceptions import ConflictIdOnContainer -from guillotina.exceptions import TIDConflictError +from guillotina.exceptions import ConflictError, ConflictIdOnContainer, TIDConflictError from guillotina.profile import profilable -from zope.interface import implementer - -import asyncio -import asyncpg -import asyncpg.connection -import concurrent -import orjson -import time try: diff --git a/guillotina/db/storages/vacuum.py b/guillotina/db/storages/vacuum.py index 5ec550924..1066a7a39 100644 --- a/guillotina/db/storages/vacuum.py +++ b/guillotina/db/storages/vacuum.py @@ -1,14 +1,13 @@ +import asyncio +import logging + +import asyncpg.exceptions + from guillotina import configure from guillotina.const import TRASHED_ID -from guillotina.db.interfaces import ICockroachStorage -from guillotina.db.interfaces import IPostgresStorage -from guillotina.db.interfaces import IVacuumProvider +from guillotina.db.interfaces import ICockroachStorage, IPostgresStorage, IVacuumProvider from guillotina.db.storages.utils import register_sql -import asyncio -import asyncpg.exceptions -import logging - logger = logging.getLogger("guillotina") diff --git a/guillotina/db/transaction.py b/guillotina/db/transaction.py index fc37c3f1d..919ffa630 100644 --- a/guillotina/db/transaction.py +++ b/guillotina/db/transaction.py @@ -1,38 +1,33 @@ +import asyncio +import logging +import sys +import time +import warnings from collections import OrderedDict +from typing import Any, AsyncIterator, Callable, Dict, List, Optional, Union + +from typing_extensions import TypedDict +from zope.interface import implementer + from guillotina import task_vars from guillotina._settings import app_settings from guillotina.component import query_adapter from guillotina.const import ROOT_ID from guillotina.content import Container from guillotina.db.db import Root -from guillotina.db.interfaces import ITransaction -from guillotina.db.interfaces import ITransactionCache -from guillotina.db.interfaces import IWriter +from guillotina.db.interfaces import ITransaction, ITransactionCache, IWriter from guillotina.db.orm.interfaces import IBaseObject -from guillotina.exceptions import ConflictError -from guillotina.exceptions import ReadOnlyError -from guillotina.exceptions import RestartCommit -from guillotina.exceptions import TIDConflictError -from guillotina.exceptions import TransactionClosedException -from guillotina.exceptions import TransactionObjectRegistrationMismatchException +from guillotina.exceptions import ( + ConflictError, + ReadOnlyError, + RestartCommit, + TIDConflictError, + TransactionClosedException, + TransactionObjectRegistrationMismatchException, +) from guillotina.profile import profilable from guillotina.registry import Registry from guillotina.utils import lazy_apply -from typing import Any -from typing import AsyncIterator -from typing import Callable -from typing import Dict -from typing import List -from typing import Optional -from typing import Union -from typing_extensions import TypedDict -from zope.interface import implementer - -import asyncio -import logging -import sys -import time -import warnings _EMPTY = "____" @@ -74,7 +69,7 @@ def record_cache_metric( def record_cache_metric( name: str, result_type: str, value: Union[ObjectResultType, str], key_args: Dict[str, Any] ) -> None: - ... + pass logger = logging.getLogger(__name__) diff --git a/guillotina/db/transaction_manager.py b/guillotina/db/transaction_manager.py index ef7f80cb2..f3a8072f4 100644 --- a/guillotina/db/transaction_manager.py +++ b/guillotina/db/transaction_manager.py @@ -1,24 +1,19 @@ +import asyncio +import typing from asyncio import shield -from guillotina import glogging -from guillotina import task_vars + +import asyncpg +from zope.interface import implementer + +from guillotina import glogging, task_vars from guillotina.db import ROOT_ID -from guillotina.db.interfaces import ITransaction -from guillotina.db.interfaces import ITransactionManager +from guillotina.db.interfaces import ITransaction, ITransactionManager from guillotina.db.orm.interfaces import IBaseObject -from guillotina.db.transaction import Status -from guillotina.db.transaction import Transaction -from guillotina.exceptions import ConflictError -from guillotina.exceptions import RequestNotFound -from guillotina.exceptions import TIDConflictError -from guillotina.exceptions import TransactionNotFound +from guillotina.db.transaction import Status, Transaction +from guillotina.exceptions import ConflictError, RequestNotFound, TIDConflictError, TransactionNotFound from guillotina.profile import profilable from guillotina.transactions import transaction from guillotina.utils import get_authenticated_user_id -from zope.interface import implementer - -import asyncio -import asyncpg -import typing logger = glogging.getLogger("guillotina") diff --git a/guillotina/db/writer.py b/guillotina/db/writer.py index 607184cba..718dd06b3 100644 --- a/guillotina/db/writer.py +++ b/guillotina/db/writer.py @@ -1,15 +1,13 @@ +import pickle + from guillotina import configure from guillotina._settings import app_settings from guillotina.catalog.catalog import DefaultCatalogDataAdapter from guillotina.component import query_adapter -from guillotina.db.interfaces import IJSONDBSerializer -from guillotina.db.interfaces import IWriter +from guillotina.db.interfaces import IJSONDBSerializer, IWriter from guillotina.db.orm.interfaces import IBaseObject from guillotina.interfaces import IResource -from guillotina.utils import find_container -from guillotina.utils import get_dotted_name - -import pickle +from guillotina.utils import find_container, get_dotted_name @configure.adapter(for_=IResource, provides=IJSONDBSerializer) diff --git a/guillotina/directives.py b/guillotina/directives.py index 56d60860f..5fe7f213e 100644 --- a/guillotina/directives.py +++ b/guillotina/directives.py @@ -1,8 +1,7 @@ +import sys from typing import Any -from zope.interface.interface import Element -from zope.interface.interface import TAGGED_DATA -import sys +from zope.interface.interface import TAGGED_DATA, Element class DirectiveClass(type): diff --git a/guillotina/documentation/__init__.py b/guillotina/documentation/__init__.py index 1d3470e23..60f1a643b 100644 --- a/guillotina/documentation/__init__.py +++ b/guillotina/documentation/__init__.py @@ -1,8 +1,8 @@ -from guillotina import configure -from guillotina import schema -from guillotina.addons import Addon from zope.interface import Interface +from guillotina import configure, schema +from guillotina.addons import Addon + class IRegistryData(Interface): foobar = schema.TextLine() diff --git a/guillotina/documentation/sphinx/__init__.py b/guillotina/documentation/sphinx/__init__.py index caee4e097..a8ffa8a2c 100644 --- a/guillotina/documentation/sphinx/__init__.py +++ b/guillotina/documentation/sphinx/__init__.py @@ -1,10 +1,18 @@ # -*- coding: utf-8 -*- -from async_asgi_testclient import TestClient +import asyncio +import json +import logging from base64 import b64encode +from typing import Any, Dict, Optional + +import docutils.statemachine +from async_asgi_testclient import TestClient from docutils import nodes from docutils.parsers.rst import Directive # type: ignore from docutils.parsers.rst import directives # type: ignore -from guillotina import routes +from zope.interface import Interface + +from guillotina import __version__, routes from guillotina._settings import app_settings from guillotina.component import query_multi_adapter from guillotina.content import load_cached_schema @@ -13,16 +21,6 @@ from guillotina.transactions import abort from guillotina.traversal import traverse from guillotina.utils import get_dotted_name -from typing import Any -from typing import Dict -from typing import Optional -from zope.interface import Interface - -import asyncio -import docutils.statemachine -import json -import logging -import pkg_resources logger = logging.getLogger("guillotina.docs") @@ -269,5 +267,4 @@ def run(self): def setup(app): app.add_directive_to_domain("http", "gapi", APICall) - dist = pkg_resources.get_distribution("guillotina") - return {"version": dist.version} + return {"version": __version__} diff --git a/guillotina/entrypoint.py b/guillotina/entrypoint.py index 39258a76c..5257982b4 100644 --- a/guillotina/entrypoint.py +++ b/guillotina/entrypoint.py @@ -1,7 +1,7 @@ -from guillotina.factory import make_app - import os +from guillotina.factory import make_app + if "G_CONFIG_FILE" not in os.environ: raise Exception("You must provide the envar G_CONFIG_FILE") diff --git a/guillotina/event.py b/guillotina/event.py index 091aa5b05..afbc5118a 100644 --- a/guillotina/event.py +++ b/guillotina/event.py @@ -1,5 +1,4 @@ -from guillotina.component.event import async_subscribers -from guillotina.component.event import sync_subscribers +from guillotina.component.event import async_subscribers, sync_subscribers async def notify(event): diff --git a/guillotina/events.py b/guillotina/events.py index cc2f4934c..7738e5182 100644 --- a/guillotina/events.py +++ b/guillotina/events.py @@ -1,43 +1,46 @@ -from guillotina.component.interfaces import IObjectEvent -from guillotina.db.orm.interfaces import IBaseObject -from guillotina.interfaces import IAfterAsyncUtilityLoadedEvent -from guillotina.interfaces import IApplicationCleanupEvent -from guillotina.interfaces import IApplicationConfiguredEvent -from guillotina.interfaces import IApplicationEvent -from guillotina.interfaces import IApplicationInitializedEvent -from guillotina.interfaces import IBeforeAsyncUtilityLoadedEvent -from guillotina.interfaces import IBeforeObjectAddedEvent -from guillotina.interfaces import IBeforeObjectModifiedEvent -from guillotina.interfaces import IBeforeObjectMovedEvent -from guillotina.interfaces import IBeforeObjectRemovedEvent -from guillotina.interfaces import IBeforeRenderViewEvent -from guillotina.interfaces import IDatabaseInitializedEvent -from guillotina.interfaces import IFileBeforeFinishUploaded -from guillotina.interfaces import IFileFinishUploaded -from guillotina.interfaces import IFileStartedUpload -from guillotina.interfaces import INewUserAdded -from guillotina.interfaces import IObjectAddedEvent -from guillotina.interfaces import IObjectDuplicatedEvent -from guillotina.interfaces import IObjectLoadedEvent -from guillotina.interfaces import IObjectLocationEvent -from guillotina.interfaces import IObjectModifiedEvent -from guillotina.interfaces import IObjectMovedEvent -from guillotina.interfaces import IObjectPermissionsModifiedEvent -from guillotina.interfaces import IObjectPermissionsViewEvent -from guillotina.interfaces import IObjectRemovedEvent -from guillotina.interfaces import IObjectVisitedEvent -from guillotina.interfaces import IRegistry -from guillotina.interfaces import IRegistryEditedEvent -from guillotina.interfaces import ITraversalMissEvent -from guillotina.interfaces import ITraversalResourceMissEvent -from guillotina.interfaces import ITraversalRouteMissEvent -from guillotina.interfaces import ITraversalViewMissEvent -from guillotina.interfaces import IUserLogin -from guillotina.interfaces import IUserRefreshToken -from guillotina.interfaces import IValidationEvent +import typing + from zope.interface import implementer -import typing +from guillotina.component.interfaces import IObjectEvent +from guillotina.db.orm.interfaces import IBaseObject +from guillotina.interfaces import ( + IAfterAsyncUtilityLoadedEvent, + IApplicationCleanupEvent, + IApplicationConfiguredEvent, + IApplicationEvent, + IApplicationInitializedEvent, + IBeforeAsyncUtilityLoadedEvent, + IBeforeObjectAddedEvent, + IBeforeObjectModifiedEvent, + IBeforeObjectMovedEvent, + IBeforeObjectRemovedEvent, + IBeforeRenderViewEvent, + IDatabaseInitializedEvent, + IFileBeforeFinishUploaded, + IFileFinishUploaded, + IFileStartedUpload, + INewUserAdded, + IObjectAddedEvent, + IObjectDuplicatedEvent, + IObjectLoadedEvent, + IObjectLocationEvent, + IObjectModifiedEvent, + IObjectMovedEvent, + IObjectPermissionsModifiedEvent, + IObjectPermissionsViewEvent, + IObjectRemovedEvent, + IObjectVisitedEvent, + IRegistry, + IRegistryEditedEvent, + ITraversalMissEvent, + ITraversalResourceMissEvent, + ITraversalRouteMissEvent, + ITraversalViewMissEvent, + IUserLogin, + IUserRefreshToken, + IValidationEvent, +) @implementer(IObjectEvent) diff --git a/guillotina/exc_resp.py b/guillotina/exc_resp.py index 4ac3059fd..376f455be 100644 --- a/guillotina/exc_resp.py +++ b/guillotina/exc_resp.py @@ -1,22 +1,25 @@ -from guillotina import configure -from guillotina import error_reasons -from guillotina.exceptions import ConflictIdOnContainer -from guillotina.exceptions import DeserializationError -from guillotina.exceptions import InvalidContentType -from guillotina.exceptions import NoPermissionToAdd -from guillotina.exceptions import NotAllowedContentType -from guillotina.exceptions import PreconditionFailed -from guillotina.exceptions import Unauthorized -from guillotina.exceptions import UnRetryableRequestError -from guillotina.interfaces import IErrorResponseException -from guillotina.response import HTTPConflict -from guillotina.response import HTTPExpectationFailed -from guillotina.response import HTTPPreconditionFailed -from guillotina.response import HTTPUnauthorized -from guillotina.response import Response - import json +from guillotina import configure, error_reasons +from guillotina.exceptions import ( + ConflictIdOnContainer, + DeserializationError, + InvalidContentType, + NoPermissionToAdd, + NotAllowedContentType, + PreconditionFailed, + Unauthorized, + UnRetryableRequestError, +) +from guillotina.interfaces import IErrorResponseException +from guillotina.response import ( + HTTPConflict, + HTTPExpectationFailed, + HTTPPreconditionFailed, + HTTPUnauthorized, + Response, +) + def render_error_response(error, reason, eid=None): data = {"reason": reason.name, "details": reason.details, "type": error} diff --git a/guillotina/exceptions.py b/guillotina/exceptions.py index 26c5a4628..c72614eb2 100644 --- a/guillotina/exceptions.py +++ b/guillotina/exceptions.py @@ -1,11 +1,11 @@ -from guillotina._settings import app_settings -from guillotina.interfaces import IUnauthorized +import jsonschema +import orjson from zope.interface import implementer from zope.interface.exceptions import Invalid # noqa pylint: disable=W0611 from zope.interface.interfaces import ComponentLookupError # noqa pylint: disable=W0611 -import jsonschema -import orjson +from guillotina._settings import app_settings +from guillotina.interfaces import IUnauthorized class NoPermissionToAdd(Exception): diff --git a/guillotina/factory/app.py b/guillotina/factory/app.py index 61465ff87..fd3002a84 100644 --- a/guillotina/factory/app.py +++ b/guillotina/factory/app.py @@ -1,38 +1,34 @@ +import json +import logging +import logging.config from copy import deepcopy -from guillotina import configure -from guillotina import glogging -from guillotina import traversal -from guillotina._settings import app_settings -from guillotina._settings import default_settings + +from jwcrypto import jwk + +from guillotina import configure, glogging, traversal +from guillotina._settings import app_settings, default_settings from guillotina.behaviors import apply_concrete_behaviors -from guillotina.component import get_utility -from guillotina.component import provide_utility +from guillotina.component import get_utility, provide_utility from guillotina.configure.config import ConfigurationMachine -from guillotina.content import JavaScriptApplication -from guillotina.content import load_cached_schema -from guillotina.content import StaticDirectory -from guillotina.content import StaticFile +from guillotina.content import JavaScriptApplication, StaticDirectory, StaticFile, load_cached_schema from guillotina.event import notify -from guillotina.events import AfterAsyncUtilityLoadedEvent -from guillotina.events import ApplicationCleanupEvent -from guillotina.events import ApplicationConfiguredEvent -from guillotina.events import ApplicationInitializedEvent -from guillotina.events import BeforeAsyncUtilityLoadedEvent -from guillotina.events import DatabaseInitializedEvent +from guillotina.events import ( + AfterAsyncUtilityLoadedEvent, + ApplicationCleanupEvent, + ApplicationConfiguredEvent, + ApplicationInitializedEvent, + BeforeAsyncUtilityLoadedEvent, + DatabaseInitializedEvent, +) from guillotina.factory.content import ApplicationRoot -from guillotina.interfaces import IApplication -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IDatabaseConfigurationFactory -from guillotina.utils import lazy_apply -from guillotina.utils import list_or_dict_items -from guillotina.utils import resolve_dotted_name -from guillotina.utils import resolve_path -from guillotina.utils import secure_passphrase -from jwcrypto import jwk - -import json -import logging -import logging.config +from guillotina.interfaces import IApplication, IDatabase, IDatabaseConfigurationFactory +from guillotina.utils import ( + lazy_apply, + list_or_dict_items, + resolve_dotted_name, + resolve_path, + secure_passphrase, +) app_logger = logging.getLogger("guillotina") diff --git a/guillotina/factory/content.py b/guillotina/factory/content.py index 16610c9da..d191e4722 100644 --- a/guillotina/factory/content.py +++ b/guillotina/factory/content.py @@ -1,31 +1,20 @@ +import asyncio +import logging +import typing from concurrent.futures import ThreadPoolExecutor + +from zope.interface import alsoProvides, implementer + from guillotina._settings import app_settings from guillotina.auth.users import RootUser from guillotina.auth.validators import hash_password -from guillotina.component import get_adapter -from guillotina.component import get_global_components -from guillotina.component import get_utility -from guillotina.component import provide_utility +from guillotina.component import get_adapter, get_global_components, get_utility, provide_utility from guillotina.const import ROOT_ID -from guillotina.db.interfaces import IDatabaseManager -from guillotina.db.interfaces import ITransaction -from guillotina.db.interfaces import ITransactionManager -from guillotina.db.interfaces import IWriter +from guillotina.db.interfaces import IDatabaseManager, ITransaction, ITransactionManager, IWriter from guillotina.db.transaction_manager import TransactionManager -from guillotina.interfaces import IApplication -from guillotina.interfaces import IDatabase +from guillotina.interfaces import IApplication, IDatabase from guillotina.transactions import get_transaction -from guillotina.utils import apply_coroutine -from guillotina.utils import import_class -from guillotina.utils import lazy_apply -from guillotina.utils import list_or_dict_items -from guillotina.utils import notice_on_error -from zope.interface import alsoProvides -from zope.interface import implementer - -import asyncio -import logging -import typing +from guillotina.utils import apply_coroutine, import_class, lazy_apply, list_or_dict_items, notice_on_error logger = logging.getLogger("guillotina") diff --git a/guillotina/factory/security.py b/guillotina/factory/security.py index f638147cc..2d8bd526a 100644 --- a/guillotina/factory/security.py +++ b/guillotina/factory/security.py @@ -1,16 +1,18 @@ -from guillotina import configure -from guillotina import security -from guillotina.auth.users import ANONYMOUS_USER_ID -from guillotina.auth.users import ROOT_USER_ID -from guillotina.interfaces import AllowSingle -from guillotina.interfaces import IApplication -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IInheritPermissionManager -from guillotina.interfaces import IPrincipalPermissionManager -from guillotina.interfaces import IRolePermissionManager -from guillotina.security.security_code import InheritPermissionManager -from guillotina.security.security_code import PrincipalPermissionManager -from guillotina.security.security_code import RolePermissionManager +from guillotina import configure, security +from guillotina.auth.users import ANONYMOUS_USER_ID, ROOT_USER_ID +from guillotina.interfaces import ( + AllowSingle, + IApplication, + IDatabase, + IInheritPermissionManager, + IPrincipalPermissionManager, + IRolePermissionManager, +) +from guillotina.security.security_code import ( + InheritPermissionManager, + PrincipalPermissionManager, + RolePermissionManager, +) @configure.adapter(for_=IDatabase, provides=IInheritPermissionManager) diff --git a/guillotina/factory/serialize.py b/guillotina/factory/serialize.py index d1df83abc..b7291894c 100644 --- a/guillotina/factory/serialize.py +++ b/guillotina/factory/serialize.py @@ -1,10 +1,12 @@ from guillotina import configure -from guillotina.interfaces import IApplication -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResourceSerializeToJson -from guillotina.interfaces import IStaticDirectory -from guillotina.interfaces import IStaticFile +from guillotina.interfaces import ( + IApplication, + IDatabase, + IRequest, + IResourceSerializeToJson, + IStaticDirectory, + IStaticFile, +) from guillotina.utils import get_security_policy diff --git a/guillotina/fields/annotation.py b/guillotina/fields/annotation.py index fea7a1a1d..5e6d5707f 100644 --- a/guillotina/fields/annotation.py +++ b/guillotina/fields/annotation.py @@ -1,34 +1,27 @@ -from guillotina import configure -from guillotina import schema -from guillotina.annotations import AnnotationData -from guillotina.component import query_adapter -from guillotina.db.orm.interfaces import IBaseObject -from guillotina.exceptions import ValueDeserializationError -from guillotina.fields import patch -from guillotina.fields.interfaces import IBucketDictField -from guillotina.fields.interfaces import IBucketListField -from guillotina.fields.interfaces import IPatchFieldOperation -from guillotina.interfaces import IAnnotationData -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import IContentBehavior -from guillotina.interfaces import IFieldValueRenderer -from guillotina.interfaces import IRequest -from guillotina.response import HTTPGone -from guillotina.response import HTTPPreconditionFailed -from typing import Any -from typing import AsyncIterator -from typing import cast -from typing import List -from typing import Optional -from typing import Tuple -from zope.interface import implementer -from zope.interface import Interface - import bisect import logging import time import typing import uuid +from typing import Any, AsyncIterator, List, Optional, Tuple, cast + +from zope.interface import Interface, implementer + +from guillotina import configure, schema +from guillotina.annotations import AnnotationData +from guillotina.component import query_adapter +from guillotina.db.orm.interfaces import IBaseObject +from guillotina.exceptions import ValueDeserializationError +from guillotina.fields import patch +from guillotina.fields.interfaces import IBucketDictField, IBucketListField, IPatchFieldOperation +from guillotina.interfaces import ( + IAnnotationData, + IAnnotations, + IContentBehavior, + IFieldValueRenderer, + IRequest, +) +from guillotina.response import HTTPGone, HTTPPreconditionFailed logger = logging.getLogger("guillotina") diff --git a/guillotina/fields/dynamic.py b/guillotina/fields/dynamic.py index f72243030..9bb75a461 100644 --- a/guillotina/fields/dynamic.py +++ b/guillotina/fields/dynamic.py @@ -1,20 +1,14 @@ from collections import namedtuple -from guillotina import configure -from guillotina import schema + +from zope.interface import Interface, implementer + +from guillotina import configure, schema from guillotina.component import get_adapter -from guillotina.exceptions import ComponentLookupError -from guillotina.exceptions import ValueDeserializationError -from guillotina.fields.interfaces import IDynamicField -from guillotina.fields.interfaces import IDynamicFieldOperation -from guillotina.fields.patch import field_converter -from guillotina.fields.patch import PatchDictDel -from guillotina.fields.patch import PatchDictSet -from guillotina.fields.patch import PatchDictUpdate -from guillotina.fields.patch import PatchField +from guillotina.exceptions import ComponentLookupError, ValueDeserializationError +from guillotina.fields.interfaces import IDynamicField, IDynamicFieldOperation +from guillotina.fields.patch import PatchDictDel, PatchDictSet, PatchDictUpdate, PatchField, field_converter from guillotina.interfaces import IJSONToValue from guillotina.schema.interfaces import IDict -from zope.interface import implementer -from zope.interface import Interface @implementer(IDynamicField) diff --git a/guillotina/fields/files.py b/guillotina/fields/files.py index aa8d105de..22542995f 100644 --- a/guillotina/fields/files.py +++ b/guillotina/fields/files.py @@ -1,8 +1,8 @@ -from guillotina.interfaces import ICloudFileField -from guillotina.interfaces import IFile -from guillotina.schema import Object from zope.interface import implementer +from guillotina.interfaces import ICloudFileField, IFile +from guillotina.schema import Object + @implementer(ICloudFileField) class CloudFileField(Object): diff --git a/guillotina/fields/interfaces.py b/guillotina/fields/interfaces.py index 244cbc613..676716db3 100644 --- a/guillotina/fields/interfaces.py +++ b/guillotina/fields/interfaces.py @@ -1,6 +1,7 @@ -from guillotina.schema.interfaces import IField from zope.interface import Interface +from guillotina.schema.interfaces import IField + class IPatchField(IField): pass diff --git a/guillotina/fields/patch.py b/guillotina/fields/patch.py index c730c9294..159164cf1 100644 --- a/guillotina/fields/patch.py +++ b/guillotina/fields/patch.py @@ -1,20 +1,14 @@ from collections import namedtuple -from guillotina import configure -from guillotina import schema -from guillotina.component import get_adapter -from guillotina.component import query_adapter + +from zope.interface import implementer + +from guillotina import configure, schema +from guillotina.component import get_adapter, query_adapter from guillotina.exceptions import ValueDeserializationError -from guillotina.fields.interfaces import IPatchField -from guillotina.fields.interfaces import IPatchFieldOperation +from guillotina.fields.interfaces import IPatchField, IPatchFieldOperation from guillotina.interfaces import IJSONToValue -from guillotina.schema.interfaces import IArrayJSONField -from guillotina.schema.interfaces import IDict -from guillotina.schema.interfaces import IInt -from guillotina.schema.interfaces import IList -from guillotina.schema.interfaces import IObjectJSONField -from guillotina.schema.interfaces import ITuple +from guillotina.schema.interfaces import IArrayJSONField, IDict, IInt, IList, IObjectJSONField, ITuple from guillotina.utils import apply_coroutine -from zope.interface import implementer @implementer(IPatchField) diff --git a/guillotina/files/__init__.py b/guillotina/files/__init__.py index a7f3e04bb..cafc25a4c 100644 --- a/guillotina/files/__init__.py +++ b/guillotina/files/__init__.py @@ -1,3 +1,5 @@ +from guillotina.exceptions import UnRetryableRequestError # noqa + from .adapter import DBFileStorageManagerAdapter # noqa from .const import CHUNK_SIZE # noqa from .const import MAX_REQUEST_CACHE_SIZE # noqa @@ -7,7 +9,6 @@ from .utils import convert_base64_to_binary # noqa from .utils import get_contenttype # noqa from .utils import read_request_data # noqa -from guillotina.exceptions import UnRetryableRequestError # noqa CloudFileManager = FileManager # b/w compat diff --git a/guillotina/files/adapter.py b/guillotina/files/adapter.py index 0a4671451..8c300a931 100644 --- a/guillotina/files/adapter.py +++ b/guillotina/files/adapter.py @@ -1,28 +1,26 @@ -from .dbfile import DBFile -from .exceptions import RangeNotFound -from .exceptions import RangeNotSupported +import time +from typing import AsyncIterator + from guillotina import configure from guillotina.blob import Blob from guillotina.event import notify -from guillotina.events import FileBeforeUploadFinishedEvent -from guillotina.events import FileUploadFinishedEvent -from guillotina.events import FileUploadStartedEvent -from guillotina.exceptions import BlobChunkNotFound -from guillotina.exceptions import FileNotFoundException -from guillotina.files.utils import generate_key -from guillotina.files.utils import guess_content_type -from guillotina.interfaces import IDBFileField -from guillotina.interfaces import IFileCleanup -from guillotina.interfaces import IFileField -from guillotina.interfaces import IFileNameGenerator -from guillotina.interfaces import IFileStorageManager -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResource -from guillotina.interfaces import IUploadDataManager +from guillotina.events import FileBeforeUploadFinishedEvent, FileUploadFinishedEvent, FileUploadStartedEvent +from guillotina.exceptions import BlobChunkNotFound, FileNotFoundException +from guillotina.files.utils import generate_key, guess_content_type +from guillotina.interfaces import ( + IDBFileField, + IFileCleanup, + IFileField, + IFileNameGenerator, + IFileStorageManager, + IRequest, + IResource, + IUploadDataManager, +) from guillotina.response import HTTPPreconditionFailed -from typing import AsyncIterator -import time +from .dbfile import DBFile +from .exceptions import RangeNotFound, RangeNotSupported @configure.adapter(for_=(IResource, IFileField), provides=IFileNameGenerator) diff --git a/guillotina/files/dbfile.py b/guillotina/files/dbfile.py index de04618c6..e38b26bfd 100644 --- a/guillotina/files/dbfile.py +++ b/guillotina/files/dbfile.py @@ -1,9 +1,12 @@ -from .field import BaseCloudFile -from guillotina.blob import Blob -from guillotina.interfaces import IDBFile from typing import Optional + from zope.interface import implementer +from guillotina.blob import Blob +from guillotina.interfaces import IDBFile + +from .field import BaseCloudFile + @implementer(IDBFile) class DBFile(BaseCloudFile): diff --git a/guillotina/files/field.py b/guillotina/files/field.py index 1dbd04823..f6ebad358 100644 --- a/guillotina/files/field.py +++ b/guillotina/files/field.py @@ -1,19 +1,15 @@ +import base64 +import uuid from functools import partial + +from zope.interface import implementer + from guillotina import configure from guillotina.component import get_multi_adapter -from guillotina.files.utils import convert_base64_to_binary -from guillotina.files.utils import guess_content_type -from guillotina.interfaces import ICloudFileField -from guillotina.interfaces import IContentBehavior -from guillotina.interfaces import IFile -from guillotina.interfaces import IFileManager +from guillotina.files.utils import convert_base64_to_binary, guess_content_type +from guillotina.interfaces import ICloudFileField, IContentBehavior, IFile, IFileManager from guillotina.schema.fieldproperty import FieldProperty -from guillotina.utils import get_current_request -from guillotina.utils import to_str -from zope.interface import implementer - -import base64 -import uuid +from guillotina.utils import get_current_request, to_str @configure.value_serializer(for_=IFile) diff --git a/guillotina/files/manager.py b/guillotina/files/manager.py index 0adbf901c..3ee31a2a1 100644 --- a/guillotina/files/manager.py +++ b/guillotina/files/manager.py @@ -1,34 +1,36 @@ -from .const import CHUNK_SIZE -from .exceptions import RangeException -from guillotina import configure -from guillotina import glogging -from guillotina._settings import app_settings -from guillotina.api.service import DictFieldProxy -from guillotina.component import get_adapter -from guillotina.component import get_multi_adapter -from guillotina.files.utils import read_request_data -from guillotina.interfaces import ICloudFileField -from guillotina.interfaces import IFileManager -from guillotina.interfaces import IFileStorageManager -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResource -from guillotina.interfaces import IUploadDataManager -from guillotina.response import HTTPClientClosedRequest -from guillotina.response import HTTPConflict -from guillotina.response import HTTPNotFound -from guillotina.response import HTTPPreconditionFailed -from guillotina.response import HTTPRequestRangeNotSatisfiable -from guillotina.response import Response -from guillotina.utils import apply_coroutine -from guillotina.utils import get_object_url -from guillotina.utils import resolve_dotted_name -from zope.interface import alsoProvides - import asyncio import base64 import posixpath import uuid +from zope.interface import alsoProvides + +from guillotina import configure, glogging +from guillotina._settings import app_settings +from guillotina.api.service import DictFieldProxy +from guillotina.component import get_adapter, get_multi_adapter +from guillotina.files.utils import read_request_data +from guillotina.interfaces import ( + ICloudFileField, + IFileManager, + IFileStorageManager, + IRequest, + IResource, + IUploadDataManager, +) +from guillotina.response import ( + HTTPClientClosedRequest, + HTTPConflict, + HTTPNotFound, + HTTPPreconditionFailed, + HTTPRequestRangeNotSatisfiable, + Response, +) +from guillotina.utils import apply_coroutine, get_object_url, resolve_dotted_name + +from .const import CHUNK_SIZE +from .exceptions import RangeException + logger = glogging.getLogger("guillotina") diff --git a/guillotina/files/utils.py b/guillotina/files/utils.py index 108575ddf..e095710a9 100644 --- a/guillotina/files/utils.py +++ b/guillotina/files/utils.py @@ -1,15 +1,15 @@ -from .const import MAX_REQUEST_CACHE_SIZE -from guillotina import task_vars -from guillotina.exceptions import UnRetryableRequestError -from guillotina.utils import get_content_path -from guillotina.utils import to_str - import asyncio import base64 import mimetypes import os import uuid +from guillotina import task_vars +from guillotina.exceptions import UnRetryableRequestError +from guillotina.utils import get_content_path, to_str + +from .const import MAX_REQUEST_CACHE_SIZE + async def read_request_data(request, chunk_size): """ diff --git a/guillotina/glogging.py b/guillotina/glogging.py index eaca81c8c..77d200352 100644 --- a/guillotina/glogging.py +++ b/guillotina/glogging.py @@ -1,14 +1,13 @@ -from guillotina import task_vars - import logging import uuid +from guillotina import task_vars + def _wrapped(name): def log(self, *args, **kwargs): - from guillotina.utils import get_authenticated_user_id - from guillotina.utils import get_current_request from guillotina.exceptions import RequestNotFound + from guillotina.utils import get_authenticated_user_id, get_current_request func = getattr(self._logger, name) request = kwargs.pop("request", None) diff --git a/guillotina/gtypes.py b/guillotina/gtypes.py index 81469db35..5ae56c1e2 100644 --- a/guillotina/gtypes.py +++ b/guillotina/gtypes.py @@ -1,10 +1,5 @@ -from typing import Any -from typing import Dict -from typing import List -from typing import Tuple -from typing import TypeVar - import types +from typing import Any, Dict, List, Tuple, TypeVar ResolvableType = TypeVar("ResolvableType", types.ModuleType, types.FunctionType, type) diff --git a/guillotina/interfaces/__init__.py b/guillotina/interfaces/__init__.py index ca32a8fdf..4670a5e7e 100644 --- a/guillotina/interfaces/__init__.py +++ b/guillotina/interfaces/__init__.py @@ -11,6 +11,7 @@ from .behaviors import IBehaviorAdapterFactory # noqa from .behaviors import IBehaviorSchemaAwareFactory # noqa from .behaviors import IContentBehavior # noqa +from .catalog import ICatalogDataAdapter # noqa; noqa from .catalog import ICatalogUtility # noqa from .catalog import IPGCatalogUtility # noqa from .catalog import ISearchParser # noqa @@ -138,9 +139,6 @@ from .views import IView # noqa -from .catalog import ICatalogDataAdapter # noqa; noqa - - DEFAULT_ADD_PERMISSION = "guillotina.AddContent" DEFAULT_READ_PERMISSION = "guillotina.ViewContent" DEFAULT_WRITE_PERMISSION = "guillotina.ManageContent" diff --git a/guillotina/interfaces/async_util.py b/guillotina/interfaces/async_util.py index 1043c3510..8770c7f72 100644 --- a/guillotina/interfaces/async_util.py +++ b/guillotina/interfaces/async_util.py @@ -1,5 +1,5 @@ -from typing import Dict -from typing import Optional +from typing import Dict, Optional + from zope.interface import Interface diff --git a/guillotina/interfaces/behaviors.py b/guillotina/interfaces/behaviors.py index 3c3ee176e..03d2fae76 100644 --- a/guillotina/interfaces/behaviors.py +++ b/guillotina/interfaces/behaviors.py @@ -1,8 +1,8 @@ -from guillotina import schema -from zope.interface import Attribute -from zope.interface import Interface +from zope.interface import Attribute, Interface from zope.interface.interfaces import IInterface +from guillotina import schema + class IBehavior(Interface): """A description of a behavior. These should be registered as named diff --git a/guillotina/interfaces/catalog.py b/guillotina/interfaces/catalog.py index 5fa66558d..9b185c854 100644 --- a/guillotina/interfaces/catalog.py +++ b/guillotina/interfaces/catalog.py @@ -1,8 +1,10 @@ -from .content import IContainer -from guillotina.db.orm.interfaces import IBaseObject +import typing + from zope.interface import Interface -import typing +from guillotina.db.orm.interfaces import IBaseObject + +from .content import IContainer class ICatalogUtility(Interface): diff --git a/guillotina/interfaces/content.py b/guillotina/interfaces/content.py index 1f65d4b94..9408befb6 100644 --- a/guillotina/interfaces/content.py +++ b/guillotina/interfaces/content.py @@ -1,14 +1,14 @@ +from typing import TYPE_CHECKING + +from zope.interface import Attribute, Interface +from zope.interface.common.mapping import IEnumerableMapping + +import guillotina.schema from guillotina.component.interfaces import IFactory from guillotina.component.interfaces import ISite as IComponentSite from guillotina.db.orm.interfaces import IBaseObject from guillotina.interfaces.common import IMapping from guillotina.schema import TextLine -from typing import TYPE_CHECKING -from zope.interface import Attribute -from zope.interface import Interface -from zope.interface.common.mapping import IEnumerableMapping - -import guillotina.schema if TYPE_CHECKING: # pragma: no cover diff --git a/guillotina/interfaces/events.py b/guillotina/interfaces/events.py index 36c997e84..784b9e214 100644 --- a/guillotina/interfaces/events.py +++ b/guillotina/interfaces/events.py @@ -1,6 +1,4 @@ -from zope.interface import Attribute -from zope.interface import Interface -from zope.interface import interfaces +from zope.interface import Attribute, Interface, interfaces class IBeforeObjectModifiedEvent(interfaces.IObjectEvent): diff --git a/guillotina/interfaces/files.py b/guillotina/interfaces/files.py index a30545e73..1eeb6ed15 100644 --- a/guillotina/interfaces/files.py +++ b/guillotina/interfaces/files.py @@ -1,8 +1,10 @@ -from guillotina import schema -from guillotina.schema.interfaces import IObject from typing import AsyncIterator + from zope.interface import Interface +from guillotina import schema +from guillotina.schema.interfaces import IObject + class IUploadDataManager(Interface): """ diff --git a/guillotina/interfaces/json.py b/guillotina/interfaces/json.py index 7184e73e5..5047d511b 100644 --- a/guillotina/interfaces/json.py +++ b/guillotina/interfaces/json.py @@ -1,6 +1,7 @@ -from guillotina.i18n import MessageFactory from zope.interface import Interface +from guillotina.i18n import MessageFactory + _ = MessageFactory("guillotina") diff --git a/guillotina/interfaces/mail.py b/guillotina/interfaces/mail.py index e19d721b5..d1abe8d92 100644 --- a/guillotina/interfaces/mail.py +++ b/guillotina/interfaces/mail.py @@ -1,8 +1,5 @@ -from typing import Any -from typing import Coroutine -from typing import List -from typing import Optional -from typing import Union +from typing import Any, Coroutine, List, Optional, Union + from zope.interface import Interface diff --git a/guillotina/interfaces/misc.py b/guillotina/interfaces/misc.py index 6c9101ddf..cd3604080 100644 --- a/guillotina/interfaces/misc.py +++ b/guillotina/interfaces/misc.py @@ -1,10 +1,9 @@ +from typing import Dict, Optional, Tuple, Type + +from zope.interface import Interface + from guillotina.db.orm.interfaces import IBaseObject from guillotina.interfaces.content import IApplication -from typing import Dict -from typing import Optional -from typing import Tuple -from typing import Type -from zope.interface import Interface class IRequest(Interface): @@ -57,7 +56,7 @@ def uninstall(container, request): # noqa: N805 class IIDChecker(Interface): def __init__(context): - ... + """ """ async def __call__(id_: str, type_: str) -> bool: - ... + """ """ diff --git a/guillotina/interfaces/registry.py b/guillotina/interfaces/registry.py index 2d11ebb00..baca5c0a5 100644 --- a/guillotina/interfaces/registry.py +++ b/guillotina/interfaces/registry.py @@ -1,6 +1,7 @@ +from zope.interface import Interface + from guillotina import schema from guillotina.i18n import MessageFactory -from zope.interface import Interface _ = MessageFactory("guillotina") diff --git a/guillotina/interfaces/response.py b/guillotina/interfaces/response.py index 013005fc7..7d67a2dda 100644 --- a/guillotina/interfaces/response.py +++ b/guillotina/interfaces/response.py @@ -1,7 +1,7 @@ -from multidict import CIMultiDict from typing import Union -from zope.interface import Attribute -from zope.interface import Interface + +from multidict import CIMultiDict +from zope.interface import Attribute, Interface class IResponse(Interface): diff --git a/guillotina/interfaces/security.py b/guillotina/interfaces/security.py index 75459ff4a..1cf873a2d 100644 --- a/guillotina/interfaces/security.py +++ b/guillotina/interfaces/security.py @@ -1,17 +1,14 @@ -from .misc import IRequest +import copyreg # type: ignore +import typing + +from zope.interface import Attribute, Interface + from guillotina.db.orm.interfaces import IBaseObject from guillotina.directives import read_permission from guillotina.i18n import MessageFactory -from guillotina.schema import Dict -from guillotina.schema import List -from guillotina.schema import Object -from guillotina.schema import Text -from guillotina.schema import TextLine -from zope.interface import Attribute -from zope.interface import Interface +from guillotina.schema import Dict, List, Object, Text, TextLine -import copyreg # type: ignore -import typing +from .misc import IRequest _ = MessageFactory("guillotina") diff --git a/guillotina/interfaces/types.py b/guillotina/interfaces/types.py index 30500d1b3..c16309686 100644 --- a/guillotina/interfaces/types.py +++ b/guillotina/interfaces/types.py @@ -1,6 +1,7 @@ -from .content import IResource from zope.interface import Interface +from .content import IResource + class IConstrainTypes(Interface): # pylint: disable=E0239 def __init__(context: IResource, default=None): # noqa: N805 diff --git a/guillotina/json/deserialize_content.py b/guillotina/json/deserialize_content.py index c6a1bd28f..ccf92c006 100644 --- a/guillotina/json/deserialize_content.py +++ b/guillotina/json/deserialize_content.py @@ -1,39 +1,30 @@ # -*- coding: utf-8 -*- +import asyncio from copy import deepcopy -from guillotina import configure -from guillotina import glogging -from guillotina.component import ComponentLookupError -from guillotina.component import get_adapter -from guillotina.component import query_utility -from guillotina.content import get_all_behaviors -from guillotina.content import get_cached_factory +from typing import Any, Dict, List, Type + +from zope.interface import Interface + +from guillotina import configure, glogging +from guillotina.component import ComponentLookupError, get_adapter, query_utility +from guillotina.content import get_all_behaviors, get_cached_factory from guillotina.db.transaction import _EMPTY -from guillotina.directives import merged_tagged_value_dict -from guillotina.directives import write_permission -from guillotina.exceptions import DeserializationError -from guillotina.exceptions import Invalid -from guillotina.exceptions import Unauthorized -from guillotina.exceptions import ValueDeserializationError -from guillotina.interfaces import IAsyncBehavior -from guillotina.interfaces import IJSONToValue -from guillotina.interfaces import IPermission -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResource -from guillotina.interfaces import IResourceDeserializeFromJson -from guillotina.interfaces import RESERVED_ATTRS +from guillotina.directives import merged_tagged_value_dict, write_permission +from guillotina.exceptions import DeserializationError, Invalid, Unauthorized, ValueDeserializationError +from guillotina.interfaces import ( + RESERVED_ATTRS, + IAsyncBehavior, + IJSONToValue, + IPermission, + IRequest, + IResource, + IResourceDeserializeFromJson, +) from guillotina.json.utils import validate_invariants from guillotina.schema import get_fields from guillotina.schema.exceptions import ValidationError from guillotina.schema.interfaces import IField -from guillotina.utils import apply_coroutine -from guillotina.utils import get_security_policy -from typing import Any -from typing import Dict -from typing import List -from typing import Type -from zope.interface import Interface - -import asyncio +from guillotina.utils import apply_coroutine, get_security_policy logger = glogging.getLogger("guillotina") diff --git a/guillotina/json/deserialize_value.py b/guillotina/json/deserialize_value.py index beecaae80..cefa5e55c 100644 --- a/guillotina/json/deserialize_value.py +++ b/guillotina/json/deserialize_value.py @@ -1,29 +1,30 @@ # -*- coding: utf-8 -*- +import datetime + from dateutil.parser import parse +from zope.interface import Interface + from guillotina import configure -from guillotina.component import ComponentLookupError -from guillotina.component import get_adapter +from guillotina.component import ComponentLookupError, get_adapter from guillotina.exceptions import ValueDeserializationError from guillotina.interfaces import IJSONToValue from guillotina.profile import profilable from guillotina.schema._bootstrapinterfaces import IFromUnicode -from guillotina.schema.exceptions import ValidationError -from guillotina.schema.exceptions import WrongType -from guillotina.schema.interfaces import IBool -from guillotina.schema.interfaces import IDate -from guillotina.schema.interfaces import IDatetime -from guillotina.schema.interfaces import IDict -from guillotina.schema.interfaces import IField -from guillotina.schema.interfaces import IFrozenSet -from guillotina.schema.interfaces import IJSONField -from guillotina.schema.interfaces import IList -from guillotina.schema.interfaces import IObject -from guillotina.schema.interfaces import ISet -from guillotina.schema.interfaces import ITuple -from guillotina.schema.interfaces import IUnionField -from zope.interface import Interface - -import datetime +from guillotina.schema.exceptions import ValidationError, WrongType +from guillotina.schema.interfaces import ( + IBool, + IDate, + IDatetime, + IDict, + IField, + IFrozenSet, + IJSONField, + IList, + IObject, + ISet, + ITuple, + IUnionField, +) _type_conversions = (int, str, float, bool) diff --git a/guillotina/json/serialize_content.py b/guillotina/json/serialize_content.py index 0b1e0fa67..795a9f8ec 100644 --- a/guillotina/json/serialize_content.py +++ b/guillotina/json/serialize_content.py @@ -1,29 +1,25 @@ # -*- coding: utf-8 -*- -from guillotina import app_settings -from guillotina import configure -from guillotina.component import ComponentLookupError -from guillotina.component import get_multi_adapter -from guillotina.component import query_utility -from guillotina.content import get_all_behaviors -from guillotina.content import get_cached_factory -from guillotina.directives import merged_tagged_value_dict -from guillotina.directives import read_permission -from guillotina.interfaces import IAsyncBehavior -from guillotina.interfaces import IFolder -from guillotina.interfaces import IPermission -from guillotina.interfaces import IResource -from guillotina.interfaces import IResourceSerializeToJson -from guillotina.interfaces import IResourceSerializeToJsonSummary +import asyncio +import logging + +from zope.interface import Interface + +from guillotina import app_settings, configure +from guillotina.component import ComponentLookupError, get_multi_adapter, query_utility +from guillotina.content import get_all_behaviors, get_cached_factory +from guillotina.directives import merged_tagged_value_dict, read_permission +from guillotina.interfaces import ( + IAsyncBehavior, + IFolder, + IPermission, + IResource, + IResourceSerializeToJson, + IResourceSerializeToJsonSummary, +) from guillotina.json.serialize_value import json_compatible from guillotina.profile import profilable from guillotina.schema import get_fields -from guillotina.utils import apply_coroutine -from guillotina.utils import get_object_url -from guillotina.utils import get_security_policy -from zope.interface import Interface - -import asyncio -import logging +from guillotina.utils import apply_coroutine, get_object_url, get_security_policy logger = logging.getLogger("guillotina") diff --git a/guillotina/json/serialize_schema.py b/guillotina/json/serialize_schema.py index 4b0cc5cac..060d42684 100644 --- a/guillotina/json/serialize_schema.py +++ b/guillotina/json/serialize_schema.py @@ -1,16 +1,18 @@ # -*- coding: utf-8 -*- +from zope.interface import Interface + from guillotina import configure -from guillotina.component import get_multi_adapter -from guillotina.component import get_utility +from guillotina.component import get_multi_adapter, get_utility from guillotina.component.interfaces import IFactory -from guillotina.interfaces import IBehavior -from guillotina.interfaces import IFactorySerializeToJson -from guillotina.interfaces import IRequest -from guillotina.interfaces import ISchemaFieldSerializeToJson -from guillotina.interfaces import ISchemaSerializeToJson +from guillotina.interfaces import ( + IBehavior, + IFactorySerializeToJson, + IRequest, + ISchemaFieldSerializeToJson, + ISchemaSerializeToJson, +) from guillotina.profile import profilable from guillotina.schema import get_fields_in_order -from zope.interface import Interface @configure.adapter(for_=(IFactory, IRequest), provides=IFactorySerializeToJson) diff --git a/guillotina/json/serialize_schema_field.py b/guillotina/json/serialize_schema_field.py index bfd30568a..a1e9cc44a 100644 --- a/guillotina/json/serialize_schema_field.py +++ b/guillotina/json/serialize_schema_field.py @@ -1,30 +1,34 @@ +from zope.interface import Interface, implementedBy + from guillotina import configure from guillotina.component import get_multi_adapter from guillotina.fields.interfaces import IPatchField -from guillotina.interfaces import ICloudFileField -from guillotina.interfaces import IFileField -from guillotina.interfaces import ISchemaFieldSerializeToJson -from guillotina.interfaces import ISchemaSerializeToJson +from guillotina.interfaces import ( + ICloudFileField, + IFileField, + ISchemaFieldSerializeToJson, + ISchemaSerializeToJson, +) from guillotina.json.serialize_value import json_compatible from guillotina.profile import profilable from guillotina.schema import get_fields -from guillotina.schema.interfaces import IBool -from guillotina.schema.interfaces import IChoice -from guillotina.schema.interfaces import ICollection -from guillotina.schema.interfaces import IDate -from guillotina.schema.interfaces import IDatetime -from guillotina.schema.interfaces import IDecimal -from guillotina.schema.interfaces import IDict -from guillotina.schema.interfaces import IField -from guillotina.schema.interfaces import IFloat -from guillotina.schema.interfaces import IInt -from guillotina.schema.interfaces import IJSONField -from guillotina.schema.interfaces import IObject -from guillotina.schema.interfaces import IText -from guillotina.schema.interfaces import ITextLine -from guillotina.schema.interfaces import ITime -from zope.interface import implementedBy -from zope.interface import Interface +from guillotina.schema.interfaces import ( + IBool, + IChoice, + ICollection, + IDate, + IDatetime, + IDecimal, + IDict, + IField, + IFloat, + IInt, + IJSONField, + IObject, + IText, + ITextLine, + ITime, +) FIELDS_CACHE: dict = {} diff --git a/guillotina/json/serialize_value.py b/guillotina/json/serialize_value.py index ea5105dd9..a2c975a91 100644 --- a/guillotina/json/serialize_value.py +++ b/guillotina/json/serialize_value.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- from collections import OrderedDict -from datetime import date -from datetime import datetime -from datetime import time -from datetime import timedelta +from datetime import date, datetime, time, timedelta from decimal import Decimal + from guillotina import configure from guillotina.component import query_adapter from guillotina.i18n import Message diff --git a/guillotina/json/utils.py b/guillotina/json/utils.py index 5bbd6154e..1511a825f 100644 --- a/guillotina/json/utils.py +++ b/guillotina/json/utils.py @@ -1,17 +1,14 @@ +import asyncio +import logging +from typing import Any, Dict, List, Type + +from zope.interface import Interface, Invalid + from guillotina.component import get_multi_adapter from guillotina.db.orm.interfaces import IBaseObject from guillotina.exceptions import RequestNotFound from guillotina.interfaces import ISchemaSerializeToJson from guillotina.utils import get_current_request -from typing import Any -from typing import Dict -from typing import List -from typing import Type -from zope.interface import Interface -from zope.interface import Invalid - -import asyncio -import logging logger = logging.getLogger("guillotina") diff --git a/guillotina/metrics.py b/guillotina/metrics.py index d8512864f..4a0f68a5c 100644 --- a/guillotina/metrics.py +++ b/guillotina/metrics.py @@ -1,15 +1,11 @@ -from typing import Dict -from typing import Optional -from typing import Type - import asyncio import time import traceback +from typing import Dict, Optional, Type try: - from prometheus_client import Counter - from prometheus_client import Histogram + from prometheus_client import Counter, Histogram except ImportError: Counter = Histogram = None diff --git a/guillotina/middlewares/errors.py b/guillotina/middlewares/errors.py index c4984c4c2..4e9dc44ae 100644 --- a/guillotina/middlewares/errors.py +++ b/guillotina/middlewares/errors.py @@ -1,17 +1,14 @@ -from guillotina import error_reasons -from guillotina import logger -from guillotina import response -from guillotina import task_vars +import asyncio +import traceback +import uuid +from typing import Optional + +from guillotina import error_reasons, logger, response, task_vars from guillotina._settings import app_settings from guillotina.browser import View from guillotina.i18n import default_message_factory as _ from guillotina.interfaces import IRequest from guillotina.traversal import apply_rendering -from typing import Optional - -import asyncio -import traceback -import uuid class ErrorsMiddleware: diff --git a/guillotina/registry.py b/guillotina/registry.py index 2ce967400..364c0fb2b 100644 --- a/guillotina/registry.py +++ b/guillotina/registry.py @@ -1,10 +1,10 @@ +from zope.interface import alsoProvides, implementer + from guillotina.annotations import AnnotationData from guillotina.browser import get_physical_path from guillotina.db.orm.interfaces import IBaseObject from guillotina.interfaces import IRegistry from guillotina.schema._bootstrapinterfaces import IContextAwareDefaultFactory -from zope.interface import alsoProvides -from zope.interface import implementer REGISTRY_DATA_KEY = "_registry" diff --git a/guillotina/renderers.py b/guillotina/renderers.py index c521da8b7..1c416514d 100644 --- a/guillotina/renderers.py +++ b/guillotina/renderers.py @@ -1,19 +1,19 @@ +import json +from typing import Optional, cast + +import orjson +from zope.interface.interface import InterfaceClass + from guillotina import configure from guillotina.interfaces import IResponse from guillotina.interfaces.security import PermissionSetting from guillotina.profile import profilable from guillotina.response import Response -from typing import cast -from typing import Optional -from zope.interface.interface import InterfaceClass - -import json -import orjson def guillotina_json_default(obj): if isinstance(obj, str): - if type(obj) != str: # e.g, i18n.Message() + if type(obj) is not str: # e.g, i18n.Message() return str(obj) elif isinstance(obj, complex): return [obj.real, obj.imag] @@ -22,7 +22,7 @@ def guillotina_json_default(obj): elif isinstance(obj, InterfaceClass): return [x.__module__ + "." + x.__name__ for x in obj.__iro__] # noqa elif isinstance(obj, dict): - if type(obj) != dict: # e.g. collections.OrderedDict + if type(obj) is not dict: # e.g. collections.OrderedDict return dict(obj) try: diff --git a/guillotina/request.py b/guillotina/request.py index 82e6c9e9e..cda1f8320 100644 --- a/guillotina/request.py +++ b/guillotina/request.py @@ -1,29 +1,24 @@ +import asyncio +import enum +import time +import urllib.parse +import uuid from collections import OrderedDict from functools import update_wrapper +from http.cookies import SimpleCookie +from typing import Any, Dict, Iterator, List, Optional, Union + +import multidict +import orjson +from zope.interface import implementer + from guillotina import task_vars from guillotina.db.orm.interfaces import IBaseObject -from guillotina.interfaces import IDefaultLayer -from guillotina.interfaces import IRequest +from guillotina.interfaces import IDefaultLayer, IRequest from guillotina.interfaces.content import IApplication from guillotina.profile import profilable from guillotina.utils import execute from guillotina.utils.misc import build_url -from http.cookies import SimpleCookie -from typing import Any -from typing import Dict -from typing import Iterator -from typing import List -from typing import Optional -from typing import Union -from zope.interface import implementer - -import asyncio -import enum -import multidict -import orjson -import time -import urllib.parse -import uuid class reify(object): diff --git a/guillotina/response.py b/guillotina/response.py index cd9db0683..ffeadc0a4 100644 --- a/guillotina/response.py +++ b/guillotina/response.py @@ -1,13 +1,10 @@ +from typing import Dict, List, Optional, Tuple, Union + +from multidict import CIMultiDict, istr +from zope.interface import implementer + from guillotina.interfaces import IResponse from guillotina.request import Request -from multidict import CIMultiDict -from multidict import istr -from typing import Dict -from typing import List -from typing import Optional -from typing import Tuple -from typing import Union -from zope.interface import implementer @implementer(IResponse) diff --git a/guillotina/routes.py b/guillotina/routes.py index 6a9d095e7..42efa6642 100644 --- a/guillotina/routes.py +++ b/guillotina/routes.py @@ -1,7 +1,7 @@ -from guillotina.response import InvalidRoute - import re +from guillotina.response import InvalidRoute + URL_MATCH_RE = re.compile(r"\{[a-zA-Z0-9\_\-]+\}") _EXACT = object() diff --git a/guillotina/schema/__init__.py b/guillotina/schema/__init__.py index 17c3e0e74..dc9b4aaa4 100644 --- a/guillotina/schema/__init__.py +++ b/guillotina/schema/__init__.py @@ -15,50 +15,54 @@ ############################################################################## from guillotina.schema._bootstrapinterfaces import NO_VALUE -from guillotina.schema._field import ASCII -from guillotina.schema._field import ASCIILine -from guillotina.schema._field import Bool -from guillotina.schema._field import Bytes -from guillotina.schema._field import BytesLine -from guillotina.schema._field import Choice -from guillotina.schema._field import Container -from guillotina.schema._field import Date -from guillotina.schema._field import Datetime -from guillotina.schema._field import Decimal -from guillotina.schema._field import Dict -from guillotina.schema._field import DottedName -from guillotina.schema._field import Field -from guillotina.schema._field import Float -from guillotina.schema._field import FrozenSet -from guillotina.schema._field import Id -from guillotina.schema._field import Int -from guillotina.schema._field import InterfaceField -from guillotina.schema._field import Iterable -from guillotina.schema._field import JSONField -from guillotina.schema._field import List -from guillotina.schema._field import MaskTextLine -from guillotina.schema._field import MinMaxLen -from guillotina.schema._field import NativeString -from guillotina.schema._field import NativeStringLine -from guillotina.schema._field import Object -from guillotina.schema._field import Orderable -from guillotina.schema._field import OrderedDict -from guillotina.schema._field import Password -from guillotina.schema._field import Set -from guillotina.schema._field import SourceText -from guillotina.schema._field import Text -from guillotina.schema._field import TextLine -from guillotina.schema._field import Time -from guillotina.schema._field import Timedelta -from guillotina.schema._field import Tuple -from guillotina.schema._field import UnionField -from guillotina.schema._field import URI -from guillotina.schema._schema import get_fields -from guillotina.schema._schema import get_fields_in_order -from guillotina.schema._schema import getFieldNames -from guillotina.schema._schema import getFieldNamesInOrder -from guillotina.schema._schema import getSchemaValidationErrors -from guillotina.schema._schema import getValidationErrors +from guillotina.schema._field import ( + ASCII, + URI, + ASCIILine, + Bool, + Bytes, + BytesLine, + Choice, + Container, + Date, + Datetime, + Decimal, + Dict, + DottedName, + Field, + Float, + FrozenSet, + Id, + Int, + InterfaceField, + Iterable, + JSONField, + List, + MaskTextLine, + MinMaxLen, + NativeString, + NativeStringLine, + Object, + Orderable, + OrderedDict, + Password, + Set, + SourceText, + Text, + TextLine, + Time, + Timedelta, + Tuple, + UnionField, +) +from guillotina.schema._schema import ( + get_fields, + get_fields_in_order, + getFieldNames, + getFieldNamesInOrder, + getSchemaValidationErrors, + getValidationErrors, +) from guillotina.schema.accessors import accessors from guillotina.schema.exceptions import ValidationError diff --git a/guillotina/schema/_bootstrapfields.py b/guillotina/schema/_bootstrapfields.py index fa8218937..d7a3cd80d 100644 --- a/guillotina/schema/_bootstrapfields.py +++ b/guillotina/schema/_bootstrapfields.py @@ -11,24 +11,25 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -from guillotina.schema._bootstrapinterfaces import IContextAwareDefaultFactory -from guillotina.schema._bootstrapinterfaces import IFromUnicode -from guillotina.schema._schema import get_fields -from guillotina.schema.exceptions import ConstraintNotSatisfied -from guillotina.schema.exceptions import InvalidValue -from guillotina.schema.exceptions import NotAContainer -from guillotina.schema.exceptions import NotAnIterator -from guillotina.schema.exceptions import RequiredMissing -from guillotina.schema.exceptions import StopValidation -from guillotina.schema.exceptions import TooBig -from guillotina.schema.exceptions import TooLong -from guillotina.schema.exceptions import TooShort -from guillotina.schema.exceptions import TooSmall -from guillotina.schema.exceptions import WrongType from typing import Any -from zope.interface import Attribute -from zope.interface import implementer -from zope.interface import providedBy + +from zope.interface import Attribute, implementer, providedBy + +from guillotina.schema._bootstrapinterfaces import IContextAwareDefaultFactory, IFromUnicode +from guillotina.schema._schema import get_fields +from guillotina.schema.exceptions import ( + ConstraintNotSatisfied, + InvalidValue, + NotAContainer, + NotAnIterator, + RequiredMissing, + StopValidation, + TooBig, + TooLong, + TooShort, + TooSmall, + WrongType, +) __docformat__ = "restructuredtext" @@ -199,7 +200,7 @@ def validate(self, value): def __eq__(self, other): # should be the same type - if type(self) != type(other): + if type(self) is not type(other): return False # should have the same properties diff --git a/guillotina/schema/_field.py b/guillotina/schema/_field.py index b337f1ee6..041589956 100644 --- a/guillotina/schema/_field.py +++ b/guillotina/schema/_field.py @@ -11,88 +11,86 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -from collections import namedtuple +import decimal +import json +import re from collections import OrderedDict as NativeOrderedDict -from datetime import date -from datetime import datetime -from datetime import time -from datetime import timedelta -from guillotina.schema._bootstrapfields import Bool +from collections import namedtuple +from datetime import date, datetime, time, timedelta + +import jsonschema +from zope.interface import Interface, alsoProvides, classImplements, implementer +from zope.interface.interfaces import IInterface, IMethod + from guillotina.schema._bootstrapfields import Container # API import for __init__ -from guillotina.schema._bootstrapfields import Field -from guillotina.schema._bootstrapfields import Int -from guillotina.schema._bootstrapfields import Iterable -from guillotina.schema._bootstrapfields import MinMaxLen -from guillotina.schema._bootstrapfields import Orderable -from guillotina.schema._bootstrapfields import Password -from guillotina.schema._bootstrapfields import Text -from guillotina.schema._bootstrapfields import TextLine -from guillotina.schema.exceptions import ConstraintNotSatisfied -from guillotina.schema.exceptions import InvalidDottedName -from guillotina.schema.exceptions import InvalidId -from guillotina.schema.exceptions import InvalidURI -from guillotina.schema.exceptions import InvalidValue -from guillotina.schema.exceptions import NotUnique -from guillotina.schema.exceptions import SchemaNotFullyImplemented -from guillotina.schema.exceptions import SchemaNotProvided -from guillotina.schema.exceptions import ValidationError -from guillotina.schema.exceptions import WrongContainedType -from guillotina.schema.exceptions import WrongType +from guillotina.schema._bootstrapfields import ( + Bool, + Field, + Int, + Iterable, + MinMaxLen, + Orderable, + Password, + Text, + TextLine, +) +from guillotina.schema.exceptions import ( + ConstraintNotSatisfied, + InvalidDottedName, + InvalidId, + InvalidURI, + InvalidValue, + NotUnique, + SchemaNotFullyImplemented, + SchemaNotProvided, + ValidationError, + WrongContainedType, + WrongType, +) from guillotina.schema.fieldproperty import FieldProperty -from guillotina.schema.interfaces import IArrayJSONField -from guillotina.schema.interfaces import IASCII -from guillotina.schema.interfaces import IASCIILine -from guillotina.schema.interfaces import IBaseVocabulary -from guillotina.schema.interfaces import IBool -from guillotina.schema.interfaces import IBytes -from guillotina.schema.interfaces import IBytesLine -from guillotina.schema.interfaces import IChoice -from guillotina.schema.interfaces import IContextSourceBinder -from guillotina.schema.interfaces import IDate -from guillotina.schema.interfaces import IDatetime -from guillotina.schema.interfaces import IDecimal -from guillotina.schema.interfaces import IDict -from guillotina.schema.interfaces import IDottedName -from guillotina.schema.interfaces import IField -from guillotina.schema.interfaces import IFloat -from guillotina.schema.interfaces import IFromUnicode -from guillotina.schema.interfaces import IFrozenSet -from guillotina.schema.interfaces import IId -from guillotina.schema.interfaces import IInt -from guillotina.schema.interfaces import IInterfaceField -from guillotina.schema.interfaces import IJSONField -from guillotina.schema.interfaces import IList -from guillotina.schema.interfaces import IMaskTextLine -from guillotina.schema.interfaces import IMinMaxLen -from guillotina.schema.interfaces import IObject -from guillotina.schema.interfaces import IObjectJSONField -from guillotina.schema.interfaces import IOrderedDict -from guillotina.schema.interfaces import IPassword -from guillotina.schema.interfaces import ISet -from guillotina.schema.interfaces import ISource -from guillotina.schema.interfaces import ISourceText -from guillotina.schema.interfaces import IText -from guillotina.schema.interfaces import ITextLine -from guillotina.schema.interfaces import ITime -from guillotina.schema.interfaces import ITimedelta -from guillotina.schema.interfaces import ITuple -from guillotina.schema.interfaces import IUnionField -from guillotina.schema.interfaces import IURI +from guillotina.schema.interfaces import ( + IASCII, + IURI, + IArrayJSONField, + IASCIILine, + IBaseVocabulary, + IBool, + IBytes, + IBytesLine, + IChoice, + IContextSourceBinder, + IDate, + IDatetime, + IDecimal, + IDict, + IDottedName, + IField, + IFloat, + IFromUnicode, + IFrozenSet, + IId, + IInt, + IInterfaceField, + IJSONField, + IList, + IMaskTextLine, + IMinMaxLen, + IObject, + IObjectJSONField, + IOrderedDict, + IPassword, + ISet, + ISource, + ISourceText, + IText, + ITextLine, + ITime, + ITimedelta, + ITuple, + IUnionField, +) from guillotina.schema.utils import make_binary -from guillotina.schema.vocabulary import getVocabularyRegistry -from guillotina.schema.vocabulary import SimpleVocabulary -from guillotina.schema.vocabulary import VocabularyRegistryError -from zope.interface import alsoProvides -from zope.interface import classImplements -from zope.interface import implementer -from zope.interface import Interface -from zope.interface.interfaces import IInterface -from zope.interface.interfaces import IMethod - -import decimal -import json -import jsonschema -import re +from guillotina.schema.vocabulary import SimpleVocabulary, VocabularyRegistryError, getVocabularyRegistry __docformat__ = "restructuredtext" diff --git a/guillotina/schema/_schema.py b/guillotina/schema/_schema.py index 85362eeb4..d1f89da15 100644 --- a/guillotina/schema/_schema.py +++ b/guillotina/schema/_schema.py @@ -11,9 +11,10 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -import guillotina.schema import zope.interface.verify +import guillotina.schema + def getFieldNames(schema): """Return a list of all the Field names in a schema.""" diff --git a/guillotina/schema/accessors.py b/guillotina/schema/accessors.py index 491ed9f40..34c7c3dd3 100644 --- a/guillotina/schema/accessors.py +++ b/guillotina/schema/accessors.py @@ -11,8 +11,7 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -from zope.interface import implementedBy -from zope.interface import providedBy +from zope.interface import implementedBy, providedBy from zope.interface.interface import Method diff --git a/guillotina/schema/exceptions.py b/guillotina/schema/exceptions.py index ec69e7d73..45c202601 100644 --- a/guillotina/schema/exceptions.py +++ b/guillotina/schema/exceptions.py @@ -1,7 +1,7 @@ -from guillotina.schema._messageid import _ - import zope.interface +from guillotina.schema._messageid import _ + class StopValidation(Exception): """Raised if the validation is completed early. diff --git a/guillotina/schema/fieldproperty.py b/guillotina/schema/fieldproperty.py index a4c35f767..068539f5d 100644 --- a/guillotina/schema/fieldproperty.py +++ b/guillotina/schema/fieldproperty.py @@ -11,14 +11,15 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## +import sys from copy import copy -from guillotina import event -from guillotina.schema import interfaces -from guillotina.schema._bootstrapinterfaces import NO_VALUE + from zope import interface import guillotina.schema -import sys +from guillotina import event +from guillotina.schema import interfaces +from guillotina.schema._bootstrapinterfaces import NO_VALUE _marker = object() diff --git a/guillotina/schema/interfaces.py b/guillotina/schema/interfaces.py index 164f9a7f3..e113d61f6 100644 --- a/guillotina/schema/interfaces.py +++ b/guillotina/schema/interfaces.py @@ -12,18 +12,13 @@ # ############################################################################## -from guillotina.schema._bootstrapfields import Bool -from guillotina.schema._bootstrapfields import Field -from guillotina.schema._bootstrapfields import Int -from guillotina.schema._bootstrapfields import Text -from guillotina.schema._bootstrapfields import TextLine -from guillotina.schema._bootstrapinterfaces import IContextAwareDefaultFactory -from guillotina.schema._bootstrapinterfaces import IFromUnicode -from guillotina.schema._messageid import _ -from zope.interface import Attribute -from zope.interface import Interface +from zope.interface import Attribute, Interface from zope.interface.common.mapping import IEnumerableMapping +from guillotina.schema._bootstrapfields import Bool, Field, Int, Text, TextLine +from guillotina.schema._bootstrapinterfaces import IContextAwareDefaultFactory, IFromUnicode +from guillotina.schema._messageid import _ + __docformat__ = "reStructuredText" diff --git a/guillotina/schema/tests/states.py b/guillotina/schema/tests/states.py index 38b7683b0..850221e0e 100644 --- a/guillotina/schema/tests/states.py +++ b/guillotina/schema/tests/states.py @@ -12,10 +12,10 @@ # ############################################################################## # flake8: noqa -from guillotina.schema import Choice -from guillotina.schema import interfaces from zope.interface import implementer +from guillotina.schema import Choice, interfaces + # This table is based on information from the United States Postal Service: # http://www.usps.com/ncsc/lookups/abbreviations.html#states diff --git a/guillotina/schema/tests/test__bootstrapfields.py b/guillotina/schema/tests/test__bootstrapfields.py index 69e7f20d0..4d1423568 100644 --- a/guillotina/schema/tests/test__bootstrapfields.py +++ b/guillotina/schema/tests/test__bootstrapfields.py @@ -131,6 +131,7 @@ def _provoke(inst): def test___get___w_defaultFactory_w_ICAF_w_check(self): from zope.interface import directlyProvides + from guillotina.schema._bootstrapinterfaces import IContextAwareDefaultFactory _checked = [] @@ -590,12 +591,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_ITextLine(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import ITextLine verifyClass(ITextLine, self._getTargetClass()) def test_instance_conforms_to_ITextLine(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import ITextLine verifyObject(ITextLine, self._makeOne()) @@ -784,8 +787,7 @@ def test_validate_max(self): self.assertRaises(TooBig, field.validate, 20) def test_validate_min_and_max(self): - from guillotina.schema.exceptions import TooBig - from guillotina.schema.exceptions import TooSmall + from guillotina.schema.exceptions import TooBig, TooSmall field = self._makeOne(min=0, max=10) field.validate(0) diff --git a/guillotina/schema/tests/test__field.py b/guillotina/schema/tests/test__field.py index 79efce408..d660a0ce3 100644 --- a/guillotina/schema/tests/test__field.py +++ b/guillotina/schema/tests/test__field.py @@ -26,12 +26,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IBytes(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IBytes verifyClass(IBytes, self._getTargetClass()) def test_instance_conforms_to_IBytes(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IBytes verifyObject(IBytes, self._makeOne()) @@ -92,12 +94,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IASCII(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IASCII verifyClass(IASCII, self._getTargetClass()) def test_instance_conforms_to_IASCII(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IASCII verifyObject(IASCII, self._makeOne()) @@ -144,12 +148,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IBytesLine(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IBytesLine verifyClass(IBytesLine, self._getTargetClass()) def test_instance_conforms_to_IBytesLine(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IBytesLine verifyObject(IBytesLine, self._makeOne()) @@ -204,12 +210,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IASCIILine(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IASCIILine verifyClass(IASCIILine, self._getTargetClass()) def test_instance_conforms_to_IASCIILine(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IASCIILine verifyObject(IASCIILine, self._makeOne()) @@ -239,8 +247,7 @@ def test_validate_not_required(self): self.assertRaises(InvalidValue, field.validate, "\xab\xde") def test_validate_required(self): - from guillotina.schema.exceptions import InvalidValue - from guillotina.schema.exceptions import RequiredMissing + from guillotina.schema.exceptions import InvalidValue, RequiredMissing field = self._makeOne(required=True) field.validate("") @@ -269,12 +276,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IFloat(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IFloat verifyClass(IFloat, self._getTargetClass()) def test_instance_conforms_to_IFloat(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IFloat verifyObject(IFloat, self._makeOne()) @@ -314,8 +323,7 @@ def test_validate_max(self): self.assertRaises(TooBig, field.validate, 20.7) def test_validate_min_and_max(self): - from guillotina.schema.exceptions import TooBig - from guillotina.schema.exceptions import TooSmall + from guillotina.schema.exceptions import TooBig, TooSmall field = self._makeOne(min=-0.6, max=10.1) field.validate(0.0) @@ -352,12 +360,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IDecimal(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IDecimal verifyClass(IDecimal, self._getTargetClass()) def test_instance_conforms_to_IDecimal(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IDecimal verifyObject(IDecimal, self._makeOne()) @@ -373,6 +383,7 @@ def test_validate_not_required(self): def test_validate_required(self): import decimal + from guillotina.schema.exceptions import RequiredMissing field = self._makeOne(required=True) @@ -383,6 +394,7 @@ def test_validate_required(self): def test_validate_min(self): import decimal + from guillotina.schema.exceptions import TooSmall field = self._makeOne(min=decimal.Decimal("10.5")) @@ -393,6 +405,7 @@ def test_validate_min(self): def test_validate_max(self): import decimal + from guillotina.schema.exceptions import TooBig field = self._makeOne(max=decimal.Decimal("10.5")) @@ -403,8 +416,8 @@ def test_validate_max(self): def test_validate_min_and_max(self): import decimal - from guillotina.schema.exceptions import TooBig - from guillotina.schema.exceptions import TooSmall + + from guillotina.schema.exceptions import TooBig, TooSmall field = self._makeOne(min=decimal.Decimal("-0.6"), max=decimal.Decimal("10.1")) field.validate(decimal.Decimal("0.0")) @@ -443,18 +456,21 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IDatetime(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IDatetime verifyClass(IDatetime, self._getTargetClass()) def test_instance_conforms_to_IDatetime(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IDatetime verifyObject(IDatetime, self._makeOne()) def test_validate_wrong_types(self): from datetime import date + from guillotina.schema.exceptions import WrongType field = self._makeOne() @@ -485,6 +501,7 @@ def test_validate_required(self): def test_validate_w_min(self): from datetime import datetime + from guillotina.schema.exceptions import TooSmall d1 = datetime(2000, 10, 1) @@ -496,6 +513,7 @@ def test_validate_w_min(self): def test_validate_w_max(self): from datetime import datetime + from guillotina.schema.exceptions import TooBig d1 = datetime(2000, 10, 1) @@ -508,8 +526,8 @@ def test_validate_w_max(self): def test_validate_w_min_and_max(self): from datetime import datetime - from guillotina.schema.exceptions import TooBig - from guillotina.schema.exceptions import TooSmall + + from guillotina.schema.exceptions import TooBig, TooSmall d1 = datetime(2000, 10, 1) d2 = datetime(2000, 10, 2) @@ -535,18 +553,21 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IDate(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IDate verifyClass(IDate, self._getTargetClass()) def test_instance_conforms_to_IDate(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IDate verifyObject(IDate, self._makeOne()) def test_validate_wrong_types(self): from datetime import datetime + from guillotina.schema.exceptions import WrongType field = self._makeOne() @@ -571,6 +592,7 @@ def test_validate_not_required(self): def test_validate_required(self): from datetime import datetime + from guillotina.schema.exceptions import RequiredMissing field = self._makeOne(required=True) @@ -578,8 +600,8 @@ def test_validate_required(self): self.assertRaises(RequiredMissing, field.validate, None) def test_validate_w_min(self): - from datetime import date - from datetime import datetime + from datetime import date, datetime + from guillotina.schema.exceptions import TooSmall d1 = date(2000, 10, 1) @@ -592,6 +614,7 @@ def test_validate_w_min(self): def test_validate_w_max(self): from datetime import date + from guillotina.schema.exceptions import TooBig d1 = date(2000, 10, 1) @@ -604,8 +627,8 @@ def test_validate_w_max(self): def test_validate_w_min_and_max(self): from datetime import date - from guillotina.schema.exceptions import TooBig - from guillotina.schema.exceptions import TooSmall + + from guillotina.schema.exceptions import TooBig, TooSmall d1 = date(2000, 10, 1) d2 = date(2000, 10, 2) @@ -631,12 +654,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_ITimedelta(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import ITimedelta verifyClass(ITimedelta, self._getTargetClass()) def test_instance_conforms_to_ITimedelta(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import ITimedelta verifyObject(ITimedelta, self._makeOne()) @@ -650,6 +675,7 @@ def test_validate_not_required(self): def test_validate_required(self): from datetime import timedelta + from guillotina.schema.exceptions import RequiredMissing field = self._makeOne(required=True) @@ -658,6 +684,7 @@ def test_validate_required(self): def test_validate_min(self): from datetime import timedelta + from guillotina.schema.exceptions import TooSmall t1 = timedelta(hours=2) @@ -669,6 +696,7 @@ def test_validate_min(self): def test_validate_max(self): from datetime import timedelta + from guillotina.schema.exceptions import TooBig t1 = timedelta(minutes=1) @@ -681,8 +709,8 @@ def test_validate_max(self): def test_validate_min_and_max(self): from datetime import timedelta - from guillotina.schema.exceptions import TooBig - from guillotina.schema.exceptions import TooSmall + + from guillotina.schema.exceptions import TooBig, TooSmall t1 = timedelta(days=1) t2 = timedelta(days=2) @@ -708,12 +736,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_ITime(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import ITime verifyClass(ITime, self._getTargetClass()) def test_instance_conforms_to_ITime(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import ITime verifyObject(ITime, self._makeOne()) @@ -727,6 +757,7 @@ def test_validate_not_required(self): def test_validate_required(self): from datetime import time + from guillotina.schema.exceptions import RequiredMissing field = self._makeOne(required=True) @@ -735,6 +766,7 @@ def test_validate_required(self): def test_validate_min(self): from datetime import time + from guillotina.schema.exceptions import TooSmall t1 = time(12, 15, 37) @@ -747,6 +779,7 @@ def test_validate_min(self): def test_validate_max(self): from datetime import time + from guillotina.schema.exceptions import TooBig t1 = time(12, 15, 37) @@ -759,8 +792,8 @@ def test_validate_max(self): def test_validate_min_and_max(self): from datetime import time - from guillotina.schema.exceptions import TooBig - from guillotina.schema.exceptions import TooSmall + + from guillotina.schema.exceptions import TooBig, TooSmall t1 = time(12, 15, 37) t2 = time(12, 25, 18) @@ -796,12 +829,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IChoice(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IChoice verifyClass(IChoice, self._getTargetClass()) def test_instance_conforms_to_IChoice(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IChoice verifyObject(IChoice, self._makeOne(values=[1, 2, 3])) @@ -859,8 +894,8 @@ def _provoke(bound): def test_bind_w_voc_not_ICSB(self): from zope.interface import implementer - from guillotina.schema.interfaces import ISource - from guillotina.schema.interfaces import IBaseVocabulary + + from guillotina.schema.interfaces import IBaseVocabulary, ISource @implementer(IBaseVocabulary) @implementer(ISource) @@ -875,8 +910,8 @@ def __init__(self): def test_bind_w_voc_is_ICSB(self): from zope.interface import implementer - from guillotina.schema.interfaces import IContextSourceBinder - from guillotina.schema.interfaces import ISource + + from guillotina.schema.interfaces import IContextSourceBinder, ISource @implementer(IContextSourceBinder) @implementer(ISource) @@ -897,6 +932,7 @@ def __call__(self, context): def test_bind_w_voc_is_ICSB_but_not_ISource(self): from zope.interface import implementer + from guillotina.schema.interfaces import IContextSourceBinder @implementer(IContextSourceBinder) @@ -981,6 +1017,7 @@ def test__validate_w_named_vocabulary(self): def test__validate_source_is_ICSB_unbound(self): from zope.interface import implementer + from guillotina.schema.interfaces import IContextSourceBinder @implementer(IContextSourceBinder) @@ -993,8 +1030,9 @@ def __call__(self, context): def test__validate_source_is_ICSB_bound(self): from zope.interface import implementer - from guillotina.schema.interfaces import IContextSourceBinder + from guillotina.schema.exceptions import ConstraintNotSatisfied + from guillotina.schema.interfaces import IContextSourceBinder from guillotina.schema.tests.test_vocabulary import _makeSampleVocabulary @implementer(IContextSourceBinder) @@ -1024,12 +1062,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IURI(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IURI verifyClass(IURI, self._getTargetClass()) def test_instance_conforms_to_IURI(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IURI verifyObject(IURI, self._makeOne()) @@ -1062,8 +1102,7 @@ def test_validate_required(self): self.assertRaises(RequiredMissing, field.validate, None) def test_validate_not_a_uri(self): - from guillotina.schema.exceptions import ConstraintNotSatisfied - from guillotina.schema.exceptions import InvalidURI + from guillotina.schema.exceptions import ConstraintNotSatisfied, InvalidURI field = self._makeOne() self.assertRaises(InvalidURI, field.validate, "") @@ -1076,8 +1115,7 @@ def test_from_unicode_ok(self): self.assertEqual(field.from_unicode("http://example.com/"), "http://example.com/") def test_from_unicode_invalid(self): - from guillotina.schema.exceptions import ConstraintNotSatisfied - from guillotina.schema.exceptions import InvalidURI + from guillotina.schema.exceptions import ConstraintNotSatisfied, InvalidURI field = self._makeOne() self.assertRaises(InvalidURI, field.from_unicode, "") @@ -1096,12 +1134,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IDottedName(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IDottedName verifyClass(IDottedName, self._getTargetClass()) def test_instance_conforms_to_IDottedName(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IDottedName verifyObject(IDottedName, self._makeOne()) @@ -1171,8 +1211,7 @@ def test_validate_w_max_dots(self): self.assertRaises(InvalidDottedName, field.validate, "moar.dotted.name") def test_validate_not_a_dotted_name(self): - from guillotina.schema.exceptions import ConstraintNotSatisfied - from guillotina.schema.exceptions import InvalidDottedName + from guillotina.schema.exceptions import ConstraintNotSatisfied, InvalidDottedName field = self._makeOne() self.assertRaises(InvalidDottedName, field.validate, "") @@ -1184,8 +1223,7 @@ def test_from_unicode_dotted_name_ok(self): self.assertEqual(field.from_unicode("dotted.name"), "dotted.name") def test_from_unicode_invalid(self): - from guillotina.schema.exceptions import ConstraintNotSatisfied - from guillotina.schema.exceptions import InvalidDottedName + from guillotina.schema.exceptions import ConstraintNotSatisfied, InvalidDottedName field = self._makeOne() self.assertRaises(InvalidDottedName, field.from_unicode, "") @@ -1203,12 +1241,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IId(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IId verifyClass(IId, self._getTargetClass()) def test_instance_conforms_to_IId(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IId verifyObject(IId, self._makeOne()) @@ -1243,8 +1283,7 @@ def test_validate_required(self): self.assertRaises(RequiredMissing, field.validate, None) def test_validate_not_a_uri(self): - from guillotina.schema.exceptions import ConstraintNotSatisfied - from guillotina.schema.exceptions import InvalidId + from guillotina.schema.exceptions import ConstraintNotSatisfied, InvalidId field = self._makeOne() self.assertRaises(InvalidId, field.validate, "") @@ -1261,8 +1300,7 @@ def test_from_unicode_dotted_name_ok(self): self.assertEqual(field.from_unicode("dotted.name"), "dotted.name") def test_from_unicode_invalid(self): - from guillotina.schema.exceptions import ConstraintNotSatisfied - from guillotina.schema.exceptions import InvalidId + from guillotina.schema.exceptions import ConstraintNotSatisfied, InvalidId field = self._makeOne() self.assertRaises(InvalidId, field.from_unicode, "") @@ -1281,18 +1319,21 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IInterfaceField(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IInterfaceField verifyClass(IInterfaceField, self._getTargetClass()) def test_instance_conforms_to_IInterfaceField(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IInterfaceField verifyObject(IInterfaceField, self._makeOne()) def test_validate_wrong_types(self): from datetime import date + from guillotina.schema.exceptions import WrongType field = self._makeOne() @@ -1320,6 +1361,7 @@ class DummyInterface(Interface): def test_validate_required(self): from zope.interface import Interface + from guillotina.schema.exceptions import RequiredMissing class DummyInterface(Interface): @@ -1379,16 +1421,16 @@ def test_bind_w_value_Type(self): self.assertEqual(bound.unique, True) def test__validate_wrong_contained_type(self): - from guillotina.schema.exceptions import WrongContainedType from guillotina.schema._bootstrapfields import Text + from guillotina.schema.exceptions import WrongContainedType text = Text() absc = self._makeOne(text) self.assertRaises(WrongContainedType, absc.validate, [1]) def test__validate_miss_uniqueness(self): - from guillotina.schema.exceptions import NotUnique from guillotina.schema._bootstrapfields import Text + from guillotina.schema.exceptions import NotUnique text = Text() absc = self._makeOne(text, True) @@ -1406,12 +1448,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_ITuple(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import ITuple verifyClass(ITuple, self._getTargetClass()) def test_instance_conforms_to_ITuple(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import ITuple verifyObject(ITuple, self._makeOne()) @@ -1465,8 +1509,7 @@ def test_validate_max_length(self): self.assertRaises(TooLong, field.validate, (1, 2, 3)) def test_validate_min_length_and_max_length(self): - from guillotina.schema.exceptions import TooLong - from guillotina.schema.exceptions import TooShort + from guillotina.schema.exceptions import TooLong, TooShort field = self._makeOne(min_length=1, max_length=2) field.validate((1,)) @@ -1486,12 +1529,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IList(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IList verifyClass(IList, self._getTargetClass()) def test_instance_conforms_to_IList(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IList verifyObject(IList, self._makeOne()) @@ -1546,8 +1591,7 @@ def test_validate_max_length(self): self.assertRaises(TooLong, field.validate, [1, 2, 3]) def test_validate_min_length_and_max_length(self): - from guillotina.schema.exceptions import TooLong - from guillotina.schema.exceptions import TooShort + from guillotina.schema.exceptions import TooLong, TooShort field = self._makeOne(min_length=1, max_length=2) field.validate([1]) @@ -1567,12 +1611,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_ISet(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import ISet verifyClass(ISet, self._getTargetClass()) def test_instance_conforms_to_ISet(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import ISet verifyObject(ISet, self._makeOne()) @@ -1635,8 +1681,7 @@ def test_validate_max_length(self): self.assertRaises(TooLong, field.validate, set((1, 2, 3))) def test_validate_min_length_and_max_length(self): - from guillotina.schema.exceptions import TooLong - from guillotina.schema.exceptions import TooShort + from guillotina.schema.exceptions import TooLong, TooShort field = self._makeOne(min_length=1, max_length=2) field.validate(set((1,))) @@ -1656,12 +1701,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IFrozenSet(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IFrozenSet verifyClass(IFrozenSet, self._getTargetClass()) def test_instance_conforms_to_IFrozenSet(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IFrozenSet verifyObject(IFrozenSet, self._makeOne()) @@ -1721,8 +1768,7 @@ def test_validate_max_length(self): self.assertRaises(TooLong, field.validate, frozenset((1, 2, 3))) def test_validate_min_length_and_max_length(self): - from guillotina.schema.exceptions import TooLong - from guillotina.schema.exceptions import TooShort + from guillotina.schema.exceptions import TooLong, TooShort field = self._makeOne(min_length=1, max_length=2) field.validate(frozenset((1,))) @@ -1771,10 +1817,9 @@ def _getErrors(self, f, *args, **kw): self.fail("Expected WrongContainedType Error") def _makeCycles(self): - from zope.interface import Interface - from zope.interface import implementer - from guillotina.schema import Object - from guillotina.schema import List + from zope.interface import Interface, implementer + + from guillotina.schema import List, Object from guillotina.schema._messageid import _ class IUnit(Interface): @@ -1813,12 +1858,14 @@ def __init__(self, unit): def test_class_conforms_to_IObject(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IObject verifyClass(IObject, self._getTargetClass()) def test_instance_conforms_to_IObject(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IObject verifyObject(IObject, self._makeOne()) @@ -1844,8 +1891,8 @@ def test__validate_w_empty_schema(self): objf.validate(object()) # doesn't raise def test__validate_w_value_not_providing_schema(self): - from guillotina.schema.exceptions import SchemaNotProvided from guillotina.schema._bootstrapfields import Text + from guillotina.schema.exceptions import SchemaNotProvided schema = self._makeSchema(foo=Text(), bar=Text()) objf = self._makeOne(schema) @@ -1853,11 +1900,14 @@ def test__validate_w_value_not_providing_schema(self): def test__validate_w_value_providing_invalid_schema(self): from zope.interface import implementer - from guillotina.schema.exceptions import SchemaNotProvided - from guillotina.schema.exceptions import WrongContainedType - from guillotina.schema.exceptions import RequiredMissing - from guillotina.schema.exceptions import WrongType + from guillotina.schema._bootstrapfields import Text + from guillotina.schema.exceptions import ( + RequiredMissing, + SchemaNotProvided, + WrongContainedType, + WrongType, + ) schema = self._makeSchema(foo=Text()) objf = self._makeOne(schema) @@ -1874,9 +1924,9 @@ def test__validate_w_value_providing_invalid_schema(self): def test__validate_w_value_providing_schema_but_missing_fields(self): from zope.interface import implementer - from guillotina.schema.exceptions import RequiredMissing - from guillotina.schema.exceptions import WrongContainedType + from guillotina.schema._bootstrapfields import Text + from guillotina.schema.exceptions import RequiredMissing, WrongContainedType schema = self._makeSchema(foo=Text(required=True), bar=Text(required=True)) @@ -1894,10 +1944,9 @@ class Broken(object): def test__validate_w_value_providing_schema_but_invalid_fields(self): from zope.interface import implementer - from guillotina.schema.exceptions import WrongContainedType - from guillotina.schema.exceptions import RequiredMissing - from guillotina.schema.exceptions import WrongType + from guillotina.schema._bootstrapfields import Text + from guillotina.schema.exceptions import RequiredMissing, WrongContainedType, WrongType schema = self._makeSchema(foo=Text(required=True), bar=Text(required=True)) @@ -1920,6 +1969,7 @@ class Broken(object): def test__validate_w_value_providing_schema(self): from zope.interface import implementer + from guillotina.schema._bootstrapfields import Text from guillotina.schema._field import Choice @@ -1935,9 +1985,9 @@ class OK(object): objf.validate(OK()) # doesn't raise def test__validate_interface_inheritance(self): + from zope.interface import Interface, implementer + from guillotina.schema import Int, Object - from zope.interface import implementer - from zope.interface import Interface class IFoo(Interface): foo = Int() @@ -2005,12 +2055,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IDict(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IDict verifyClass(IDict, self._getTargetClass()) def test_instance_conforms_to_IDict(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IDict verifyObject(IDict, self._makeOne()) @@ -2052,8 +2104,8 @@ def test_validate_required(self): self.assertRaises(RequiredMissing, field.validate, None) def test_validate_invalid_key_type(self): - from guillotina.schema.exceptions import WrongContainedType from guillotina.schema._bootstrapfields import Int + from guillotina.schema.exceptions import WrongContainedType field = self._makeOne(key_type=Int()) field.validate({}) @@ -2062,8 +2114,8 @@ def test_validate_invalid_key_type(self): self.assertRaises(WrongContainedType, field.validate, {"a": 1}) def test_validate_invalid_value_type(self): - from guillotina.schema.exceptions import WrongContainedType from guillotina.schema._bootstrapfields import Int + from guillotina.schema.exceptions import WrongContainedType field = self._makeOne(value_type=Int()) field.validate({}) @@ -2089,8 +2141,7 @@ def test_validate_max_length(self): self.assertRaises(TooLong, field.validate, {1: "a", 2: "b", 3: "c"}) def test_validate_min_length_and_max_length(self): - from guillotina.schema.exceptions import TooLong - from guillotina.schema.exceptions import TooShort + from guillotina.schema.exceptions import TooLong, TooShort field = self._makeOne(min_length=1, max_length=2) field.validate({1: "a"}) @@ -2132,20 +2183,20 @@ def _getTargetClass(self): return UnionField def _makeOne(self, *args, **kw): - from guillotina.schema._field import Text - from guillotina.schema._field import Int - from guillotina.schema._field import List + from guillotina.schema._field import Int, List, Text return self._getTargetClass()(Text(**kw), Int(**kw), List(**kw), *args, **kw) def test_class_conforms_to_IUnionField(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IUnionField verifyClass(IUnionField, self._getTargetClass()) def test_instance_conforms_to_IUnionField(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IUnionField verifyObject(IUnionField, self._makeOne()) @@ -2199,6 +2250,7 @@ class DummyInstance(object): def _makeSampleVocabulary(): from zope.interface import implementer + from guillotina.schema.interfaces import IVocabulary class SampleTerm(object): diff --git a/guillotina/schema/tests/test_accessors.py b/guillotina/schema/tests/test_accessors.py index 5c802b853..4668dfc17 100644 --- a/guillotina/schema/tests/test_accessors.py +++ b/guillotina/schema/tests/test_accessors.py @@ -39,6 +39,7 @@ def test_ctor_not_created_inside_interface(self): def test_ctor_created_inside_interface(self): from zope.interface import Interface + from guillotina.schema import Text field = Text(title="Hmm") @@ -51,15 +52,14 @@ class IFoo(Interface): self.assertEqual(getter.__doc__, "get Hmm") def test___provides___w_field_no_provides(self): - from zope.interface import implementedBy - from zope.interface import providedBy + from zope.interface import implementedBy, providedBy wrapped = self._makeOne(object()) self.assertEqual(list(providedBy(wrapped)), list(implementedBy(self._getTargetClass()))) def test___provides___w_field_w_provides(self): - from zope.interface import implementedBy - from zope.interface import providedBy + from zope.interface import implementedBy, providedBy + from guillotina.schema import Text field = Text() @@ -152,6 +152,7 @@ def getter(self): def test_set_readonly(self): from zope.interface import Interface + from guillotina.schema import Text field = Text(readonly=True) @@ -242,6 +243,7 @@ def test_ctor_not_created_inside_interface(self): def test_ctor_created_inside_interface(self): from zope.interface import Interface + from guillotina.schema import Text field = Text(title="Hmm") @@ -275,6 +277,7 @@ def _callFUT(self, *args, **kw): def test_w_only_read_accessor(self): from zope.interface import Interface + from guillotina.schema import Text field = Text(title="Hmm", readonly=True) @@ -295,6 +298,7 @@ class IFoo(Interface): def test_w_read_and_write_accessors(self): from zope.interface import Interface + from guillotina.schema import Text field = Text(title="Hmm") diff --git a/guillotina/schema/tests/test_fieldproperty.py b/guillotina/schema/tests/test_fieldproperty.py index 82761fef7..d58727e68 100644 --- a/guillotina/schema/tests/test_fieldproperty.py +++ b/guillotina/schema/tests/test_fieldproperty.py @@ -255,8 +255,8 @@ class Foo(object): self.assertEqual(_validated, ["123"]) def test_field_event(self): - from guillotina.schema import Text from guillotina.component.event import sync_subscribers + from guillotina.schema import Text from guillotina.schema.fieldproperty import FieldUpdatedEvent log = [] @@ -277,8 +277,8 @@ def test_field_event(self): ) def test_field_event_update(self): - from guillotina.schema import Text from guillotina.component.event import sync_subscribers + from guillotina.schema import Text from guillotina.schema.fieldproperty import FieldUpdatedEvent field = Text(__name__="testing", description="DESCRIPTION", default="DEFAULT", required=True) @@ -539,8 +539,8 @@ class Foo(object): self.assertRaises(ValueError, setattr, foo, "testing", "123") def test_field_event_update(self): - from guillotina.schema import Text from guillotina.component.event import sync_subscribers + from guillotina.schema import Text from guillotina.schema.fieldproperty import FieldUpdatedEvent field = Text(__name__="testing", description="DESCRIPTION", default="DEFAULT", required=True) @@ -566,8 +566,8 @@ class Foo(object): def test_field_event(self): # fieldproperties are everywhere including in field themselfs # so event are triggered - from guillotina.schema import Text from guillotina.component.event import sync_subscribers + from guillotina.schema import Text from guillotina.schema.fieldproperty import FieldUpdatedEvent log = [] @@ -591,9 +591,8 @@ def test_field_event(self): def _getSchema(): from zope.interface import Interface - from guillotina.schema import Bytes - from guillotina.schema import Float - from guillotina.schema import Text + + from guillotina.schema import Bytes, Float, Text class Schema(Interface): title = Text(description="Short summary", default="say something") @@ -608,8 +607,7 @@ class CreateFieldPropertiesTests(unittest.TestCase): """Testing ..fieldproperty.createFieldProperties.""" def test_creates_fieldproperties_on_class(self): - from guillotina.schema.fieldproperty import createFieldProperties - from guillotina.schema.fieldproperty import FieldProperty + from guillotina.schema.fieldproperty import FieldProperty, createFieldProperties schema = _getSchema() diff --git a/guillotina/schema/tests/test_interfaces.py b/guillotina/schema/tests/test_interfaces.py index 5f3b5a433..b4409f96f 100644 --- a/guillotina/schema/tests/test_interfaces.py +++ b/guillotina/schema/tests/test_interfaces.py @@ -23,11 +23,7 @@ def test_non_fields(self): self.assertEqual(self._callFUT(object()), False) def test_w_normal_fields(self): - from guillotina.schema import Text - from guillotina.schema import Bytes - from guillotina.schema import Int - from guillotina.schema import Float - from guillotina.schema import Decimal + from guillotina.schema import Bytes, Decimal, Float, Int, Text self.assertEqual(self._callFUT(Text()), True) self.assertEqual(self._callFUT(Bytes()), True) @@ -37,6 +33,7 @@ def test_w_normal_fields(self): def test_w_explicitly_provided(self): from zope.interface import directlyProvides + from guillotina.schema.interfaces import IField class Foo(object): @@ -64,11 +61,7 @@ def test_w_non_fields(self): self.assertEqual(self._callFUT([object()]), False) def test_w_fields(self): - from guillotina.schema import Text - from guillotina.schema import Bytes - from guillotina.schema import Int - from guillotina.schema import Float - from guillotina.schema import Decimal + from guillotina.schema import Bytes, Decimal, Float, Int, Text self.assertEqual(self._callFUT([Text()]), True) self.assertEqual(self._callFUT([Bytes()]), True) @@ -78,11 +71,7 @@ def test_w_fields(self): self.assertEqual(self._callFUT([Text(), Bytes(), Int(), Float(), Decimal()]), True) def test_w_mixed(self): - from guillotina.schema import Text - from guillotina.schema import Bytes - from guillotina.schema import Int - from guillotina.schema import Float - from guillotina.schema import Decimal + from guillotina.schema import Bytes, Decimal, Float, Int, Text self.assertEqual(self._callFUT([Text(), 0]), False) self.assertEqual(self._callFUT([Text(), Bytes(), Int(), Float(), Decimal(), 0]), False) diff --git a/guillotina/schema/tests/test_schema.py b/guillotina/schema/tests/test_schema.py index b9bea2f78..b8954c926 100644 --- a/guillotina/schema/tests/test_schema.py +++ b/guillotina/schema/tests/test_schema.py @@ -12,13 +12,14 @@ # ############################################################################## # flake8: noqa -from zope.interface.interface import InterfaceClass - import unittest +from zope.interface.interface import InterfaceClass + def _makeSchema(): from zope.interface import Interface + from guillotina.schema import Bytes return InterfaceClass( @@ -154,6 +155,7 @@ class IEmpty(Interface): def test_schema_with_field_errors(self): from zope.interface import Interface + from guillotina.schema import Text from guillotina.schema.exceptions import SchemaNotFullyImplemented @@ -166,8 +168,7 @@ class IWithRequired(Interface): self.assertEqual(errors[0][1].__class__, SchemaNotFullyImplemented) def test_schema_with_invariant_errors(self): - from zope.interface import Interface - from zope.interface import invariant + from zope.interface import Interface, invariant from zope.interface.exceptions import Invalid class IWithFailingInvariant(Interface): @@ -181,8 +182,7 @@ def _epic_fail(obj): self.assertEqual(errors[0][1].__class__, Invalid) def test_schema_with_invariant_ok(self): - from zope.interface import Interface - from zope.interface import invariant + from zope.interface import Interface, invariant class IWithPassingInvariant(Interface): @invariant @@ -200,8 +200,7 @@ def _callFUT(self, schema, object): return getSchemaValidationErrors(schema, object) def test_schema_wo_fields(self): - from zope.interface import Interface - from zope.interface import Attribute + from zope.interface import Attribute, Interface class INoFields(Interface): def method(): @@ -214,6 +213,7 @@ def method(): def test_schema_with_fields_ok(self): from zope.interface import Interface + from guillotina.schema import Text class IWithFields(Interface): @@ -229,6 +229,7 @@ class Obj(object): def test_schema_with_missing_field(self): from zope.interface import Interface + from guillotina.schema import Text from guillotina.schema.exceptions import SchemaNotFullyImplemented @@ -242,6 +243,7 @@ class IWithRequired(Interface): def test_schema_with_invalid_field(self): from zope.interface import Interface + from guillotina.schema import Int from guillotina.schema.exceptions import TooSmall diff --git a/guillotina/schema/tests/test_states.py b/guillotina/schema/tests/test_states.py index 322d776b3..d828b48b5 100644 --- a/guillotina/schema/tests/test_states.py +++ b/guillotina/schema/tests/test_states.py @@ -17,9 +17,8 @@ class StateSelectionTest(unittest.TestCase): def setUp(self): - from guillotina.schema.vocabulary import _clear - from guillotina.schema.vocabulary import getVocabularyRegistry from guillotina.schema.tests.states import StateVocabulary + from guillotina.schema.vocabulary import _clear, getVocabularyRegistry _clear() vr = getVocabularyRegistry() @@ -32,6 +31,7 @@ def tearDown(self): def _makeSchema(self): from zope.interface import Interface + from guillotina.schema import Choice from guillotina.schema.tests.states import StateVocabulary @@ -59,6 +59,7 @@ class IBirthInfo(Interface): def test_default_presentation(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IVocabulary schema = self._makeSchema() @@ -69,6 +70,7 @@ def test_default_presentation(self): def test_contains(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IVocabulary from guillotina.schema.tests.states import StateVocabulary @@ -89,6 +91,7 @@ def test_contains(self): def test_prebound_vocabulary(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IVocabulary schema = self._makeSchema() diff --git a/guillotina/schema/tests/test_vocabulary.py b/guillotina/schema/tests/test_vocabulary.py index 00b9b1ce1..af92e1bc3 100644 --- a/guillotina/schema/tests/test_vocabulary.py +++ b/guillotina/schema/tests/test_vocabulary.py @@ -26,12 +26,14 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_ITokenizedTerm(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import ITokenizedTerm verifyClass(ITokenizedTerm, self._getTargetClass()) def test_instance_conforms_to_ITokenizedTerm(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import ITokenizedTerm verifyObject(ITokenizedTerm, self._makeOne("VALUE")) @@ -74,18 +76,21 @@ def _makeOne(self, *args, **kw): def test_class_conforms_to_IVocabularyTokenized(self): from zope.interface.verify import verifyClass + from guillotina.schema.interfaces import IVocabularyTokenized verifyClass(IVocabularyTokenized, self._getTargetClass()) def test_instance_conforms_to_IVocabularyTokenized(self): from zope.interface.verify import verifyObject + from guillotina.schema.interfaces import IVocabularyTokenized verifyObject(IVocabularyTokenized, self._makeOne(())) def test_ctor_additional_interfaces(self): from zope.interface import Interface + from guillotina.schema.vocabulary import SimpleTerm class IStupid(Interface): @@ -106,6 +111,7 @@ class IStupid(Interface): def test_fromValues(self): from zope.interface import Interface + from guillotina.schema.interfaces import ITokenizedTerm class IStupid(Interface): @@ -122,6 +128,7 @@ class IStupid(Interface): def test_fromItems(self): from zope.interface import Interface + from guillotina.schema.interfaces import ITokenizedTerm class IStupid(Interface): @@ -240,11 +247,10 @@ def tree_vocab_3(self): return self._getTargetClass().fromDict(self.business_tree()) def test_implementation(self): - from zope.interface.verify import verifyObject from zope.interface.common.mapping import IEnumerableMapping - from guillotina.schema.interfaces import ITreeVocabulary - from guillotina.schema.interfaces import IVocabulary - from guillotina.schema.interfaces import IVocabularyTokenized + from zope.interface.verify import verifyObject + + from guillotina.schema.interfaces import ITreeVocabulary, IVocabulary, IVocabularyTokenized for v in [self.tree_vocab_2(), self.tree_vocab_3()]: self.assertTrue(verifyObject(IEnumerableMapping, v)) @@ -508,8 +514,7 @@ def tearDown(self): _clear() def test_setVocabularyRegistry(self): - from guillotina.schema.vocabulary import setVocabularyRegistry - from guillotina.schema.vocabulary import getVocabularyRegistry + from guillotina.schema.vocabulary import getVocabularyRegistry, setVocabularyRegistry r = _makeDummyRegistry() setVocabularyRegistry(r) @@ -527,6 +532,7 @@ def test_getVocabularyRegistry(self): def _makeSampleVocabulary(): from zope.interface import implementer + from guillotina.schema.interfaces import IVocabulary class SampleTerm(object): diff --git a/guillotina/schema/utils.py b/guillotina/schema/utils.py index 50960443b..03e623267 100644 --- a/guillotina/schema/utils.py +++ b/guillotina/schema/utils.py @@ -1,4 +1,5 @@ from copy import deepcopy + from guillotina.schema.interfaces import IContextAwareDefaultFactory diff --git a/guillotina/schema/vocabulary.py b/guillotina/schema/vocabulary.py index 3efc38b4b..9c8b44554 100644 --- a/guillotina/schema/vocabulary.py +++ b/guillotina/schema/vocabulary.py @@ -15,13 +15,16 @@ """ from collections import OrderedDict -from guillotina.schema.interfaces import ITitledTokenizedTerm -from guillotina.schema.interfaces import ITokenizedTerm -from guillotina.schema.interfaces import ITreeVocabulary -from guillotina.schema.interfaces import IVocabularyRegistry -from guillotina.schema.interfaces import IVocabularyTokenized -from zope.interface import directlyProvides -from zope.interface import implementer + +from zope.interface import directlyProvides, implementer + +from guillotina.schema.interfaces import ( + ITitledTokenizedTerm, + ITokenizedTerm, + ITreeVocabulary, + IVocabularyRegistry, + IVocabularyTokenized, +) # simple vocabularies performing enumerated-like tasks diff --git a/guillotina/security/permission.py b/guillotina/security/permission.py index 8e5996e89..bfe046e13 100644 --- a/guillotina/security/permission.py +++ b/guillotina/security/permission.py @@ -1,6 +1,7 @@ +from zope.interface import implementer + from guillotina.component import get_utilities_for from guillotina.interfaces import IPermission -from zope.interface import implementer @implementer(IPermission) diff --git a/guillotina/security/policy.py b/guillotina/security/policy.py index ad275b03d..907c64731 100644 --- a/guillotina/security/policy.py +++ b/guillotina/security/policy.py @@ -1,30 +1,32 @@ +from typing import Dict, List, Optional, Union + +from lru import LRU + from guillotina import configure from guillotina.auth.users import SystemUser -from guillotina.component import get_utility -from guillotina.component import query_adapter +from guillotina.component import get_utility, query_adapter from guillotina.db.orm.interfaces import IBaseObject -from guillotina.interfaces import Allow -from guillotina.interfaces import AllowSingle -from guillotina.interfaces import Deny -from guillotina.interfaces import IGroups -from guillotina.interfaces import IInheritPermissionMap -from guillotina.interfaces import IPrincipal -from guillotina.interfaces import IPrincipalPermissionMap -from guillotina.interfaces import IPrincipalRoleMap -from guillotina.interfaces import IRolePermissionMap -from guillotina.interfaces import ISecurityPolicy -from guillotina.interfaces import IView -from guillotina.interfaces import Public -from guillotina.interfaces import Unset +from guillotina.interfaces import ( + Allow, + AllowSingle, + Deny, + IGroups, + IInheritPermissionMap, + IPrincipal, + IPrincipalPermissionMap, + IPrincipalRoleMap, + IRolePermissionMap, + ISecurityPolicy, + IView, + Public, + Unset, +) from guillotina.profile import profilable -from guillotina.security.security_code import principal_permission_manager -from guillotina.security.security_code import principal_role_manager -from guillotina.security.security_code import role_permission_manager -from lru import LRU -from typing import Dict -from typing import List -from typing import Optional -from typing import Union +from guillotina.security.security_code import ( + principal_permission_manager, + principal_role_manager, + role_permission_manager, +) code_principal_permission_setting = principal_permission_manager.get_setting diff --git a/guillotina/security/security_code.py b/guillotina/security/security_code.py index a571ce3b4..b410c39ae 100644 --- a/guillotina/security/security_code.py +++ b/guillotina/security/security_code.py @@ -1,15 +1,18 @@ +from zope.interface import implementer + from guillotina.auth.role import check_role -from guillotina.interfaces import Allow -from guillotina.interfaces import AllowSingle -from guillotina.interfaces import Deny -from guillotina.interfaces import IInheritPermissionManager -from guillotina.interfaces import IPrincipalPermissionManager -from guillotina.interfaces import IPrincipalRoleManager -from guillotina.interfaces import IRolePermissionManager -from guillotina.interfaces import Unset +from guillotina.interfaces import ( + Allow, + AllowSingle, + Deny, + IInheritPermissionManager, + IPrincipalPermissionManager, + IPrincipalRoleManager, + IRolePermissionManager, + Unset, +) from guillotina.security.permission import get_all_permissions from guillotina.security.securitymap import SecurityMap -from zope.interface import implementer @implementer(IPrincipalRoleManager) diff --git a/guillotina/security/security_local.py b/guillotina/security/security_local.py index 4201c49fe..720a85c80 100644 --- a/guillotina/security/security_local.py +++ b/guillotina/security/security_local.py @@ -1,14 +1,16 @@ from guillotina import configure -from guillotina.interfaces import Allow -from guillotina.interfaces import AllowSingle -from guillotina.interfaces import Deny -from guillotina.interfaces import IInheritPermissionManager -from guillotina.interfaces import INHERIT_KEY -from guillotina.interfaces import IPrincipalPermissionManager -from guillotina.interfaces import IPrincipalRoleManager -from guillotina.interfaces import IResource -from guillotina.interfaces import IRolePermissionManager -from guillotina.interfaces import Unset +from guillotina.interfaces import ( + INHERIT_KEY, + Allow, + AllowSingle, + Deny, + IInheritPermissionManager, + IPrincipalPermissionManager, + IPrincipalRoleManager, + IResource, + IRolePermissionManager, + Unset, +) from guillotina.security.securitymap import GuillotinaSecurityMap diff --git a/guillotina/security/utils.py b/guillotina/security/utils.py index 57dd4535e..9bc5d3777 100644 --- a/guillotina/security/utils.py +++ b/guillotina/security/utils.py @@ -2,20 +2,23 @@ from guillotina.event import notify from guillotina.events import ObjectPermissionsModifiedEvent from guillotina.exceptions import PreconditionFailed -from guillotina.interfaces import Deny -from guillotina.interfaces import IInheritPermissionManager -from guillotina.interfaces import IInheritPermissionMap -from guillotina.interfaces import IPrincipalPermissionManager -from guillotina.interfaces import IPrincipalPermissionMap -from guillotina.interfaces import IPrincipalRoleManager -from guillotina.interfaces import IPrincipalRoleMap -from guillotina.interfaces import IRolePermissionManager -from guillotina.interfaces import IRolePermissionMap -from guillotina.security.policy import cached_principals -from guillotina.security.policy import cached_roles -from guillotina.security.security_code import principal_permission_manager -from guillotina.security.security_code import principal_role_manager -from guillotina.security.security_code import role_permission_manager +from guillotina.interfaces import ( + Deny, + IInheritPermissionManager, + IInheritPermissionMap, + IPrincipalPermissionManager, + IPrincipalPermissionMap, + IPrincipalRoleManager, + IPrincipalRoleMap, + IRolePermissionManager, + IRolePermissionMap, +) +from guillotina.security.policy import cached_principals, cached_roles +from guillotina.security.security_code import ( + principal_permission_manager, + principal_role_manager, + role_permission_manager, +) def protect_view(cls, permission): @@ -85,7 +88,7 @@ def settings_for_object(ob): if inherit_permissions is not None: settings = inherit_permissions.get_locked_permissions() data["perminhe"] = [] - for (p, s) in settings: + for p, s in settings: if s is Deny: locked_permissions.append(p) data["perminhe"].append({"permission": p, "setting": s}) diff --git a/guillotina/subscribers.py b/guillotina/subscribers.py index 969bf4a4f..91bc99901 100644 --- a/guillotina/subscribers.py +++ b/guillotina/subscribers.py @@ -1,11 +1,11 @@ from datetime import datetime + from dateutil.tz import tzutc + from guillotina import configure from guillotina.component._api import get_component_registry -from guillotina.component.interfaces import ComponentLookupError -from guillotina.component.interfaces import IObjectEvent -from guillotina.interfaces import IObjectModifiedEvent -from guillotina.interfaces import IResource +from guillotina.component.interfaces import ComponentLookupError, IObjectEvent +from guillotina.interfaces import IObjectModifiedEvent, IResource _zone = tzutc() diff --git a/guillotina/task_vars.py b/guillotina/task_vars.py index 50ead784c..1dd5bcf31 100644 --- a/guillotina/task_vars.py +++ b/guillotina/task_vars.py @@ -1,14 +1,8 @@ from contextvars import ContextVar -from guillotina.db.interfaces import ITransaction -from guillotina.db.interfaces import ITransactionManager -from guillotina.interfaces import IContainer -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IPrincipal -from guillotina.interfaces import IRegistry -from guillotina.interfaces import IRequest -from guillotina.interfaces import ISecurityPolicy -from typing import Dict -from typing import Optional +from typing import Dict, Optional + +from guillotina.db.interfaces import ITransaction, ITransactionManager +from guillotina.interfaces import IContainer, IDatabase, IPrincipal, IRegistry, IRequest, ISecurityPolicy request: ContextVar[Optional[IRequest]] = ContextVar("g_request", default=None) diff --git a/guillotina/test_package.py b/guillotina/test_package.py index 98ddfb204..4c08a2157 100644 --- a/guillotina/test_package.py +++ b/guillotina/test_package.py @@ -1,50 +1,43 @@ # this is for testing.py, do not import into other modules -from guillotina import configure -from guillotina import fields -from guillotina import schema +import asyncio +import json +import os +import tempfile +import typing +from shutil import copyfile +from typing import AsyncIterator + +from zope.interface import Interface, implementer + +from guillotina import configure, fields, schema from guillotina.async_util import IAsyncUtility -from guillotina.behaviors.instance import AnnotationBehavior -from guillotina.behaviors.instance import ContextBehavior +from guillotina.behaviors.instance import AnnotationBehavior, ContextBehavior from guillotina.behaviors.properties import ContextProperty from guillotina.component import get_multi_adapter -from guillotina.content import Item -from guillotina.content import Resource -from guillotina.directives import index_field -from guillotina.directives import metadata -from guillotina.directives import read_permission -from guillotina.directives import write_permission -from guillotina.exceptions import FileNotFoundException -from guillotina.exceptions import NoIndexField +from guillotina.content import Item, Resource +from guillotina.directives import index_field, metadata, read_permission, write_permission +from guillotina.exceptions import FileNotFoundException, NoIndexField from guillotina.fields import CloudFileField from guillotina.files import BaseCloudFile from guillotina.files.exceptions import RangeNotFound -from guillotina.interfaces import IApplication -from guillotina.interfaces import IContainer -from guillotina.interfaces import IExternalFileStorageManager -from guillotina.interfaces import IFile -from guillotina.interfaces import IFileField -from guillotina.interfaces import IFileNameGenerator -from guillotina.interfaces import IIDGenerator -from guillotina.interfaces import IItem -from guillotina.interfaces import IJSONToValue -from guillotina.interfaces import IObjectAddedEvent -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResource +from guillotina.interfaces import ( + IApplication, + IContainer, + IExternalFileStorageManager, + IFile, + IFileField, + IFileNameGenerator, + IIDGenerator, + IItem, + IJSONToValue, + IObjectAddedEvent, + IRequest, + IResource, +) from guillotina.response import HTTPUnprocessableEntity from guillotina.schema import Object from guillotina.schema.interfaces import IContextAwareDefaultFactory -from guillotina.utils import apply_coroutine -from guillotina.utils import execute -from shutil import copyfile -from typing import AsyncIterator -from zope.interface import implementer -from zope.interface import Interface - -import asyncio -import json -import os -import tempfile -import typing +from guillotina.utils import apply_coroutine, execute app_settings = {"applications": ["guillotina"]} diff --git a/guillotina/testing.py b/guillotina/testing.py index 6aecbfb64..303073bd2 100644 --- a/guillotina/testing.py +++ b/guillotina/testing.py @@ -1,11 +1,10 @@ +import base64 +import os from copy import deepcopy +from typing import Any, Dict + from guillotina.auth.users import ROOT_USER_ID from guillotina.utils import lazy_apply -from typing import Any -from typing import Dict - -import base64 -import os TESTING_PORT = 55001 diff --git a/guillotina/tests/cache/test_cache_memory.py b/guillotina/tests/cache/test_cache_memory.py index 7bbaf408c..0181d2a1a 100644 --- a/guillotina/tests/cache/test_cache_memory.py +++ b/guillotina/tests/cache/test_cache_memory.py @@ -1,15 +1,15 @@ +import asyncio +from unittest import mock +from unittest.mock import MagicMock, Mock + +import pytest + from guillotina.component import get_utility from guillotina.contrib.cache.strategy import BasicCache from guillotina.db.transaction import Transaction from guillotina.interfaces import ICacheUtility from guillotina.tests import mocks from guillotina.tests.utils import create_content -from unittest import mock -from unittest.mock import MagicMock -from unittest.mock import Mock - -import asyncio -import pytest pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/cache/test_cache_pubsub.py b/guillotina/tests/cache/test_cache_pubsub.py index 58c7f8e67..94c884421 100644 --- a/guillotina/tests/cache/test_cache_pubsub.py +++ b/guillotina/tests/cache/test_cache_pubsub.py @@ -1,17 +1,17 @@ +import asyncio +import pickle + +import pytest + from guillotina import app_settings from guillotina.component import get_utility -from guillotina.contrib.cache import CACHE_PREFIX -from guillotina.contrib.cache import serialize +from guillotina.contrib.cache import CACHE_PREFIX, serialize from guillotina.contrib.cache.strategy import BasicCache from guillotina.interfaces import ICacheUtility from guillotina.tests import mocks from guillotina.tests.utils import create_content from guillotina.utils import resolve_dotted_name -import asyncio -import pickle -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/cache/test_cache_store.py b/guillotina/tests/cache/test_cache_store.py index f4e8cb38f..b5c15a02e 100644 --- a/guillotina/tests/cache/test_cache_store.py +++ b/guillotina/tests/cache/test_cache_store.py @@ -1,13 +1,12 @@ +import pytest + from guillotina.component import get_utility -from guillotina.contrib.cache import CACHE_PREFIX -from guillotina.contrib.cache import serialize +from guillotina.contrib.cache import CACHE_PREFIX, serialize from guillotina.contrib.cache.strategy import BasicCache from guillotina.interfaces import ICacheUtility from guillotina.tests import mocks from guillotina.utils import resolve_dotted_name -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/cache/test_cache_txn.py b/guillotina/tests/cache/test_cache_txn.py index 7ce9831db..577d28848 100644 --- a/guillotina/tests/cache/test_cache_txn.py +++ b/guillotina/tests/cache/test_cache_txn.py @@ -1,14 +1,13 @@ +import pytest + from guillotina import app_settings from guillotina.annotations import AnnotationData from guillotina.api.container import create_container from guillotina.component import get_utility -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import ICacheUtility +from guillotina.interfaces import IAnnotations, ICacheUtility from guillotina.transactions import transaction from guillotina.utils import get_database -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/cache/test_utils.py b/guillotina/tests/cache/test_utils.py index 1e5ca1409..ab01106ce 100644 --- a/guillotina/tests/cache/test_utils.py +++ b/guillotina/tests/cache/test_utils.py @@ -1,14 +1,15 @@ -from guillotina.contrib.cache.utility import CacheUtility - import pytest +from guillotina.contrib.cache.utility import CacheUtility + @pytest.mark.asyncio async def test_get_size_of_item(): rcache = CacheUtility() - from guillotina.contrib.cache.utility import _default_size import sys + from guillotina.contrib.cache.utility import _default_size + assert rcache.get_size(dict(a=1)) == _default_size assert rcache.get_size(1) == sys.getsizeof(1) assert rcache.get_size(dict(state=b"x" * 10)) == 10 diff --git a/guillotina/tests/conftest.py b/guillotina/tests/conftest.py index 7dd351dd2..1166a425f 100644 --- a/guillotina/tests/conftest.py +++ b/guillotina/tests/conftest.py @@ -25,6 +25,3 @@ "POSTGRES_USER": "postgres", }, ) - - -pytest_plugins = ["guillotina.tests.fixtures", "pytest_docker_fixtures"] diff --git a/guillotina/tests/dbusers/test_api.py b/guillotina/tests/dbusers/test_api.py index 096bec057..3f7d8d07a 100644 --- a/guillotina/tests/dbusers/test_api.py +++ b/guillotina/tests/dbusers/test_api.py @@ -1,10 +1,12 @@ -from . import settings -from guillotina.tests.utils import get_container - import base64 import json + import pytest +from guillotina.tests.utils import get_container + +from . import settings + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/dbusers/test_manage_groups.py b/guillotina/tests/dbusers/test_manage_groups.py index dcc216506..afc38201d 100644 --- a/guillotina/tests/dbusers/test_manage_groups.py +++ b/guillotina/tests/dbusers/test_manage_groups.py @@ -1,9 +1,12 @@ -from . import settings -from guillotina.tests.test_catalog import NOT_POSTGRES - import copy import json + import pytest +import pytest_asyncio + +from guillotina.tests.test_catalog import NOT_POSTGRES + +from . import settings pytestmark = pytest.mark.asyncio @@ -17,12 +20,12 @@ } -@pytest.fixture() +@pytest_asyncio.fixture() async def user_data(): return settings.user_data.copy() -@pytest.fixture() +@pytest_asyncio.fixture() async def second_user_data(): return settings.second_user_data.copy() diff --git a/guillotina/tests/dbusers/test_manage_users.py b/guillotina/tests/dbusers/test_manage_users.py index 24490ce74..92a91adf2 100644 --- a/guillotina/tests/dbusers/test_manage_users.py +++ b/guillotina/tests/dbusers/test_manage_users.py @@ -1,15 +1,18 @@ -from . import settings -from guillotina.tests.test_catalog import NOT_POSTGRES - import copy import json + import pytest +import pytest_asyncio + +from guillotina.tests.test_catalog import NOT_POSTGRES + +from . import settings pytestmark = pytest.mark.asyncio -@pytest.fixture() +@pytest_asyncio.fixture() async def user_data(): return settings.user_data.copy() diff --git a/guillotina/tests/dbusers/test_registration.py b/guillotina/tests/dbusers/test_registration.py index c6807326d..d19923c01 100644 --- a/guillotina/tests/dbusers/test_registration.py +++ b/guillotina/tests/dbusers/test_registration.py @@ -1,9 +1,11 @@ -from . import settings +import json + +import pytest + from guillotina.component import get_utility from guillotina.interfaces import IMailer -import json -import pytest +from . import settings pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/dbusers/test_reset_password.py b/guillotina/tests/dbusers/test_reset_password.py index a2eaf98fe..b8368630a 100644 --- a/guillotina/tests/dbusers/test_reset_password.py +++ b/guillotina/tests/dbusers/test_reset_password.py @@ -1,11 +1,13 @@ -from . import settings -from guillotina.component import get_utility -from guillotina.interfaces import IMailer - import base64 import json + import pytest +from guillotina.component import get_utility +from guillotina.interfaces import IMailer + +from . import settings + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/dbusers/test_security.py b/guillotina/tests/dbusers/test_security.py index cb5b4a688..3eb9184fd 100644 --- a/guillotina/tests/dbusers/test_security.py +++ b/guillotina/tests/dbusers/test_security.py @@ -1,9 +1,11 @@ -from . import settings +import json + +import pytest + from guillotina import configure from guillotina.interfaces import IFolder -import json -import pytest +from . import settings pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/dbusers/test_setup.py b/guillotina/tests/dbusers/test_setup.py index 39d13804c..79f4e8380 100644 --- a/guillotina/tests/dbusers/test_setup.py +++ b/guillotina/tests/dbusers/test_setup.py @@ -1,7 +1,8 @@ -from . import settings +import pytest + from guillotina.tests.utils import get_container -import pytest +from . import settings pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/dyncontent/test_dynapi.py b/guillotina/tests/dyncontent/test_dynapi.py index 2e0662229..07ec80344 100644 --- a/guillotina/tests/dyncontent/test_dynapi.py +++ b/guillotina/tests/dyncontent/test_dynapi.py @@ -1,8 +1,9 @@ -from . import settings - import json + import pytest +from . import settings + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/fixtures.py b/guillotina/tests/fixtures.py index bd49399f2..014afcf1f 100644 --- a/guillotina/tests/fixtures.py +++ b/guillotina/tests/fixtures.py @@ -1,32 +1,30 @@ +import asyncio +import json +import os +from unittest import mock + +import aiohttp +import pytest +import pytest_asyncio from async_asgi_testclient import TestClient -from guillotina import task_vars -from guillotina import testing -from guillotina.component import get_utility -from guillotina.component import globalregistry -from guillotina.const import ROOT_ID -from guillotina.const import TRASHED_ID -from guillotina.db.interfaces import ICockroachStorage -from guillotina.db.interfaces import IPostgresStorage + +from guillotina import task_vars, testing +from guillotina.component import get_utility, globalregistry +from guillotina.const import ROOT_ID, TRASHED_ID +from guillotina.db.interfaces import ICockroachStorage, IPostgresStorage from guillotina.db.storages.cockroach import CockroachStorage from guillotina.factory import make_app -from guillotina.interfaces import IApplication -from guillotina.interfaces import IDatabase +from guillotina.interfaces import IApplication, IDatabase from guillotina.tests import mocks -from guillotina.tests.utils import ContainerRequesterAsyncContextManager -from guillotina.tests.utils import get_mocked_request -from guillotina.tests.utils import login -from guillotina.tests.utils import logout -from guillotina.tests.utils import wrap_request -from guillotina.transactions import get_tm -from guillotina.transactions import transaction +from guillotina.tests.utils import ( + ContainerRequesterAsyncContextManager, + get_mocked_request, + login, + logout, + wrap_request, +) +from guillotina.transactions import get_tm, transaction from guillotina.utils import merge_dicts -from unittest import mock - -import aiohttp -import asyncio -import json -import os -import pytest _dir = os.path.dirname(os.path.realpath(__file__)) @@ -53,7 +51,7 @@ def base_settings_configurator(settings): testing.configure_with(base_settings_configurator) -@pytest.yield_fixture +@pytest.fixture def event_loop(): """Create an instance of the default event loop for each test case.""" # https://github.com/pytest-dev/pytest-asyncio/issues/30#issuecomment-226947196 @@ -389,7 +387,7 @@ def clear_task_vars(): getattr(task_vars, var).set(None) -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def dummy_guillotina(event_loop, request): globalregistry.reset() app = make_app(settings=get_dummy_settings(request.node), loop=event_loop) @@ -417,8 +415,8 @@ async def __aexit__(self, exc_type, exc, tb): @pytest.fixture(scope="function") def dummy_request(dummy_guillotina, monkeypatch): - from guillotina.interfaces import IApplication from guillotina.component import get_utility + from guillotina.interfaces import IApplication root = get_utility(IApplication, name="root") db = root["db"] @@ -444,7 +442,7 @@ async def __aexit__(self, exc_type, exc, tb): await self.txn.abort() -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def dummy_txn_root(dummy_request): return RootAsyncContextManager(dummy_request) @@ -487,7 +485,7 @@ async def _clear_dbs(root): ) -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def app(event_loop, db, request): globalregistry.reset() settings = get_db_settings(request.node) @@ -499,7 +497,7 @@ async def app(event_loop, db, request): from uvicorn import Config, Server - config = Config(app, host=host, port=port, lifespan="on") + config = Config(app, host=host, port=port, lifespan="on", server_header=False) server = Server(config=config) task = asyncio.ensure_future(server.serve(), loop=event_loop) @@ -519,7 +517,7 @@ async def app(event_loop, db, request): clear_task_vars() -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def app_client(event_loop, db, request): globalregistry.reset() app = make_app(settings=get_db_settings(request.node), loop=event_loop) @@ -529,19 +527,19 @@ async def app_client(event_loop, db, request): clear_task_vars() -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def guillotina_main(app_client): app, _ = app_client return app -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def guillotina(app_client): _, client = app_client return GuillotinaDBAsgiRequester(client) -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def guillotina_server(app): host, port = app return GuillotinaDBHttpRequester(host, port) @@ -557,7 +555,7 @@ def container_install_requester(guillotina, install_addons): return ContainerRequesterAsyncContextManager(guillotina, install_addons) -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def container_requester_server(guillotina_server): return ContainerRequesterAsyncContextManager(guillotina_server) @@ -650,12 +648,12 @@ def memcached_container(memcached): annotations["memcached"] = None -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def dbusers_requester(guillotina): return ContainerRequesterAsyncContextManager(guillotina, ["dbusers"]) -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def metrics_registry(): import prometheus_client.registry diff --git a/guillotina/tests/image/test_field.py b/guillotina/tests/image/test_field.py index d664aaee5..653400bec 100644 --- a/guillotina/tests/image/test_field.py +++ b/guillotina/tests/image/test_field.py @@ -1,15 +1,18 @@ -from guillotina.contrib.image.behaviors import IImageAttachment -from guillotina.contrib.image.behaviors import IMultiImageAttachment -from guillotina.contrib.image.behaviors import IMultiImageOrderedAttachment +import json +import os + +import pytest + +from guillotina.contrib.image.behaviors import ( + IImageAttachment, + IMultiImageAttachment, + IMultiImageOrderedAttachment, +) from guillotina.directives import index_field from guillotina.test_package import IExample from guillotina.tests.image import TEST_DATA_LOCATION from guillotina.utils import get_behavior -import json -import os -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/image/test_scale.py b/guillotina/tests/image/test_scale.py index c4245a19a..ab7d81962 100644 --- a/guillotina/tests/image/test_scale.py +++ b/guillotina/tests/image/test_scale.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- -from guillotina.contrib.image.scale import scaleImage -from guillotina.contrib.image.scale import scalePILImage -from guillotina.tests.image import TEST_DATA_LOCATION +import os.path +import warnings from io import BytesIO from unittest import TestCase -import os.path import PIL.Image import PIL.ImageDraw -import warnings + +from guillotina.contrib.image.scale import scaleImage, scalePILImage +from guillotina.tests.image import TEST_DATA_LOCATION with open(os.path.join(TEST_DATA_LOCATION, "logo.png"), "rb") as fio: @@ -202,7 +202,7 @@ def testQuality(self): img3 = scaleImage(CMYK, 84, 103, quality=20)[0] self.assertNotEqual(img1, img2) self.assertNotEqual(img1, img3) - self.failUnless(len(img1) > len(img2) > len(img3)) + self.assertTrue(len(img1) > len(img2) > len(img3)) def testResultBuffer(self): img1 = scaleImage(PNG, 84, 103)[0] diff --git a/guillotina/tests/mailer/test_mailer.py b/guillotina/tests/mailer/test_mailer.py index cea68c026..ffa5d2029 100644 --- a/guillotina/tests/mailer/test_mailer.py +++ b/guillotina/tests/mailer/test_mailer.py @@ -1,8 +1,8 @@ +import pytest + from guillotina.component import get_utility from guillotina.interfaces import IMailer -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/mcp/test_mcp.py b/guillotina/tests/mcp/test_mcp.py new file mode 100644 index 000000000..71ee54020 --- /dev/null +++ b/guillotina/tests/mcp/test_mcp.py @@ -0,0 +1,315 @@ +import asyncio +import json + +import pytest + +from guillotina.contrib.mcp import resources as mcp_resources +from guillotina.utils import resolve_dotted_name + + +pytestmark = pytest.mark.asyncio + +MCP_SETTINGS = { + "applications": ["guillotina", "guillotina.contrib.mcp"], +} + +MCP_SETTINGS_REDIS = { + "applications": [ + "guillotina", + "guillotina.contrib.mcp", + "guillotina.contrib.redis", + ], +} + +PROTOCOL_HEADERS = { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", +} + + +def _skip_if_protocol_unavailable(response, status): + if status != 503: + return + reason = "" + if isinstance(response, dict): + reason = str(response.get("reason") or response.get("message") or "") + known_causes = ( + "MCP SDK missing", + 'Install "guillotina[mcp]"', + "MCP registry utility is not available", + ) + if any(cause in reason for cause in known_causes): + detail = f": {reason}" if reason else "" + pytest.skip(f"MCP protocol unavailable in this environment{detail}") + + +async def _protocol(requester, method, params=None, id=1): + payload = {"jsonrpc": "2.0", "id": id, "method": method, "params": params or {}} + response, status = await requester( + "POST", + "/db/guillotina/@mcp/protocol", + data=json.dumps(payload), + headers=PROTOCOL_HEADERS, + ) + _skip_if_protocol_unavailable(response, status) + return response, status + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_initialize(container_requester): + async with container_requester as requester: + response, status = await _protocol( + requester, + "initialize", + params={ + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": {"name": "test", "version": "1.0"}, + }, + ) + assert status == 200 + assert response["jsonrpc"] == "2.0" + assert response["result"]["protocolVersion"] == "2024-11-05" + assert "tools" in response["result"]["capabilities"] + assert "resources" in response["result"]["capabilities"] + assert response["result"]["serverInfo"]["name"] == "guillotina-mcp" + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_tools_list(container_requester): + async with container_requester as requester: + response, status = await _protocol(requester, "tools/list") + assert status == 200 + names = {t["name"] for t in response["result"]["tools"]} + assert "search" in names + assert "list_children" in names + assert "resolve_path" in names + assert "serialize_resource" in names + assert "notify_modified" in names + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_tools_call_resolve_path(container_requester): + async with container_requester as requester: + response, status = await _protocol( + requester, + "tools/call", + params={"name": "resolve_path", "arguments": {"path": "/"}}, + ) + assert status == 200 + content = json.loads(response["result"]["content"][0]["text"]) + assert content["path"] == "/" + assert content["resource"]["@type"] == "Container" + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_tools_call_list_children(container_requester): + async with container_requester as requester: + _, status = await requester( + "POST", + "/db/guillotina", + data=json.dumps({"@type": "Item", "id": "item-proto"}), + ) + assert status == 201 + + response, status = await _protocol( + requester, + "tools/call", + params={"name": "list_children", "arguments": {"path": "/", "limit": 20}}, + ) + assert status == 200 + content = json.loads(response["result"]["content"][0]["text"]) + ids = {item["id"] for item in content["items"]} + assert "item-proto" in ids + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_tools_call_unknown_tool_returns_error(container_requester): + async with container_requester as requester: + response, status = await _protocol( + requester, + "tools/call", + params={"name": "does-not-exist", "arguments": {}}, + ) + assert status == 200 + # Unknown tool: SDK returns a tool result with isError=True (not a JSON-RPC error) + assert response["result"]["isError"] is True + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_resources_list(container_requester): + async with container_requester as requester: + response, status = await _protocol(requester, "resources/list") + assert status == 200 + names = {r["name"] for r in response["result"]["resources"]} + for expected in ("info", "health", "config", "users", "catalog", "summary"): + assert expected in names, f"Missing resource: {expected}" + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_resources_read_info(container_requester): + async with container_requester as requester: + response, status = await _protocol( + requester, + "resources/read", + params={"uri": "guillotina://resources/info"}, + ) + assert status == 200 + content = json.loads(response["result"]["contents"][0]["text"]) + assert "version" in content + assert "container_id" in content + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_resources_read_summary_with_path(container_requester): + """resources/read for summary accepts ?path= URI query params (server.py uri matching fix).""" + async with container_requester as requester: + # Without path — should return container summary + response, status = await _protocol( + requester, + "resources/read", + params={"uri": "guillotina://resources/summary"}, + ) + assert status == 200 + content = json.loads(response["result"]["contents"][0]["text"]) + assert content["path"] == "/" + assert content["@type"] == "Container" + + # With ?path=/ — same result, proves URI query params are forwarded + response, status = await _protocol( + requester, + "resources/read", + params={"uri": "guillotina://resources/summary?path=/"}, + ) + assert status == 200 + content = json.loads(response["result"]["contents"][0]["text"]) + assert content["path"] == "/" + assert content["@type"] == "Container" + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_requires_accept_header(container_requester): + async with container_requester as requester: + response, status = await requester( + "POST", + "/db/guillotina/@mcp/protocol", + data=json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}), + headers={"Content-Type": "application/json"}, + ) + _skip_if_protocol_unavailable(response, status) + assert status in (200, 406) + if status == 200: + assert response.get("jsonrpc") == "2.0" + assert "result" in response + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_invalid_json_rpc_returns_400(container_requester): + async with container_requester as requester: + response, status = await requester( + "POST", + "/db/guillotina/@mcp/protocol", + data=json.dumps({"not": "jsonrpc"}), + headers=PROTOCOL_HEADERS, + ) + _skip_if_protocol_unavailable(response, status) + assert status == 400 + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_unknown_action_returns_404(container_requester): + async with container_requester as requester: + response, status = await requester( + "POST", + "/db/guillotina/@mcp/not-a-real-action", + data=json.dumps({}), + headers=PROTOCOL_HEADERS, + ) + assert status == 404 + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_protocol_resource_registry_matches_defaults(container_requester): + async with container_requester as requester: + response, status = await _protocol(requester, "resources/list") + assert status == 200 + registered_names = {r["name"] for r in response["result"]["resources"]} + default_names = {res[0] for res in mcp_resources.default_resources()} + assert registered_names == default_names + + +@pytest.mark.app_settings(MCP_SETTINGS) +async def test_invoke_resolve_path_tool(container_requester): + """Kept as an alias — now uses the JSON-RPC protocol path.""" + async with container_requester as requester: + _, status = await requester( + "POST", + "/db/guillotina", + data=json.dumps({"@type": "Item", "id": "foo_item"}), + ) + assert status == 201 + + response, status = await _protocol( + requester, + "tools/call", + params={"name": "resolve_path", "arguments": {"path": "/"}}, + ) + assert status == 200 + content = json.loads(response["result"]["content"][0]["text"]) + assert content["resource"]["@type"] == "Container" + assert content["path"] == "/" + + response, status = await _protocol( + requester, + "tools/call", + params={"name": "resolve_path", "arguments": {"path": "/foo_item"}}, + ) + assert status == 200 + content = json.loads(response["result"]["content"][0]["text"]) + assert content["resource"]["@type"] == "Item" + assert content["path"] == "/foo_item" + + +@pytest.mark.app_settings(MCP_SETTINGS_REDIS) +async def test_cache_redis(redis_container, container_requester): + async with container_requester as requester: + _, status = await requester( + "POST", + "/db/guillotina", + data=json.dumps({"@type": "Item", "id": "foo_item"}), + ) + assert status == 201 + + response, status = await _protocol( + requester, + "tools/call", + params={"name": "resolve_path", "arguments": {"path": "/"}}, + ) + assert status == 200 + content = json.loads(response["result"]["content"][0]["text"]) + assert content["resource"]["@type"] == "Container" + assert content["path"] == "/" + + response, status = await _protocol( + requester, + "tools/call", + params={"name": "resolve_path", "arguments": {"path": "/foo_item"}}, + ) + assert status == 200 + content = json.loads(response["result"]["content"][0]["text"]) + assert content["resource"]["@type"] == "Item" + assert content["path"] == "/foo_item" + driver = await resolve_dotted_name("guillotina.contrib.redis").get_driver() + assert driver.initialized + keys = await driver.keys_startswith("mcp_tool_cache:v1") + assert len(keys) == 2 + # Let's modify an object to invalidate the cache + _, status = await requester( + "PATCH", + "/db/guillotina/foo_item", + data=json.dumps({"title": "Foo title"}), + ) + assert status == 204 + await asyncio.sleep(0.5) + keys = await driver.keys_startswith("mcp_tool_cache:v1") + assert len(keys) == 0 diff --git a/guillotina/tests/memcached/test_cache.py b/guillotina/tests/memcached/test_cache.py index 543a716e9..12b08e6a1 100644 --- a/guillotina/tests/memcached/test_cache.py +++ b/guillotina/tests/memcached/test_cache.py @@ -2,22 +2,33 @@ from guillotina.contrib.memcached.driver import MemcachedDriver except ImportError: MemcachedDriver = None # type: ignore -from guillotina.component import get_utility -from guillotina.interfaces import ICacheUtility + +try: + import pymemcache +except ModuleNotFoundError: + pymemcache = None import pytest +from guillotina.component import get_utility +from guillotina.interfaces import ICacheUtility + pytestmark = pytest.mark.asyncio MEMCACHED_SETTINGS = { - "applications": ["guillotina", "guillotina.contrib.memcached", "guillotina.contrib.cache"], + "applications": [ + "guillotina", + "guillotina.contrib.memcached", + "guillotina.contrib.cache", + ], "cache": {"updates_channel": None, "driver": "guillotina.contrib.memcached"}, } @pytest.mark.skipif(MemcachedDriver is None, reason="emcache not installed") +@pytest.mark.skipif(pymemcache is None, reason="pymemcache not installed") @pytest.mark.app_settings(MEMCACHED_SETTINGS) async def test_cache_uses_memcached_driver_when_configured(memcached_container, guillotina_main): cache = get_utility(ICacheUtility) diff --git a/guillotina/tests/memcached/test_memcached_driver.py b/guillotina/tests/memcached/test_memcached_driver.py index d7d5a1250..6057f6b2b 100644 --- a/guillotina/tests/memcached/test_memcached_driver.py +++ b/guillotina/tests/memcached/test_memcached_driver.py @@ -1,16 +1,22 @@ try: import emcache - from guillotina.contrib.memcached.driver import MemcachedDriver - from guillotina.contrib.memcached.driver import safe_key - from guillotina.contrib.memcached.driver import update_connection_pool_metrics + + from guillotina.contrib.memcached.driver import MemcachedDriver, safe_key, update_connection_pool_metrics except ModuleNotFoundError: emcache = None -from guillotina.utils import resolve_dotted_name -from unittest import mock +try: + import pymemcache +except ModuleNotFoundError: + pymemcache = None import asyncio +from unittest import mock + import pytest +import pytest_asyncio + +from guillotina.utils import resolve_dotted_name pytestmark = pytest.mark.asyncio @@ -37,6 +43,7 @@ def mocked_create_client(): @pytest.mark.skipif(emcache is None, reason="emcache not installed") +@pytest.mark.skipif(pymemcache is None, reason="pymemcache not installed") async def test_create_client_returns_emcache_client(memcached_container, guillotina_main): driver = MemcachedDriver() assert driver.client is None @@ -83,6 +90,7 @@ async def test_create_client_sets_configured_params(mocked_create_client, param, @pytest.mark.skipif(emcache is None, reason="emcache not installed") +@pytest.mark.skipif(pymemcache is None, reason="pymemcache not installed") @pytest.mark.app_settings(MEMCACHED_SETTINGS) async def test_memcached_ops(memcached_container, guillotina_main, dont_probe_metrics): driver = await resolve_dotted_name("guillotina.contrib.memcached").get_driver() @@ -124,6 +132,7 @@ async def test_memcached_ops(memcached_container, guillotina_main, dont_probe_me @pytest.mark.skipif(emcache is None, reason="emcache not installed") +@pytest.mark.skipif(pymemcache is None, reason="pymemcache not installed") @pytest.mark.app_settings(MEMCACHED_SETTINGS) @pytest.mark.parametrize("unsafe_key", unsafe_keys) async def test_memcached_ops_are_safe_key( @@ -157,7 +166,10 @@ async def test_delete_all(): watch_mocked.assert_called() all_keys.observe.assert_called_with(2) driver._client.delete.assert_has_calls( - [mock.call(safe_key("foo"), noreply=True), mock.call(safe_key("bar"), noreply=True)] + [ + mock.call(safe_key("foo"), noreply=True), + mock.call(safe_key("bar"), noreply=True), + ] ) @@ -194,7 +206,7 @@ def upper(self): with mock.patch("guillotina.contrib.memcached.driver.MEMCACHED_CREATE_CONNECTION_UPPER") as _upper: yield _upper - @pytest.fixture + @pytest_asyncio.fixture() async def metrics(self): metrics = mock.Mock() metrics.cur_connections = 1 diff --git a/guillotina/tests/memcached/test_metrics.py b/guillotina/tests/memcached/test_metrics.py index d5cbe9150..abf0dbb4d 100644 --- a/guillotina/tests/memcached/test_metrics.py +++ b/guillotina/tests/memcached/test_metrics.py @@ -3,29 +3,44 @@ except ModuleNotFoundError: MemcachedDriver = None # type: ignore -from asyncmock import AsyncMock +from unittest import mock import pytest +from asyncmock import AsyncMock pytestmark = pytest.mark.asyncio +MEMCACHED_SETTINGS = {"applications": ["guillotina", "memcached", "guillotina.contrib.memcached"]} @pytest.mark.skipif(MemcachedDriver is None, reason="emcache not installed") +@pytest.mark.app_settings(MEMCACHED_SETTINGS) class TestMemcachedMetrics: async def test_connect_metric(self, metrics_registry, event_loop): driver = MemcachedDriver() - driver._client = AsyncMock() - await driver.initialize(event_loop) + client = AsyncMock() + with mock.patch.dict( + "guillotina.contrib.memcached.driver.app_settings", + {"memcached": {"hosts": ["localhost:11211"]}}, + clear=False, + ): + with mock.patch( + "guillotina.contrib.memcached.driver.emcache.create_client", + AsyncMock(return_value=client), + ): + with mock.patch("guillotina.contrib.memcached.driver._SEND_METRICS", False): + await driver.initialize(event_loop) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_total", {"type": "connect", "error": "none"} + "guillotina_cache_memcached_ops_total", + {"type": "connect", "error": "none"}, ) == 1.0 ) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_processing_time_seconds_sum", {"type": "connect"} + "guillotina_cache_memcached_ops_processing_time_seconds_sum", + {"type": "connect"}, ) > 0 ) @@ -43,7 +58,8 @@ async def test_set_memcached_metric(self, metrics_registry): ) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_processing_time_seconds_sum", {"type": "set"} + "guillotina_cache_memcached_ops_processing_time_seconds_sum", + {"type": "set"}, ) > 0 ) @@ -60,7 +76,8 @@ async def test_get_memcached_metric(self, metrics_registry): ) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_processing_time_seconds_sum", {"type": "get"} + "guillotina_cache_memcached_ops_processing_time_seconds_sum", + {"type": "get"}, ) > 0 ) @@ -72,13 +89,15 @@ async def test_get_miss_memcached_metric(self, metrics_registry): await driver.get("foo") assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_total", {"type": "get_miss", "error": "none"} + "guillotina_cache_memcached_ops_total", + {"type": "get_miss", "error": "none"}, ) == 1.0 ) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_processing_time_seconds_sum", {"type": "get_miss"} + "guillotina_cache_memcached_ops_processing_time_seconds_sum", + {"type": "get_miss"}, ) > 0 ) @@ -89,13 +108,15 @@ async def test_delete_memcached_metric(self, metrics_registry): await driver.delete("foo") assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_total", {"type": "delete", "error": "none"} + "guillotina_cache_memcached_ops_total", + {"type": "delete", "error": "none"}, ) == 1.0 ) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_processing_time_seconds_sum", {"type": "delete"} + "guillotina_cache_memcached_ops_processing_time_seconds_sum", + {"type": "delete"}, ) > 0 ) @@ -106,25 +127,29 @@ async def test_delete_many_memcached_metric(self, metrics_registry): await driver.delete_all(["foo", "bar"]) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_total", {"type": "delete_many", "error": "none"} + "guillotina_cache_memcached_ops_total", + {"type": "delete_many", "error": "none"}, ) == 1.0 ) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_processing_time_seconds_sum", {"type": "delete_many"} + "guillotina_cache_memcached_ops_processing_time_seconds_sum", + {"type": "delete_many"}, ) > 0 ) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_total", {"type": "delete", "error": "none"} + "guillotina_cache_memcached_ops_total", + {"type": "delete", "error": "none"}, ) == 2.0 ) assert ( metrics_registry.get_sample_value( - "guillotina_cache_memcached_ops_processing_time_seconds_sum", {"type": "delete"} + "guillotina_cache_memcached_ops_processing_time_seconds_sum", + {"type": "delete"}, ) > 0 ) diff --git a/guillotina/tests/mocks.py b/guillotina/tests/mocks.py index 3232ccdf7..e5dac1225 100644 --- a/guillotina/tests/mocks.py +++ b/guillotina/tests/mocks.py @@ -1,14 +1,12 @@ +import asyncio +import uuid from collections import OrderedDict -from guillotina import app_settings -from guillotina import task_vars -from guillotina.db.cache.dummy import DummyCache -from guillotina.db.interfaces import IStorage -from guillotina.db.interfaces import ITransaction -from guillotina.db.interfaces import IWriter + from zope.interface import implementer -import asyncio -import uuid +from guillotina import app_settings, task_vars +from guillotina.db.cache.dummy import DummyCache +from guillotina.db.interfaces import IStorage, ITransaction, IWriter class MockDBTransaction: diff --git a/guillotina/tests/pubsub/test_pubsub.py b/guillotina/tests/pubsub/test_pubsub.py index 3a47358a1..c4b0006ad 100644 --- a/guillotina/tests/pubsub/test_pubsub.py +++ b/guillotina/tests/pubsub/test_pubsub.py @@ -1,10 +1,11 @@ -from guillotina.component import get_utility -from guillotina.interfaces import IPubSubUtility - import asyncio + import pytest import pytest_docker_fixtures +from guillotina.component import get_utility +from guillotina.interfaces import IPubSubUtility + @pytest.mark.app_settings( {"applications": ["guillotina", "guillotina.contrib.redis", "guillotina.contrib.pubsub"]} diff --git a/guillotina/tests/redis/test_driver.py b/guillotina/tests/redis/test_driver.py index 4b61ee55f..d40e7ff48 100644 --- a/guillotina/tests/redis/test_driver.py +++ b/guillotina/tests/redis/test_driver.py @@ -1,8 +1,9 @@ -from guillotina.utils import resolve_dotted_name - import asyncio + import pytest +from guillotina.utils import resolve_dotted_name + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/redis_session/test_session.py b/guillotina/tests/redis_session/test_session.py index 2b93cc11a..1616be0ec 100644 --- a/guillotina/tests/redis_session/test_session.py +++ b/guillotina/tests/redis_session/test_session.py @@ -1,11 +1,13 @@ -from . import settings -from guillotina.auth.users import ROOT_USER_ID -from guillotina.testing import TESTING_SETTINGS - import json + import jwt import pytest +from guillotina.auth.users import ROOT_USER_ID +from guillotina.testing import TESTING_SETTINGS + +from . import settings + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_adapters.py b/guillotina/tests/test_adapters.py index 3a11f6e6e..f5716a0c6 100644 --- a/guillotina/tests/test_adapters.py +++ b/guillotina/tests/test_adapters.py @@ -1,32 +1,30 @@ +import pytest + from guillotina import schema -from guillotina.component import get_adapter -from guillotina.component import get_multi_adapter -from guillotina.component import get_utility -from guillotina.content import Container -from guillotina.content import Item -from guillotina.factory.security import ApplicationSpecialPermissions -from guillotina.factory.security import DatabaseSpecialPermissions -from guillotina.interfaces import IApplication -from guillotina.interfaces import IFactorySerializeToJson -from guillotina.interfaces import IItem -from guillotina.interfaces import IPrincipalPermissionManager -from guillotina.interfaces import IResource -from guillotina.interfaces import IResourceDeserializeFromJson -from guillotina.interfaces import IResourceFactory -from guillotina.interfaces import IResourceSerializeToJson -from guillotina.interfaces import IResourceSerializeToJsonSummary -from guillotina.interfaces import ISchemaFieldSerializeToJson -from guillotina.interfaces import ISchemaSerializeToJson -from guillotina.json import deserialize_content -from guillotina.json import serialize_schema -from guillotina.json import serialize_schema_field -from guillotina.json.serialize_content import DefaultJSONSummarySerializer -from guillotina.json.serialize_content import SerializeFolderToJson -from guillotina.json.serialize_content import SerializeToJson +from guillotina.component import get_adapter, get_multi_adapter, get_utility +from guillotina.content import Container, Item +from guillotina.factory.security import ApplicationSpecialPermissions, DatabaseSpecialPermissions +from guillotina.interfaces import ( + IApplication, + IFactorySerializeToJson, + IItem, + IPrincipalPermissionManager, + IResource, + IResourceDeserializeFromJson, + IResourceFactory, + IResourceSerializeToJson, + IResourceSerializeToJsonSummary, + ISchemaFieldSerializeToJson, + ISchemaSerializeToJson, +) +from guillotina.json import deserialize_content, serialize_schema, serialize_schema_field +from guillotina.json.serialize_content import ( + DefaultJSONSummarySerializer, + SerializeFolderToJson, + SerializeToJson, +) from guillotina.json.serialize_value import json_compatible -import pytest - @pytest.mark.asyncio async def test_DatabaseSpecialPermissions_IDatabase(dummy_txn_root): # noqa: N802 @@ -87,7 +85,7 @@ def test_vocabulary(dummy_request): vocab = SimpleVocabulary.fromItems((("Foo", "id_foo"), ("Bar", "id_bar"))) res = json_compatible(vocab) - assert type(res) == list + assert isinstance(res, list) def test_SerializeFactoryToJson(dummy_request): # noqa: N802 diff --git a/guillotina/tests/test_annotations.py b/guillotina/tests/test_annotations.py index d29f46906..316ab7cb6 100644 --- a/guillotina/tests/test_annotations.py +++ b/guillotina/tests/test_annotations.py @@ -1,3 +1,9 @@ +import os +import time +from uuid import uuid4 + +import pytest + from guillotina.annotations import AnnotationData from guillotina.content import create_content_in_container from guillotina.fields.annotation import BucketDictValue @@ -5,11 +11,6 @@ from guillotina.tests.utils import login from guillotina.transactions import transaction from guillotina.utils import get_database -from uuid import uuid4 - -import os -import pytest -import time pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_api.py b/guillotina/tests/test_api.py index 2eb86d8b5..bb126f942 100644 --- a/guillotina/tests/test_api.py +++ b/guillotina/tests/test_api.py @@ -1,7 +1,11 @@ -from datetime import datetime -from datetime import time -from guillotina import configure -from guillotina import schema +import base64 +import json +from datetime import datetime, time + +import pytest +from zope.interface import Interface + +from guillotina import configure, schema from guillotina.addons import Addon from guillotina.api.service import _safe_int_or_float_cast from guillotina.behaviors.attachment import IAttachment @@ -9,19 +13,12 @@ from guillotina.configure import contenttype from guillotina.content import Item from guillotina.fields.patch import PatchField -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import IFile -from guillotina.interfaces import IResource +from guillotina.interfaces import IAnnotations, IFile, IResource from guillotina.test_package import ITestBehavior from guillotina.tests import utils from guillotina.tests.dbusers.settings import DEFAULT_SETTINGS as DBUSERS_DEFAULT_SETTINGS from guillotina.transactions import transaction from guillotina.utils import get_behavior -from zope.interface import Interface - -import base64 -import json -import pytest pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_attachment.py b/guillotina/tests/test_attachment.py index 8c6128264..d708d229f 100644 --- a/guillotina/tests/test_attachment.py +++ b/guillotina/tests/test_attachment.py @@ -1,18 +1,18 @@ -from guillotina import task_vars -from guillotina.behaviors.attachment import IAttachment -from guillotina.behaviors.attachment import IMultiAttachment -from guillotina.component import get_multi_adapter -from guillotina.interfaces import IFileManager -from guillotina.tests import utils -from guillotina.transactions import transaction - import asyncio import base64 import hashlib import json -import pytest import random +import pytest + +from guillotina import task_vars +from guillotina.behaviors.attachment import IAttachment, IMultiAttachment +from guillotina.component import get_multi_adapter +from guillotina.interfaces import IFileManager +from guillotina.tests import utils +from guillotina.transactions import transaction + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_auth.py b/guillotina/tests/test_auth.py index 3893918b8..5d1f51bda 100644 --- a/guillotina/tests/test_auth.py +++ b/guillotina/tests/test_auth.py @@ -1,11 +1,11 @@ -from datetime import datetime -from datetime import timedelta -from guillotina._settings import app_settings -from guillotina.auth import validators +from datetime import datetime, timedelta import jwt import pytest +from guillotina._settings import app_settings +from guillotina.auth import validators + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_blob.py b/guillotina/tests/test_blob.py index 8011c6f70..b81294226 100644 --- a/guillotina/tests/test_blob.py +++ b/guillotina/tests/test_blob.py @@ -1,17 +1,15 @@ +import pytest + from guillotina.behaviors.attachment import IAttachment from guillotina.blob import Blob from guillotina.component import get_multi_adapter from guillotina.content import create_content_in_container from guillotina.exceptions import BlobChunkNotFound -from guillotina.files.exceptions import RangeNotFound -from guillotina.files.exceptions import RangeNotSupported +from guillotina.files.exceptions import RangeNotFound, RangeNotSupported from guillotina.interfaces import IFileManager from guillotina.tests.utils import login from guillotina.transactions import transaction -from guillotina.utils import get_behavior -from guillotina.utils import get_database - -import pytest +from guillotina.utils import get_behavior, get_database pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_catalog.py b/guillotina/tests/test_catalog.py index f027a220e..3cc144b5c 100644 --- a/guillotina/tests/test_catalog.py +++ b/guillotina/tests/test_catalog.py @@ -1,29 +1,21 @@ +import json +import os from datetime import datetime -from guillotina import configure -from guillotina import task_vars + +import pytest + +from guillotina import configure, task_vars from guillotina.catalog import index -from guillotina.catalog.utils import get_index_fields -from guillotina.catalog.utils import get_metadata_fields -from guillotina.catalog.utils import parse_query -from guillotina.component import get_adapter -from guillotina.component import query_utility -from guillotina.content import Container -from guillotina.content import create_content -from guillotina.content import Resource +from guillotina.catalog.utils import get_index_fields, get_metadata_fields, parse_query +from guillotina.component import get_adapter, query_utility +from guillotina.content import Container, Resource, create_content from guillotina.directives import index_field from guillotina.event import notify from guillotina.events import ObjectModifiedEvent -from guillotina.interfaces import ICatalogDataAdapter -from guillotina.interfaces import ICatalogUtility -from guillotina.interfaces import IResource -from guillotina.interfaces import ISecurityInfo +from guillotina.interfaces import ICatalogDataAdapter, ICatalogUtility, IResource, ISecurityInfo from guillotina.tests import mocks from guillotina.tests import utils as test_utils -import json -import os -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_cockroach.py b/guillotina/tests/test_cockroach.py index 975596830..5fa362dc3 100644 --- a/guillotina/tests/test_cockroach.py +++ b/guillotina/tests/test_cockroach.py @@ -1,13 +1,14 @@ +import asyncio +import os + +import pytest + from guillotina.component import get_adapter from guillotina.content import Folder from guillotina.db.interfaces import IVacuumProvider from guillotina.db.transaction_manager import TransactionManager from guillotina.tests.utils import create_content -import asyncio -import os -import pytest - pytestmark = [ pytest.mark.asyncio, diff --git a/guillotina/tests/test_commands.py b/guillotina/tests/test_commands.py index ea3f8a81a..f0ac01e98 100644 --- a/guillotina/tests/test_commands.py +++ b/guillotina/tests/test_commands.py @@ -1,19 +1,19 @@ +import io +import json +import os from contextlib import redirect_stdout +from tempfile import mkstemp + +import pytest + from guillotina import testing -from guillotina.commands import Command -from guillotina.commands import get_settings +from guillotina.commands import Command, get_settings from guillotina.commands.crypto import CryptoCommand from guillotina.commands.migrate import MigrateCommand from guillotina.commands.run import RunCommand from guillotina.commands.vacuum import VacuumCommand from guillotina.exceptions import TransactionNotFound from guillotina.utils import get_current_transaction -from tempfile import mkstemp - -import io -import json -import os -import pytest DATABASE = os.environ.get("DATABASE", "DUMMY") diff --git a/guillotina/tests/test_configure.py b/guillotina/tests/test_configure.py index d8ebdec24..ccf77c18f 100644 --- a/guillotina/tests/test_configure.py +++ b/guillotina/tests/test_configure.py @@ -1,22 +1,18 @@ +import pytest +from zope.interface import Interface + from guillotina import configure from guillotina.addons import Addon from guillotina.api.service import Service -from guillotina.component import get_utility -from guillotina.component import query_multi_adapter +from guillotina.component import get_utility, query_multi_adapter from guillotina.configure.config import ConfigurationMachine -from guillotina.content import get_all_possible_schemas_for_type -from guillotina.content import Item +from guillotina.content import Item, get_all_possible_schemas_for_type from guillotina.event import notify from guillotina.events import ObjectAddedEvent -from guillotina.factory.app import ApplicationConfigurator -from guillotina.factory.app import configure_application +from guillotina.factory.app import ApplicationConfigurator, configure_application from guillotina.factory.content import ApplicationRoot -from guillotina.interfaces import IApplication -from guillotina.interfaces import IContainer +from guillotina.interfaces import IApplication, IContainer from guillotina.tests.utils import create_content -from zope.interface import Interface - -import pytest pytestmark = pytest.mark.asyncio @@ -103,8 +99,8 @@ class MyType(Item): async def test_register_behavior(container_requester): cur_count = len(configure.get_configurations("guillotina.tests", "behavior")) - from guillotina.interfaces import IResource from guillotina import schema + from guillotina.interfaces import IResource class IMyBehavior(Interface): foobar = schema.Text() diff --git a/guillotina/tests/test_configure_component.py b/guillotina/tests/test_configure_component.py index 741166fd6..0235ef7ee 100644 --- a/guillotina/tests/test_configure_component.py +++ b/guillotina/tests/test_configure_component.py @@ -24,9 +24,10 @@ def _callFUT(self, *args, **kw): # noqa: N802 def test_uses_configured_site_manager(self): from zope.interface.registry import Components + from guillotina.component import get_component_registry - from guillotina.component.testfiles.components import comp, IApp from guillotina.component._compat import _BLANK + from guillotina.component.testfiles.components import IApp, comp registry = Components() @@ -87,6 +88,7 @@ def _callFUT(self, *args, **kw): # noqa: N802 def test_empty_factory(self): from zope.interface import Interface + from guillotina.configure.component import ComponentConfigurationError class IFoo(Interface): @@ -97,6 +99,7 @@ class IFoo(Interface): def test_multiple_factory_multiple_for_(self): from zope.interface import Interface + from guillotina.configure.component import ComponentConfigurationError class IFoo(Interface): @@ -134,9 +137,10 @@ class IFoo(Interface): class IBar(Interface): pass - from guillotina.component import adapter from zope.interface import implementer, named + from guillotina.component import adapter + @adapter(IFoo) @implementer(IBar) @named("bar") @@ -152,6 +156,7 @@ def __init__(self, context): def test_no_for__factory_adapts_no_provides_factory_not_implement(self): from zope.interface import Interface + from guillotina.component._declaration import adapter @adapter(Interface) @@ -164,6 +169,7 @@ def __init__(self, context): def test_multiple_factory_single_for__w_name(self): from zope.interface import Interface + from guillotina.component.interface import provide_interface from guillotina.configure.component import handler @@ -203,8 +209,8 @@ class Bar(object): self.assertEqual(action["args"], ("", Interface)) def test_no_for__no_provides_factory_adapts_factory_implement(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component._declaration import adapter from guillotina.configure.component import handler @@ -287,6 +293,7 @@ class Foo(object): def test_no_factory_w_handler_no_provides(self): from zope.interface import Interface + from guillotina.component.interface import provide_interface from guillotina.configure.component import handler @@ -314,6 +321,7 @@ def _handler(*args): def test_w_factory_w_provides(self): from zope.interface import Interface + from guillotina.component.interface import provide_interface from guillotina.configure.component import handler @@ -381,6 +389,7 @@ def test_w_component_wo_provides_component_no_provides(self): def test_w_factory_w_provides(self): from zope.interface import Interface + from guillotina.component.interface import provide_interface from guillotina.configure.component import handler @@ -411,8 +420,8 @@ class Foo(object): self.assertEqual(action["args"], ("", IFoo)) def test_w_factory_wo_provides_factory_implement(self): - from zope.interface import Interface - from zope.interface import implementer + from zope.interface import Interface, implementer + from guillotina.component.interface import provide_interface from guillotina.configure.component import handler @@ -445,6 +454,7 @@ class Foo(object): def test_w_component_w_provides_w_name(self): from zope.interface import Interface + from guillotina.component.interface import provide_interface from guillotina.configure.component import handler @@ -491,8 +501,8 @@ class Foo(object): self.assertEqual(action["args"][3], "foo") def test_w_component_wo_provides_component_provides(self): - from zope.interface import Interface - from zope.interface import directlyProvides + from zope.interface import Interface, directlyProvides + from guillotina.component.interface import provide_interface from guillotina.configure.component import handler @@ -532,6 +542,7 @@ def _callFUT(self, *args, **kw): # noqa: N802 def test_wo_name_wo_type(self): from zope.interface import Interface + from guillotina.component.interface import provide_interface class IFoo(Interface): @@ -548,6 +559,7 @@ class IFoo(Interface): def test_w_name_w_type(self): from zope.interface import Interface + from guillotina.component.interface import provide_interface class IFoo(Interface): @@ -574,6 +586,7 @@ def _callFUT(self, *args, **kw): # noqa: N802 def test_w_factory_as_empty(self): from zope.interface import Interface + from guillotina.configure.component import ComponentConfigurationError class IViewType(Interface): @@ -592,6 +605,7 @@ class IViewType(Interface): def test_w_multiple_factory_multiple_for_(self): from zope.interface import Interface + from guillotina.configure.component import ComponentConfigurationError class IViewType(Interface): @@ -616,6 +630,7 @@ class Bar(object): def test_w_for__as_empty(self): from zope.interface import Interface + from guillotina.configure.component import ComponentConfigurationError class IViewType(Interface): @@ -632,8 +647,9 @@ def __init__(self, context): def test_w_single_factory_single_for__wo_permission_w_name(self): from zope.interface import Interface - from guillotina.configure.component import handler + from guillotina.component.interface import provide_interface + from guillotina.configure.component import handler class IViewType(Interface): pass @@ -675,6 +691,7 @@ def __init__(self, context): def test_w_multiple_factory_single_for__wo_permission(self): from zope.interface import Interface + from guillotina.configure.component import handler class IViewType(Interface): @@ -737,10 +754,11 @@ def test_suite(): def test_configuration_machine_allows_overriding(): - from guillotina.configure.config import ConfigurationMachine - from guillotina.configure import component + from zope.interface import Interface, implementer, named + from guillotina.component import adapter, get_adapter - from zope.interface import implementer, Interface, named + from guillotina.configure import component + from guillotina.configure.config import ConfigurationMachine class IFoo(Interface): pass diff --git a/guillotina/tests/test_content.py b/guillotina/tests/test_content.py index 2324d71e2..52fdb6386 100644 --- a/guillotina/tests/test_content.py +++ b/guillotina/tests/test_content.py @@ -1,31 +1,29 @@ +import json +import pickle + +import pytest + from guillotina import configure from guillotina.behaviors.attachment import IAttachment from guillotina.behaviors.dublincore import IDublinCore from guillotina.component import get_utility from guillotina.component.interfaces import ComponentLookupError -from guillotina.content import create_content -from guillotina.content import create_content_in_container -from guillotina.content import Folder -from guillotina.content import get_all_behaviors -from guillotina.content import Item -from guillotina.content import load_cached_schema -from guillotina.exceptions import NoPermissionToAdd -from guillotina.exceptions import NotAllowedContentType -from guillotina.interfaces import IApplication -from guillotina.interfaces import IItem +from guillotina.content import ( + Folder, + Item, + create_content, + create_content_in_container, + get_all_behaviors, + load_cached_schema, +) +from guillotina.exceptions import NoPermissionToAdd, NotAllowedContentType +from guillotina.interfaces import IApplication, IItem from guillotina.interfaces.types import IConstrainTypes -from guillotina.schema import Dict -from guillotina.schema import TextLine +from guillotina.schema import Dict, TextLine from guillotina.test_package import ITestBehavior from guillotina.tests import utils from guillotina.transactions import transaction -from guillotina.utils import get_behavior -from guillotina.utils import get_database -from guillotina.utils import get_object_by_oid - -import json -import pickle -import pytest +from guillotina.utils import get_behavior, get_database, get_object_by_oid pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_contentapi.py b/guillotina/tests/test_contentapi.py index d93847e3a..291e09ec5 100644 --- a/guillotina/tests/test_contentapi.py +++ b/guillotina/tests/test_contentapi.py @@ -1,8 +1,8 @@ +import pytest + from guillotina.contentapi import ContentAPI from guillotina.utils import get_content_path -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_cors.py b/guillotina/tests/test_cors.py index 8a282cec6..c36d49f41 100644 --- a/guillotina/tests/test_cors.py +++ b/guillotina/tests/test_cors.py @@ -1,11 +1,12 @@ from copy import deepcopy + +import pytest + from guillotina import cors from guillotina._settings import app_settings from guillotina.response import HTTPUnauthorized from guillotina.tests.utils import get_mocked_request -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_dbobjects.py b/guillotina/tests/test_dbobjects.py index 8781f19e4..00d5b68a4 100644 --- a/guillotina/tests/test_dbobjects.py +++ b/guillotina/tests/test_dbobjects.py @@ -1,12 +1,11 @@ +import pytest +from zope.interface import implementer + from guillotina.behaviors.dublincore import IDublinCore from guillotina.content import Item from guillotina.db.orm.base import BaseObject from guillotina.db.transaction import Transaction -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import IResource -from zope.interface import implementer - -import pytest +from guillotina.interfaces import IAnnotations, IResource pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_dynamic_schema.py b/guillotina/tests/test_dynamic_schema.py index 5087cd9c6..59c7e4144 100644 --- a/guillotina/tests/test_dynamic_schema.py +++ b/guillotina/tests/test_dynamic_schema.py @@ -1,14 +1,14 @@ +import json + +import pytest +import pytest_asyncio +from zope.interface import Interface + from guillotina import configure -from guillotina.behaviors.dynamic import IDynamicFields -from guillotina.behaviors.dynamic import IDynamicFieldValues +from guillotina.behaviors.dynamic import IDynamicFields, IDynamicFieldValues from guillotina.behaviors.properties import FunctionProperty -from guillotina.content import Item -from guillotina.content import load_cached_schema +from guillotina.content import Item, load_cached_schema from guillotina.tests.utils import ContainerRequesterAsyncContextManager -from zope.interface import Interface - -import json -import pytest pytestmark = pytest.mark.asyncio @@ -42,7 +42,7 @@ async def __aenter__(self): return requester -@pytest.fixture(scope="function") +@pytest_asyncio.fixture(scope="function") async def custom_type_container_requester(guillotina): return CustomTypeContainerRequesterAsyncContextManager(guillotina) diff --git a/guillotina/tests/test_errors.py b/guillotina/tests/test_errors.py index a0a108967..0c5e49ab5 100644 --- a/guillotina/tests/test_errors.py +++ b/guillotina/tests/test_errors.py @@ -1,11 +1,11 @@ -from guillotina.exceptions import DeserializationError -from guillotina.exceptions import ValueDeserializationError -from guillotina.response import Response +import asyncio from unittest import mock -import asyncio import pytest +from guillotina.exceptions import DeserializationError, ValueDeserializationError +from guillotina.response import Response + @pytest.mark.asyncio async def test_non_existing_container(container_requester): diff --git a/guillotina/tests/test_files.py b/guillotina/tests/test_files.py index 3e56fcfcb..6395c72e2 100644 --- a/guillotina/tests/test_files.py +++ b/guillotina/tests/test_files.py @@ -1,10 +1,9 @@ +import pytest + from guillotina.exceptions import UnRetryableRequestError -from guillotina.files.utils import get_contenttype -from guillotina.files.utils import read_request_data +from guillotina.files.utils import get_contenttype, read_request_data from guillotina.tests.utils import get_mocked_request -import pytest - @pytest.mark.asyncio async def test_read_request_data_handles_retries(): diff --git a/guillotina/tests/test_guillo.py b/guillotina/tests/test_guillo.py index 5b18466eb..8fc424612 100644 --- a/guillotina/tests/test_guillo.py +++ b/guillotina/tests/test_guillo.py @@ -1,13 +1,13 @@ -from copy import deepcopy -from guillotina import testing -from guillotina import utils -from guillotina.component import globalregistry -from guillotina.factory import make_app - import json import logging +from copy import deepcopy + import pytest +from guillotina import testing, utils +from guillotina.component import globalregistry +from guillotina.factory import make_app + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_info.py b/guillotina/tests/test_info.py index ee300fbb3..b18d1d7fe 100644 --- a/guillotina/tests/test_info.py +++ b/guillotina/tests/test_info.py @@ -1,7 +1,7 @@ -from guillotina.auth.recaptcha import VALIDATION_HEADER - import pytest +from guillotina.auth.recaptcha import VALIDATION_HEADER + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_jsonschema.py b/guillotina/tests/test_jsonschema.py index 2aa990645..41bfcb15e 100644 --- a/guillotina/tests/test_jsonschema.py +++ b/guillotina/tests/test_jsonschema.py @@ -1,11 +1,10 @@ -from guillotina.behaviors.dublincore import IDublinCore -from guillotina.json.utils import convert_interfaces_to_schema -from guillotina.utils import get_schema_validator -from guillotina.utils import JSONSchemaRefResolver - import jsonschema import pytest +from guillotina.behaviors.dublincore import IDublinCore +from guillotina.json.utils import convert_interfaces_to_schema +from guillotina.utils import JSONSchemaRefResolver, get_schema_validator + def test_convert_dublin_core(dummy_guillotina): all_schemas = convert_interfaces_to_schema([IDublinCore]) diff --git a/guillotina/tests/test_login.py b/guillotina/tests/test_login.py index eeafa3d6e..4b0465b9d 100644 --- a/guillotina/tests/test_login.py +++ b/guillotina/tests/test_login.py @@ -1,10 +1,11 @@ -from guillotina.auth.users import ROOT_USER_ID -from guillotina.testing import TESTING_SETTINGS - import json + import jwt import pytest +from guillotina.auth.users import ROOT_USER_ID +from guillotina.testing import TESTING_SETTINGS + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_metrics.py b/guillotina/tests/test_metrics.py index 4f442232d..a2580a688 100644 --- a/guillotina/tests/test_metrics.py +++ b/guillotina/tests/test_metrics.py @@ -1,4 +1,11 @@ +import asyncio +import pickle +from unittest.mock import MagicMock + +import prometheus_client +import pytest from asyncmock import AsyncMock + from guillotina import metrics from guillotina.const import ROOT_ID from guillotina.content import Container @@ -8,12 +15,6 @@ from guillotina.db.transaction import Transaction from guillotina.db.transaction_manager import TransactionManager from guillotina.tests.utils import create_content -from unittest.mock import MagicMock - -import asyncio -import pickle -import prometheus_client -import pytest pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_middlewares.py b/guillotina/tests/test_middlewares.py index 8d9d1b80a..e12c770e3 100644 --- a/guillotina/tests/test_middlewares.py +++ b/guillotina/tests/test_middlewares.py @@ -1,10 +1,11 @@ -from guillotina.middlewares.errors import generate_error_response - import asyncio -import pytest import time import unittest +import pytest + +from guillotina.middlewares.errors import generate_error_response + class AsgiMiddlewate: def __init__(self, app): @@ -41,9 +42,9 @@ def _makeOne(self, exc, request=None): def test_cancelled_error(self): resp = self._makeOne(asyncio.CancelledError()) assert resp.content["message"].startswith("Cancelled execution") - self.assertEquals(resp.content["reason"], "unknownError") + self.assertEqual(resp.content["reason"], "unknownError") def test_other_error(self): resp = self._makeOne(ValueError()) assert resp.content["message"].startswith("Error on execution") - self.assertEquals(resp.content["reason"], "unknownError") + self.assertEqual(resp.content["reason"], "unknownError") diff --git a/guillotina/tests/test_postgres.py b/guillotina/tests/test_postgres.py index 0bb9f0fe8..cb0ad5491 100644 --- a/guillotina/tests/test_postgres.py +++ b/guillotina/tests/test_postgres.py @@ -1,3 +1,11 @@ +import asyncio +import os +from unittest.mock import Mock, patch +from uuid import uuid4 + +import asyncpg +import pytest + from guillotina.api.container import create_container from guillotina.component import get_adapter from guillotina.content import Folder @@ -5,18 +13,9 @@ from guillotina.db.storages.cockroach import CockroachStorage from guillotina.db.storages.pg import PostgresqlStorage from guillotina.db.transaction_manager import TransactionManager -from guillotina.exceptions import ConflictError -from guillotina.exceptions import ConflictIdOnContainer +from guillotina.exceptions import ConflictError, ConflictIdOnContainer from guillotina.tests import mocks from guillotina.tests.utils import create_content -from unittest.mock import Mock -from unittest.mock import patch -from uuid import uuid4 - -import asyncio -import asyncpg -import os -import pytest pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_queue.py b/guillotina/tests/test_queue.py index fa39c0feb..92c5f5656 100644 --- a/guillotina/tests/test_queue.py +++ b/guillotina/tests/test_queue.py @@ -1,13 +1,13 @@ -from guillotina.async_util import IAsyncJobPool -from guillotina.async_util import IQueueUtility +import asyncio + +import pytest + +from guillotina.async_util import IAsyncJobPool, IQueueUtility from guillotina.browser import View from guillotina.component import get_utility from guillotina.tests import utils from guillotina.transactions import transaction -import asyncio -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_renderers.py b/guillotina/tests/test_renderers.py index 4b462dab9..c2799e6a7 100644 --- a/guillotina/tests/test_renderers.py +++ b/guillotina/tests/test_renderers.py @@ -1,8 +1,9 @@ from datetime import datetime -from guillotina.renderers import guillotina_json_default import pytest +from guillotina.renderers import guillotina_json_default + def test_guillotina_json_default_doesnt_serialize_datetime(): def _makeOne(obj): diff --git a/guillotina/tests/test_request.py b/guillotina/tests/test_request.py index afa6df373..94d9b7c4d 100644 --- a/guillotina/tests/test_request.py +++ b/guillotina/tests/test_request.py @@ -1,8 +1,9 @@ -from guillotina.tests.utils import make_mocked_request -from multidict import CIMultiDict - import asyncio + import pytest +from multidict import CIMultiDict + +from guillotina.tests.utils import make_mocked_request pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_routes.py b/guillotina/tests/test_routes.py index 28bf03be7..aadb46d2f 100644 --- a/guillotina/tests/test_routes.py +++ b/guillotina/tests/test_routes.py @@ -1,8 +1,8 @@ +import pytest + from guillotina import routes from guillotina.response import InvalidRoute -import pytest - def test_convert_simple_route_to_view_name(): assert routes.Route("@foobar").view_name == "@foobar" diff --git a/guillotina/tests/test_security.py b/guillotina/tests/test_security.py index 2f0abef5b..125af3fc0 100644 --- a/guillotina/tests/test_security.py +++ b/guillotina/tests/test_security.py @@ -1,21 +1,21 @@ +import json + +import pytest + from guillotina.api.container import create_container from guillotina.auth.users import GuillotinaUser from guillotina.content import create_content_in_container -from guillotina.interfaces import Allow -from guillotina.interfaces import IRolePermissionManager +from guillotina.interfaces import Allow, IRolePermissionManager from guillotina.security.policy import cached_roles -from guillotina.security.utils import get_principals_with_access_content -from guillotina.security.utils import get_roles_with_access_content -from guillotina.security.utils import settings_for_object +from guillotina.security.utils import ( + get_principals_with_access_content, + get_roles_with_access_content, + settings_for_object, +) from guillotina.tests import utils from guillotina.tests.utils import get_db from guillotina.transactions import transaction -from guillotina.utils import get_authenticated_user -from guillotina.utils import get_roles_principal -from guillotina.utils import get_security_policy - -import json -import pytest +from guillotina.utils import get_authenticated_user, get_roles_principal, get_security_policy pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_serialize.py b/guillotina/tests/test_serialize.py index a0647840a..45d771997 100644 --- a/guillotina/tests/test_serialize.py +++ b/guillotina/tests/test_serialize.py @@ -1,30 +1,24 @@ -from datetime import datetime -from datetime import time +import uuid +from datetime import datetime, time + +import pytest from dateutil.tz import tzutc -from guillotina import fields -from guillotina import schema -from guillotina.component import get_adapter -from guillotina.component import get_multi_adapter +from zope.interface import Interface, Invalid +from zope.interface.interface import invariant + +from guillotina import fields, schema +from guillotina.component import get_adapter, get_multi_adapter from guillotina.exceptions import ValueDeserializationError from guillotina.fields import patch from guillotina.fields.interfaces import IPatchFieldOperation from guillotina.files.dbfile import DBFile -from guillotina.interfaces import IJSONToValue -from guillotina.interfaces import IResourceDeserializeFromJson -from guillotina.interfaces import IResourceSerializeToJson +from guillotina.interfaces import IJSONToValue, IResourceDeserializeFromJson, IResourceSerializeToJson from guillotina.json import deserialize_value from guillotina.json.deserialize_value import schema_compatible from guillotina.json.serialize_value import json_compatible from guillotina.json.utils import validate_invariants from guillotina.schema.exceptions import WrongType -from guillotina.tests.utils import create_content -from guillotina.tests.utils import login -from zope.interface import Interface -from zope.interface import Invalid -from zope.interface.interface import invariant - -import pytest -import uuid +from guillotina.tests.utils import create_content, login pytestmark = pytest.mark.asyncio @@ -78,8 +72,8 @@ async def test_serialize_omit_main_interface_field(dummy_request, mock_txn): async def test_serialize_cloud_file(dummy_request, mock_txn): - from guillotina.test_package import FileContent, IFileContent from guillotina.interfaces import IFileManager + from guillotina.test_package import FileContent, IFileContent obj = create_content(FileContent) obj.file = DBFile(filename="foobar.json", md5="foobar") @@ -101,7 +95,7 @@ async def _data(): async def test_deserialize_cloud_file(dummy_request, mock_txn): - from guillotina.test_package import IFileContent, FileContent + from guillotina.test_package import FileContent, IFileContent obj = create_content(FileContent) obj.file = None diff --git a/guillotina/tests/test_server.py b/guillotina/tests/test_server.py index eb5087e20..f1d272d3b 100644 --- a/guillotina/tests/test_server.py +++ b/guillotina/tests/test_server.py @@ -1,17 +1,18 @@ +import asyncio +from unittest import mock + +import pytest + from guillotina.component import get_utility from guillotina.exceptions import ConflictError from guillotina.factory.app import close_utilities from guillotina.test_package import ITestAsyncUtility from guillotina.traversal import TraversalRouter -from unittest import mock - -import asyncio -import pytest def test_make_app(dummy_guillotina): assert dummy_guillotina is not None - assert type(dummy_guillotina.router) == TraversalRouter + assert isinstance(dummy_guillotina.router, TraversalRouter) @pytest.mark.asyncio diff --git a/guillotina/tests/test_static.py b/guillotina/tests/test_static.py index 180453d9d..f4dc49865 100644 --- a/guillotina/tests/test_static.py +++ b/guillotina/tests/test_static.py @@ -1,8 +1,8 @@ +import pytest + from guillotina.component import get_utility from guillotina.interfaces import IApplication -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_storages.py b/guillotina/tests/test_storages.py index a5bd8f941..50cdfd2ed 100644 --- a/guillotina/tests/test_storages.py +++ b/guillotina/tests/test_storages.py @@ -1,13 +1,14 @@ +import json +import os + +import pytest + from guillotina._settings import app_settings from guillotina.component import get_adapter from guillotina.db.factory import CockroachDatabaseManager from guillotina.db.interfaces import IDatabaseManager from guillotina.utils import get_database -import json -import os -import pytest - pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_swagger.py b/guillotina/tests/test_swagger.py index b020ff8fe..4ae48d2c8 100644 --- a/guillotina/tests/test_swagger.py +++ b/guillotina/tests/test_swagger.py @@ -1,8 +1,8 @@ -from openapi_spec_validator import validate_v3_spec - import json import os + import pytest +from openapi_spec_validator import validate_spec pytestmark = pytest.mark.asyncio @@ -42,7 +42,7 @@ async def test_validate_swagger_definition(container_requester): for path in ("/", "/db", "/db/guillotina"): resp, status = await requester("GET", os.path.join(path, "@swagger")) assert status == 200 - validate_v3_spec(resp) + validate_spec(resp) @pytest.mark.app_settings(SWAGGER_SETTINGS) diff --git a/guillotina/tests/test_transactions.py b/guillotina/tests/test_transactions.py index 103a63653..033c5c349 100644 --- a/guillotina/tests/test_transactions.py +++ b/guillotina/tests/test_transactions.py @@ -1,15 +1,16 @@ +import pytest + from guillotina import task_vars from guillotina.content import create_content_in_container from guillotina.db import ROOT_ID -from guillotina.exceptions import TransactionClosedException -from guillotina.exceptions import TransactionNotFound -from guillotina.exceptions import TransactionObjectRegistrationMismatchException +from guillotina.exceptions import ( + TransactionClosedException, + TransactionNotFound, + TransactionObjectRegistrationMismatchException, +) from guillotina.tests import utils from guillotina.transactions import transaction -from guillotina.utils import get_database -from guillotina.utils import get_object_by_uid - -import pytest +from guillotina.utils import get_database, get_object_by_uid pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/test_traversal.py b/guillotina/tests/test_traversal.py index 3b2b24a5b..12dabec50 100644 --- a/guillotina/tests/test_traversal.py +++ b/guillotina/tests/test_traversal.py @@ -1,9 +1,9 @@ +import pytest + from guillotina.response import Response from guillotina.tests.utils import get_mocked_request from guillotina.traversal import apply_cors -import pytest - class CorsTestRenderer: def __init__(self, settings): diff --git a/guillotina/tests/test_urls.py b/guillotina/tests/test_urls.py index c99bfc0c4..13b3c5c52 100644 --- a/guillotina/tests/test_urls.py +++ b/guillotina/tests/test_urls.py @@ -1,9 +1,10 @@ -from guillotina.tests.utils import make_mocked_request -from guillotina.utils import get_url - import json + import pytest +from guillotina.tests.utils import make_mocked_request +from guillotina.utils import get_url + def test_url(dummy_guillotina): request = make_mocked_request("GET", "/a/b", query_string=b"include=title") diff --git a/guillotina/tests/test_utils.py b/guillotina/tests/test_utils.py index a03f68190..5b6699fc4 100644 --- a/guillotina/tests/test_utils.py +++ b/guillotina/tests/test_utils.py @@ -1,17 +1,14 @@ +import json + +import pytest + from guillotina import utils from guillotina.behaviors.dublincore import IDublinCore -from guillotina.interfaces import IPrincipalRoleManager -from guillotina.interfaces import IResource -from guillotina.tests.utils import create_content -from guillotina.tests.utils import get_mocked_request -from guillotina.tests.utils import get_root -from guillotina.tests.utils import login +from guillotina.interfaces import IPrincipalRoleManager, IResource +from guillotina.tests.utils import create_content, get_mocked_request, get_root, login from guillotina.utils import get_behavior from guillotina.utils.navigator import Navigator -import json -import pytest - def test_module_resolve_path(): assert utils.resolve_module_path("guillotina") == "guillotina" diff --git a/guillotina/tests/test_vocabulary.py b/guillotina/tests/test_vocabulary.py index 95ba89406..bb4a4ef3c 100644 --- a/guillotina/tests/test_vocabulary.py +++ b/guillotina/tests/test_vocabulary.py @@ -1,8 +1,8 @@ +import pytest + from guillotina import configure from guillotina.schema.vocabulary import getVocabularyRegistry -import pytest - @configure.vocabulary(name="testvocab") class VocabTest: diff --git a/guillotina/tests/test_ws.py b/guillotina/tests/test_ws.py index 83297625c..ba4967c5a 100644 --- a/guillotina/tests/test_ws.py +++ b/guillotina/tests/test_ws.py @@ -1,8 +1,9 @@ -from guillotina.testing import ADMIN_TOKEN - import json + import pytest +from guillotina.testing import ADMIN_TOKEN + pytestmark = pytest.mark.asyncio diff --git a/guillotina/tests/utils.py b/guillotina/tests/utils.py index c1ae98d1c..b19efc909 100644 --- a/guillotina/tests/utils.py +++ b/guillotina/tests/utils.py @@ -1,20 +1,20 @@ +import asyncio +import json +import uuid +from typing import Dict + +from zope.interface import alsoProvides + from guillotina import task_vars from guillotina._settings import app_settings from guillotina.auth.users import RootUser from guillotina.auth.utils import set_authenticated_user from guillotina.behaviors import apply_markers from guillotina.content import Item -from guillotina.interfaces import IDefaultLayer -from guillotina.interfaces import IRequest +from guillotina.interfaces import IDefaultLayer, IRequest from guillotina.request import Request from guillotina.transactions import transaction from guillotina.utils import get_database -from typing import Dict -from zope.interface import alsoProvides - -import asyncio -import json -import uuid def get_db(app, db_id): diff --git a/guillotina/transactions.py b/guillotina/transactions.py index 21574123b..647ec1409 100644 --- a/guillotina/transactions.py +++ b/guillotina/transactions.py @@ -1,10 +1,9 @@ -from guillotina import task_vars -from guillotina.db.interfaces import ITransaction -from guillotina.db.interfaces import ITransactionManager - import logging import typing +from guillotina import task_vars +from guillotina.db.interfaces import ITransaction, ITransactionManager + logger = logging.getLogger("guillotina") diff --git a/guillotina/traversal.py b/guillotina/traversal.py index 23942ee28..b793dfba3 100644 --- a/guillotina/traversal.py +++ b/guillotina/traversal.py @@ -1,60 +1,48 @@ """Main routing traversal class.""" + +import asyncio +import traceback from contextlib import contextmanager -from guillotina import __version__ -from guillotina import logger -from guillotina import response -from guillotina import routes -from guillotina import task_vars +from typing import Optional, Tuple + +from zope.interface import alsoProvides + +from guillotina import __version__, logger, response, routes, task_vars from guillotina._settings import app_settings from guillotina.api.content import DefaultOPTIONS from guillotina.auth.users import AnonymousUser -from guillotina.auth.utils import authenticate_request -from guillotina.auth.utils import set_authenticated_user +from guillotina.auth.utils import authenticate_request, set_authenticated_user from guillotina.browser import View -from guillotina.component import get_utility -from guillotina.component import query_adapter -from guillotina.component import query_multi_adapter -from guillotina.contentnegotiation import get_acceptable_content_types -from guillotina.contentnegotiation import get_acceptable_languages +from guillotina.component import get_utility, query_adapter, query_multi_adapter +from guillotina.contentnegotiation import get_acceptable_content_types, get_acceptable_languages from guillotina.db.orm.interfaces import IBaseObject from guillotina.event import notify -from guillotina.events import BeforeRenderViewEvent -from guillotina.events import ObjectLoadedEvent -from guillotina.events import TraversalRouteMissEvent -from guillotina.events import TraversalViewMissEvent -from guillotina.exceptions import ApplicationNotFound -from guillotina.exceptions import ConflictError -from guillotina.exceptions import TIDConflictError -from guillotina.interfaces import ACTIVE_LAYERS_KEY -from guillotina.interfaces import IApplication -from guillotina.interfaces import IAsyncContainer -from guillotina.interfaces import IContainer -from guillotina.interfaces import IDatabase -from guillotina.interfaces import ILanguage -from guillotina.interfaces import IOPTIONS -from guillotina.interfaces import IPermission -from guillotina.interfaces import IRenderer -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResponse -from guillotina.interfaces import ITraversable +from guillotina.events import ( + BeforeRenderViewEvent, + ObjectLoadedEvent, + TraversalRouteMissEvent, + TraversalViewMissEvent, +) +from guillotina.exceptions import ApplicationNotFound, ConflictError, TIDConflictError +from guillotina.interfaces import ( + ACTIVE_LAYERS_KEY, + IOPTIONS, + IApplication, + IAsyncContainer, + IContainer, + IDatabase, + ILanguage, + IPermission, + IRenderer, + IRequest, + IResponse, + ITraversable, +) from guillotina.profile import profilable -from guillotina.response import HTTPBadRequest -from guillotina.response import HTTPMethodNotAllowed -from guillotina.response import HTTPNotFound -from guillotina.response import HTTPUnauthorized -from guillotina.response import Response +from guillotina.response import HTTPBadRequest, HTTPMethodNotAllowed, HTTPNotFound, HTTPUnauthorized, Response from guillotina.security.utils import get_view_permission -from guillotina.transactions import abort -from guillotina.transactions import commit -from guillotina.utils import get_registry -from guillotina.utils import get_security_policy -from guillotina.utils import import_class -from typing import Optional -from typing import Tuple -from zope.interface import alsoProvides - -import asyncio -import traceback +from guillotina.transactions import abort, commit +from guillotina.utils import get_registry, get_security_policy, import_class async def traverse( @@ -195,9 +183,9 @@ def debug(self, request, resp): for idx, query in enumerate(txn._queries.keys()): counts = txn._queries[query] duration = "{0:.5f}".format(counts[1] * 1000) - resp.headers[ - f"XG-Query-{idx}" - ] = f"count: {counts[0]}, time: {duration}, query: {query}" # noqa + resp.headers[f"XG-Query-{idx}"] = ( + f"count: {counts[0]}, time: {duration}, query: {query}" # noqa + ) except (KeyError, AttributeError): resp.headers["XG-Error"] = "Could not get stats" diff --git a/guillotina/utils/__init__.py b/guillotina/utils/__init__.py index 08cc9306d..30f477733 100644 --- a/guillotina/utils/__init__.py +++ b/guillotina/utils/__init__.py @@ -17,6 +17,8 @@ from .content import valid_id # noqa from .crypto import get_jwk_key # noqa from .crypto import secure_passphrase # noqa +from .misc import JSONSchemaRefResolver # noqa +from .misc import apply_coroutine # noqa; noqa from .misc import dump_task_vars # noqa from .misc import find_container # noqa from .misc import get_current_container # noqa @@ -28,7 +30,6 @@ from .misc import get_request_scheme # noqa from .misc import get_schema_validator # noqa from .misc import get_url # noqa -from .misc import JSONSchemaRefResolver # noqa from .misc import lazy_apply # noqa from .misc import list_or_dict_items # noqa from .misc import load_task_vars # noqa @@ -51,7 +52,4 @@ from .navigator import Navigator # noqa -from .misc import apply_coroutine # noqa; noqa - - get_object_by_oid = get_object_by_uid diff --git a/guillotina/utils/auth.py b/guillotina/utils/auth.py index 5b41c075a..2320fe139 100644 --- a/guillotina/utils/auth.py +++ b/guillotina/utils/auth.py @@ -1,8 +1,8 @@ +from typing import Optional + from guillotina import task_vars from guillotina.component import get_adapter -from guillotina.interfaces import IPrincipal -from guillotina.interfaces import ISecurityPolicy -from typing import Optional +from guillotina.interfaces import IPrincipal, ISecurityPolicy def get_authenticated_user() -> Optional[IPrincipal]: diff --git a/guillotina/utils/content.py b/guillotina/utils/content.py index cf2a2cd47..b0595923e 100644 --- a/guillotina/utils/content.py +++ b/guillotina/utils/content.py @@ -1,25 +1,24 @@ -from .misc import get_current_request -from .misc import list_or_dict_items -from guillotina import glogging -from guillotina import task_vars +import typing + +from guillotina import glogging, task_vars from guillotina._settings import app_settings -from guillotina.component import get_adapter -from guillotina.component import get_utility -from guillotina.component import query_multi_adapter +from guillotina.component import get_adapter, get_utility, query_multi_adapter from guillotina.const import TRASHED_ID from guillotina.db.interfaces import IDatabaseManager from guillotina.db.orm.interfaces import IBaseObject from guillotina.exceptions import DatabaseNotFound -from guillotina.interfaces import IAbsoluteURL -from guillotina.interfaces import IApplication -from guillotina.interfaces import IAsyncContainer -from guillotina.interfaces import IContainer -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IPrincipalRoleMap -from guillotina.interfaces import IRequest -from guillotina.interfaces import IResource - -import typing +from guillotina.interfaces import ( + IAbsoluteURL, + IApplication, + IAsyncContainer, + IContainer, + IDatabase, + IPrincipalRoleMap, + IRequest, + IResource, +) + +from .misc import get_current_request, list_or_dict_items logger = glogging.getLogger("guillotina") diff --git a/guillotina/utils/crypto.py b/guillotina/utils/crypto.py index 593d09ae0..1ef417f8a 100644 --- a/guillotina/utils/crypto.py +++ b/guillotina/utils/crypto.py @@ -1,9 +1,10 @@ -from guillotina._settings import app_settings -from jwcrypto import jwk - import logging import string +from jwcrypto import jwk + +from guillotina._settings import app_settings + logger = logging.getLogger("guillotina") diff --git a/guillotina/utils/execute.py b/guillotina/utils/execute.py index 54bcdaf17..bdf950013 100644 --- a/guillotina/utils/execute.py +++ b/guillotina/utils/execute.py @@ -1,20 +1,16 @@ +import asyncio +import uuid from functools import partial +from typing import Any, Callable, Coroutine, Optional + from guillotina import task_vars from guillotina.component import get_utility from guillotina.exceptions import TransactionNotFound -from guillotina.interfaces import IAsyncJobPool -from guillotina.interfaces import IQueueUtility +from guillotina.interfaces import IAsyncJobPool, IQueueUtility from guillotina.profile import profilable from guillotina.task_vars import txn from guillotina.transactions import get_transaction from guillotina.utils import notice_on_error_internal -from typing import Any -from typing import Callable -from typing import Coroutine -from typing import Optional - -import asyncio -import uuid class ExecuteContext: diff --git a/guillotina/utils/misc.py b/guillotina/utils/misc.py index 08b13ce81..2c3ecc912 100644 --- a/guillotina/utils/misc.py +++ b/guillotina/utils/misc.py @@ -1,27 +1,5 @@ -from collections.abc import MutableMapping -from functools import partial -from guillotina import glogging -from guillotina import task_vars -from guillotina._settings import app_settings -from guillotina.component import get_utility -from guillotina.db.interfaces import ITransaction -from guillotina.exceptions import ContainerNotFound -from guillotina.exceptions import DatabaseNotFound -from guillotina.exceptions import RequestNotFound -from guillotina.exceptions import TransactionNotFound -from guillotina.interfaces import IAnnotations -from guillotina.interfaces import IApplication -from guillotina.interfaces import IContainer -from guillotina.interfaces import IDatabase -from guillotina.interfaces import IRegistry -from guillotina.interfaces import IRequest -from guillotina.profile import profilable -from hashlib import sha256 as sha -from urllib.parse import unquote - import asyncio import inspect -import jsonschema.validators import os import random import string @@ -29,6 +7,20 @@ import types import typing import urllib.parse +from collections.abc import MutableMapping +from functools import partial +from hashlib import sha256 as sha +from urllib.parse import unquote + +import jsonschema.validators + +from guillotina import glogging, task_vars +from guillotina._settings import app_settings +from guillotina.component import get_utility +from guillotina.db.interfaces import ITransaction +from guillotina.exceptions import ContainerNotFound, DatabaseNotFound, RequestNotFound, TransactionNotFound +from guillotina.interfaces import IAnnotations, IApplication, IContainer, IDatabase, IRegistry, IRequest +from guillotina.profile import profilable try: diff --git a/guillotina/utils/modules.py b/guillotina/utils/modules.py index cd84a109d..40189ffef 100644 --- a/guillotina/utils/modules.py +++ b/guillotina/utils/modules.py @@ -1,14 +1,14 @@ -from guillotina.gtypes import ResolvableType -from typing import Any -from typing import Optional -from zope.interface.interfaces import IInterface - import importlib import inspect import os import pathlib import sys import types +from typing import Any, Optional + +from zope.interface.interfaces import IInterface + +from guillotina.gtypes import ResolvableType def import_class(import_string: str) -> Optional[types.ModuleType]: diff --git a/guillotina/utils/navigator.py b/guillotina/utils/navigator.py index fa627c7f1..488646d4d 100644 --- a/guillotina/utils/navigator.py +++ b/guillotina/utils/navigator.py @@ -1,9 +1,10 @@ -from .content import get_content_path +import posixpath +import weakref + from guillotina import task_vars from guillotina.interfaces import IResource -import posixpath -import weakref +from .content import get_content_path class Navigator: diff --git a/pytest.ini b/pytest.ini index a07fd9017..05673e38e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,9 +1,9 @@ # content of pytest.ini [pytest] -norecursedirs = cookiecutter +norecursedirs = cookiecutter build _build filterwarnings = ignore::ResourceWarning ignore::DeprecationWarning ignore::PendingDeprecationWarning markers = - app_settings: customize app_settings for tests \ No newline at end of file + app_settings: customize app_settings for tests diff --git a/requirements.txt b/requirements.txt index 41d0ff7f9..128c28fad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,21 +2,21 @@ Cython==0.29.24 asyncpg>=0.28.0 cffi==1.14.6 chardet==3.0.4 -jsonschema==2.6.0 +jsonschema==4.24.1 multidict==6.0.4 pycparser==2.20 pycryptodome==3.6.6 PyJWT~=2.1.0 python-dateutil==2.8.2 PyYaml>=5.1 -six==1.11.0 +six==1.17.0 orjson>=3,<4 zope.interface==5.1.0 -uvicorn==0.17.6 +uvicorn==0.42.0 argon2-cffi==18.3.0 backoff==1.10.0 prometheus-client==0.8.0 -typing_extensions==3.7.4.3 +typing_extensions==4.14.1 types-chardet==0.1.5 types-docutils==0.17.0 diff --git a/setup.cfg b/setup.cfg index 23095fb55..aa7f7359a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,18 @@ [aliases] test = pytest +[tool:pytest] +asyncio_mode = auto + [zest.releaser] create-wheel = yes [isort] +profile=black lines_after_imports=2 -force_single_line=true line_length=110 -not_skip=__init__.py skip_glob=*.pyi diff --git a/setup.py b/setup.py index f2be4575d..de37eee2f 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ install_requires=[ "uvicorn", "websockets", - "jsonschema==2.6.0", + "jsonschema==4.24.1", "python-dateutil", "pycryptodome", "jwcrypto", @@ -78,17 +78,17 @@ ], extras_require={ "test": [ - "pytest>=3.8.0,<6.3.0", + "pytest>=7.4.0,<8.0.0", "docker==7.1.0", "backoff", "psycopg2-binary", - "pytest-asyncio<=0.13.0", + "pytest-asyncio>=0.21.0,<0.22.0", "pytest-cov", "coverage>=4.0.3", - "pytest-docker-fixtures", - "pytest-rerunfailures<=10.1", + "pytest-docker-fixtures==1.4.2", + "pytest-rerunfailures>=12.0,<13.0", "async-asgi-testclient<2.0.0", - "openapi-spec-validator==0.2.9", + "openapi-spec-validator==0.8.4", "aiohttp>=3.0.0,<4.0.0", "asyncmock", "prometheus-client", @@ -111,6 +111,7 @@ "memcached": ["emcache"], "validation": ["pytz==2020.1"], "recaptcha": ["aiohttp<4"], + "mcp": ["mcp>=1.0.0"], }, entry_points={ "console_scripts": [