Best Python code snippet using localstack_python
webarchive.py
Source:webarchive.py  
1"""WebArchive class implementation."""2import os3import io4import plistlib5import mimetypes6from urllib.parse import urlparse, urljoin7from .exceptions import WebArchiveError8from .webresource import WebResource9from .util import (is_html_mime_type,10                   process_css_resource, process_html_resource)11__all__ = ["WebArchive"]12class WebArchive(object):13    """An archive storing a webpage's content and embedded external media.14    A webarchive consists of the following elements:15      * A main resource (required). This is a WebResource object storing16        the page's HTML content.17      * Subresources (optional). These are WebResource objects storing18        external media like images, scripts, and style sheets.19      * Subframe archives (optional). These are nested WebArchive objects20        storing other webpages displayed in HTML frames.21    You can examine these resources using this class's main_resource,22    subresources, and subframe_archives properties, respectively.23    The main operation of interest on webarchives is extraction, which24    here simply means converting the webarchive to a standard HTML page.25    See the extract() method's documentation for details.26    Note: You should always use webarchive.open() to access a webarchive27    file rather than instantiate this class directly.28    """29    __slots__ = ["_parent",30                 "_main_resource", "_subresources", "_subframe_archives",31                 "_local_paths"]32    def __init__(self, parent=None):33        """Return a new WebArchive object.34        You should always use webarchive.open() to access a WebArchive35        rather than instantiate this class directly, since the constructor36        arguments may change in a future release.37        """38        self._parent = parent39        self._main_resource = None40        self._subresources = []41        self._subframe_archives = []42        # Basenames for extracted subresources, indexed by (absolute) URL43        #44        # This also contains entries for the main resources, but not45        # subresources, of any subframe archives.46        #47        # Each subframe archive has its own local paths dictionary so it48        # can be extracted independently of its parent archive.49        self._local_paths = {}50    def __del__(self):51        """Clean up before deleting this object."""52        self.close()53    def __enter__(self):54        """Enter the runtime context."""55        return self56    def __exit__(self, exc_type, exc_value, traceback):57        """Exit the runtime context."""58        self.close()59        return False  # process any raised exception normally60    def close(self):61        """Close this webarchive."""62        # This currently does nothing, but is provided for semantic63        # compatibility with io.open().64        pass65    def extract(self, output_path, embed_subresources=False,66                *, before_cb=None, after_cb=None, canceled_cb=None):67        """Extract this webarchive.68        Extraction converts a webarchive to a standard HTML document.69        The resulting document should look and behave identically to the70        original webarchive file as displayed by Safari (apart from the71        usual rendering differences if you are using a different browser).72        External media such as images, scripts, and style sheets are73        handled as follows:74          * If embed_subresources is False (the default), this archive's75            subresources will be saved as individual files. References76            to those resources will be rewritten to use the local copies.77            This is how the "Save As" command in other browsers like78            Mozilla Firefox usually works.79          * If embed_subresources is True, subresources will be embedded80            inline using data URIs. This allows the entire page to be81            stored in a single file, mimicking the webarchive format's82            main feature, with full cross-browser support but much lower83            efficiency. Be aware that this typically requires more disk84            space and processing time than extracting to separate files.85          * Regardless of the above setting, references to media not86            stored as subresources will be replaced with absolute URLs.87        To allow monitoring or canceling an extraction in process, you88        can specify callback functions for the following keyword arguments:89          before_cb(res, path)90            Called just before extracting a WebResource. No return value.91            - res is the WebResource object to be extracted.92            - path is the absolute path where it will be extracted.93          after_cb(res, path)94            Called just after extracting a WebResource. No return value.95            - res is the WebResource object that was extracted.96            - path is the absolute path where it was extracted.97          canceled_cb()98            Called periodically to check if extraction was canceled99            by the user. Should return True to cancel, False otherwise.100        If an error occurs during extraction, this will raise a101        WebArchiveError with a message explaining what went wrong.102        """103        # Note: _extract_main_resource() checks that an archive actually104        # has a main resource, and raises an exception if it's missing.105        # The embed_subresources argument was previously named single_file.106        # Since it is intended to be used as a positional rather than a107        # keyword argument, I think this is an acceptable change to provide108        # a more descriptive name for this feature. However, this does break109        # backwards compatibility for any code passing single_file as a110        # keyword argument against our intentions.111        if canceled_cb and canceled_cb():112            return113        def BEFORE(res, path):114            if before_cb:115                before_cb(res, path)116        def AFTER(res, path):117            if after_cb:118                after_cb(res, path)119        # Strip the extension from the output path120        base, ext = os.path.splitext(os.path.basename(output_path))121        if embed_subresources:122            # Extract the main resource, embedding subresources recursively123            # using data URIs124            BEFORE(self._main_resource, output_path)125            self._extract_main_resource(output_path, None)126            AFTER(self._main_resource, output_path)127        else:128            # Make sure all subresources have local paths129            # (this is redundant for read-only archives, but could be130            # useful if pywebarchive ever implements write support)131            self._make_local_paths()132            # Basename of the directory containing extracted subresources133            subresource_dir_base = "{0}_files".format(base)134            # Full path to the directory containing extracted subresources135            subresource_dir = os.path.join(os.path.dirname(output_path),136                                           subresource_dir_base)137            # Extract the main resource138            BEFORE(self._main_resource, output_path)139            self._extract_main_resource(output_path, subresource_dir_base)140            AFTER(self._main_resource, output_path)141            # Make a directory for subresources142            if self._subresources or self._subframe_archives:143                os.makedirs(subresource_dir, exist_ok=True)144            # Extract subresources145            for res in self._subresources:146                # Full path to the extracted subresource147                subresource_path = os.path.join(subresource_dir,148                                                self._local_paths[res.url])149                if canceled_cb and canceled_cb():150                    return151                # Extract this subresource152                BEFORE(res, subresource_path)153                self._extract_subresource(res, subresource_path)154                AFTER(res, subresource_path)155            # Recursively extract subframe archives156            for subframe_archive in self._subframe_archives:157                # We test this here to stop processing further subframe158                # archives; the nested calls to extract() will separately159                # test this to stop subresource extraction.160                if canceled_cb and canceled_cb():161                    return162                sf_main = subframe_archive._main_resource163                sf_local_path = os.path.join(subresource_dir,164                                             self._local_paths[sf_main.url])165                subframe_archive.extract(sf_local_path,166                                         embed_subresources,167                                         before_cb=before_cb,168                                         after_cb=after_cb,169                                         canceled_cb=canceled_cb)170    def get_local_path(self, url):171        """Return the local path for the subresource at the specified URL.172        The local path is the basename for the file that would be created173        for this subresource if this archive were extracted.174        If no such subresource exists in this archive, this will raise175        a WebArchiveError exception.176        """177        if url in self._local_paths:178            return self._local_paths[url]179        else:180            raise WebArchiveError("no local path for the specified URL")181    def get_subframe_archive(self, url):182        """Return the subframe archive for the specified URL.183        If no such subframe archive exists in this archive, this will184        raise a WebArchiveError exception.185        """186        if not "://" in url:187            raise WebArchiveError("must specify an absolute URL")188        for subframe_archive in self._subframe_archives:189            if subframe_archive.main_resource.url == url:190                return subframe_archive191        else:192            raise WebArchiveError("no subframe archive for the specified URL")193    def get_subresource(self, url):194        """Return the subresource at the specified URL.195        If no such subresource exists in this archive, this will raise196        a WebArchiveError exception.197        """198        if not "://" in url:199            raise WebArchiveError("must specify an absolute URL")200        for subresource in self._subresources:201            if subresource.url == url:202                return subresource203        else:204            raise WebArchiveError("no subresource for the specified URL")205    def resource_count(self):206        """Return the total number of WebResources in this archive.207        This includes WebResources in subframe archives.208        """209        res_count = 0210        # Just because we should have a main resource doesn't mean we do211        if self._main_resource:212            res_count += 1213        res_count += len(self._subresources)214        for subframe_archive in self._subframe_archives:215            res_count += subframe_archive.resource_count()216        return res_count217    def to_html(self):218        """Return this archive's contents as an HTML document.219        Subresources will be embedded recursively using data URIs,220        as they are when extracting the archive in single-file mode.221        """222        if not self._main_resource:223            raise WebArchiveError("archive does not have a main resource")224        with io.StringIO() as output:225            process_html_resource(self._main_resource, output, None)226            return output.getvalue()227    def _extract_main_resource(self, output_path, subresource_dir):228        """Extract the archive's main resource."""229        main_resource = self._main_resource230        if not main_resource:231            raise WebArchiveError("archive does not have a main resource")232        if is_html_mime_type(main_resource.mime_type):233            with io.open(output_path, "w",234                         encoding=main_resource.text_encoding) as output:235                process_html_resource(main_resource, output, subresource_dir)236        else:237            # Non-HTML main resources are possible; for example, I have238            # one from YouTube where the main resource is JavaScript.239            with io.open(output_path, "wb") as output:240                output.write(bytes(main_resource))241    def _extract_subresource(self, res, output_path):242        """Extract the specified subresource from the archive."""243        if res.mime_type == "text/css":244            with io.open(output_path, "w",245                         encoding=res.text_encoding) as output:246                # URLs in CSS are resolved relative to the style sheet's247                # location, and in our case all subresources are extracted248                # to the same directory.249                process_css_resource(res, output, "")250        elif is_html_mime_type(res.mime_type):251            # HTML subresources are weird, but possible252            with io.open(output_path, "w",253                         encoding=res.text_encoding) as output:254                process_html_resource(res, output, "")255        else:256            # Extract other subresources as-is257            with io.open(output_path, "wb") as output:258                output.write(bytes(res))259    def _get_absolute_url(self, url, base=None):260        """Return the absolute URL to the specified resource.261        Relative URLs are resolved from the main resource's URL262        unless an alternative base is specified. For example,263        URLs in CSS files are resolved relative to the style sheet.264        """265        if not base:266            # Rewrite URLs relative to this archive's main resource267            base = self._main_resource.url268        elif not "://" in base:269            raise WebArchiveError("base must be an absolute URL")270        return urljoin(base, url)271    def _get_local_url(self, subresource_dir, orig_url, base=None):272        """Return a (preferably local) URL for the specified resource.273        This is used to rewrite subresource URLs in HTML and CSS274        resources when extracting a webarchive.275        Relative URLs are resolved from the main resource's URL276        unless an alternative base is specified. For example,277        URLs in CSS files are resolved relative to the style sheet.278        If the resource exists in this archive, this will return its279        local path if subresource_dir is a string, or a data URI280        otherwise. If the resource is not in this archive, this will281        return its absolute URL, so the extracted page will still282        display correctly so long as the original remains available.283        Note: If subresource_dir == '', this returns a local path284        relative to the current directory; this is used when rewriting285        url() values in style sheets. This is deliberately distinct286        from if subresource_dir is None, which returns a data URI.287        """288        # Get the absolute URL of the original resource289        abs_url = self._get_absolute_url(orig_url, base)290        try:291            if subresource_dir is None:292                # Single-file extraction mode293                res = self.get_subresource(abs_url)294                return res.to_data_uri()295            else:296                # Multi-file extraction mode297                local_path = self.get_local_path(abs_url)298                if subresource_dir:299                    return "{0}/{1}".format(subresource_dir, local_path)300                else:301                    return local_path302        except (WebArchiveError):303            # Resource not in archive; return its absolute URL304            return abs_url305    def _make_local_path(self, res):306        """Returns a local path for the specified WebResource."""307        # Basename for the extracted resource308        base = ""309        if res.url:310            # Parse the resource's URL311            parsed_url = urlparse(res.url)312            if parsed_url.scheme == "data":313                # Data URLs are anonymous, so assign a default basename314                base = "data_url"315            else:316                # Get the basename of the URL path317                url_path_basename = os.path.basename(parsed_url.path)318                base, ext = os.path.splitext(url_path_basename)319        if not base:320            # No URL, or blank URL path (why would this occur?)321            base = "blank_url"322        # Files served over HTTP(S) can have any extension, or none at323        # all, because the Content-type header indicates what type of324        # data they contain. However, local files don't have HTTP headers,325        # so browsers rely on file extensions to determine their types.326        # We should thus choose extensions they'll be likely to recognize.327        ext = mimetypes.guess_extension(res.mime_type)328        if not ext:329            ext = ""330        # Certain characters can cause problems in local paths.331        # "%" is used as an escape character in URLs, and both forward-332        # and backslashes are common directory separators. The other333        # characters are forbidden on Windows and some Unix filesystems.334        for c in "%", "<", ">", ":", '"', "/", "\\", "|", "?", "*":335            base = base.replace(c, "_")336        # Windows also doesn't allow certain reserved names that were337        # historically used for DOS devices. Even if we're not running338        # on Windows, it's better to avoid these names anyway in case339        # our extracted files are later copied over to a Windows system.340        if (base.lower() in ("con", "prn", "aux", "nul")341            or (len(base) == 4342                and base[:3].lower() in ("com", "lpt")343                and base[3].isdigit())):344            base = "{0}_".format(base)345        # Re-join the base and extension346        local_path = "{0}{1}".format(base, ext)347        # Append a copy number if needed to ensure a unique basename348        copy_num = 1349        while local_path in self._local_paths.values():350            copy_num += 1351            local_path = "{0}.{1}{2}".format(base, copy_num, ext)352        # Save this resource's local path353        self._local_paths[res.url] = local_path354        return local_path355    def _make_local_paths(self):356        """Make local paths for all of this archive's resources."""357        resources = []358        # This check is to safely handle archives without a main resource.359        # A well-formed webarchive should always have one, but this isn't360        # the place to enforce that.361        if self._main_resource:362            resources.append(self._main_resource)363        for subresource in self._subresources:364            resources.append(subresource)365        # The main resource of a subframe archive is effectively also366        # a subresource, so we include entries for those here367        for subframe_archive in self._subframe_archives:368            resources.append(subframe_archive._main_resource)369        # Generate local paths for any URLs we don't have them for370        for res in resources:371            if not res.url in self._local_paths:372                self._local_paths[res.url] = self._make_local_path(res)373    def _populate_from_plist_data(self, archive_data):374        """Populate this webarchive using parsed data from plistlib."""375        # Property names:376        # - WebMainResource377        # - WebSubresources378        # - WebSubframeArchives379        # Process the main resource380        self._main_resource = WebResource._create_from_plist_data(381            archive_data["WebMainResource"], self382        )383        # Process subresources384        if "WebSubresources" in archive_data:385            for res_data in archive_data["WebSubresources"]:386                res = WebResource._create_from_plist_data(res_data, self)387                self._subresources.append(res)388        # Process subframe archives389        if "WebSubframeArchives" in archive_data:390            for sa_data in archive_data["WebSubframeArchives"]:391                sa = WebArchive._create_from_plist_data(sa_data, self)392                self._subframe_archives.append(sa)393        # Make local paths for subresources394        self._make_local_paths()395    def _populate_from_stream(self, stream):396        """Populate this webarchive from the specified stream."""397        if isinstance(stream, io.IOBase):398            archive_data = plistlib.load(stream)399            self._populate_from_plist_data(archive_data)400        else:401            raise WebArchiveError("invalid stream type")402    @classmethod403    def _create_from_plist_data(cls, archive_data, parent=None):404        """Create a WebArchive object using parsed data from plistlib."""405        res = cls(parent)406        res._populate_from_plist_data(archive_data)407        return res408    @classmethod409    def _open(cls, path, mode="r"):410        """Open the specified webarchive file.411        Only mode 'r' (reading) is currently supported.412        """413        # Note this is the actual function exported as webarchive.open().414        # It uses a private name here to hide its real location from pydoc,415        # since that is an implementation detail that could change, but be416        # aware that any changes made here will be very public indeed.417        archive = cls()418        if isinstance(mode, str):419            if mode == "r":420                # Read this webarchive421                with io.open(path, "rb") as stream:422                    archive._populate_from_stream(stream)423            else:424                raise WebArchiveException(425                    "only mode 'r' (reading) is currently supported"426                )427        else:428            raise WebArchiveError("mode must be a str")429        return archive430    @property431    def main_resource(self):432        """This archive's main resource (a WebResource object)."""433        return self._main_resource434    @property435    def parent(self):436        """This archive's parent WebArchive, if this is a subframe archive.437        This will be set to None if this is the top-level webarchive.438        Note this property is not part of the webarchive format itself,439        but rather is provided by pywebarchive as a convenience.440        """441        return self._parent442    @property443    def subresources(self):444        """This archive's subresources (a list of WebResource objects)."""445        return self._subresources446    @property447    def subframe_archives(self):448        """This archive's subframes (a list of WebArchive objects)."""449        return self._subframe_archives450# These are common file extensions for web content451# that the mimetypes module may not already know452mimetypes.add_type("application/font-woff", ".woff")453mimetypes.add_type("application/x-font-woff", ".woff")454mimetypes.add_type("application/x-javascript", ".js")455mimetypes.add_type("font/woff", ".woff")456mimetypes.add_type("font/woff2", ".woff2")...HypermediaCollection.py
Source:HypermediaCollection.py  
1"""2Hypermedia Collection extends the Hypermedia Resource class by adding  hypermedia based URI routing and 3senml modeling of items and subresources.4Routing uses link relations rel=grp, rel=sub and rel=item5routing is done by identifying subresources that match the next uri segment to be routed until the last6segment in the request uri is identified for resource selection. Each collection routes requests in a 7self contained way, using only knowledge of it's own uri path and it's direct subresources.8group links labeled grp will be processed by forwarding the request to the href uri of the group link9items are senml modeled values in the local context of the collection, stored in an array of objects.10items are processed locally using the SenmlItems class in the same way an instance of the Links class 11is used to store the collection's links.12subresources are sub-collections in the context of the collection. requests to subresources are routed 13to the subresources selected14items and subresources are selected by either matching the resource uri with the request uri, or matching 15the collection uri with the request uri and seleting the resource(s) using query filtering on the link 16attributes, or by selecting the collection uri and matching resource names with senml names "n" in the 17update body. 18routing and group forwarding is done in this class, and resource processing e.g. GET, POST, is done in 19the respective Content Handlers.20resource endpoints are subresource collections with a single item, the item being referenced using the 21name of the collection e.g. /a/b/c is the URI of a resource endpoint. The resource c is a collection 22with a single item that is referenced by the base name and returns representations like this: 23senml    { "bn": "/a/b/c", "e": {"sv": "test"} }24collection+senml    { "l": {"href": "", "rel": "item"}, "bn": "/a/b/c", "e": {"sv": "test"} }25Items and subresources are created by POSTing representations containing items and optionally links in26the senml+collection content-format. THe location of the created resource will consist of the collection 27uri and the resource name specified in the "href" link attribute or the "n" attribute of the corresponding28senml element.29    30Links that fail to select a resource are returned with a status code of 404 Not Found31"""32import MachineHypermediaToolkit.terms as v33from HypermediaResource import HypermediaResource34from copy import deepcopy35from Items import SenmlItems36from PlainTextHandler import PlainTextHandler37from SenmlHandler import SenmlHandler38from SenmlCollectionHandler import SenmlCollectionHandler39class HypermediaCollection(HypermediaResource):40    def __init__(self, rootResource=None, uriPath=["/"], resourceLink=None, resourceItem=None ):41        HypermediaResource.__init__(self)42        self._uriPath = uriPath43        self._pathString = "/"44        for pathElement in uriPath[1:]:45            self._pathString += (pathElement + "/")46        self._pathLen = len(self._uriPath)47        if ["/"] == uriPath :48            self._rootResource = self49        else:50            self._rootResource = rootResource51        self._unrouted = 052            53        self._itemArray = SenmlItems()54        self._subresources = {}55        56        """ merge the constructor rt link attribute values into the self link """57        if None != resourceLink:58            if v._rt in resourceLink:59                self._linkArray.selectMerge({v._rel:v._self}, {v._rt: resourceLink[v._rt]})60            61        """ if there is an item in the constructor, null the resource name and add it to items """62        if None != resourceItem :63            resourceItem[v._n] = v._null64            self._itemArray.add(resourceItem)65            self._linkArray.selectMerge({v._href:v._null},{ v._rel: v._item})66        PlainTextHandler(self)67        SenmlHandler(self)68        SenmlCollectionHandler(self)69    """ Route requests using hyperlinks. Link relations "item" and "sub" are used to identify 70        local items in the collection and sub resources, respectively."""        71    def routeRequest(self, request):  72        self._request = request73        self._unrouted = len(request[v.uriPath]) - self._pathLen74            75        if 0 == self._unrouted:76            """ this resource is selected, process content-format """77            self._processGroup(self._request)78            self.handleRequest(self._request)79        else:80            self._resourceName = self._request[v.uriPath][self._pathLen]81            # if there is both sub and item, route sub and ignore item82            if [] != self._linkArray.get({v._href:self._resourceName, v._rel:v._sub}):83                """ route request to subresource item"""84                self._subresources[self._resourceName].routeRequest(self._request)85            elif 1 == self._unrouted and [] != self._linkArray.get({v._href:self._resourceName, v._rel:v._item}) :86                """ item in the local collection is selected, handle content-format in this context"""87                self.handleRequest(self._request)88            else:89                """ nothing to route or process """90                self._request[v.response][v.status] = v.NotFound91    def _processGroup(self, request):92        """invoke a proxy or promise to forward the request to each resource marked with rel=grp """93        self._groupLinks = self._linkArray.get({v._rel:v._grp})94        if [] != self._groupLinks:95            request[v.response][v.payload] = []96            request[v.response][v.code] = []97            """ make request instances """98            self._requests = []99            for self._link in self._groupLinks:100                print "group link: ", self._link101                self._requests.append( deepcopy(request) )102            for self._request in self._requests:103                """ overwrite uriPath """                104                self._request[v.uriPath] = ["/"]105                for self._pathElement in self._groupLinks.popleft()[v._href].split("/"):106                    if len(self._pathElement) > 0:107                        self._request[v.uriPath].append(self._pathElement)108                """ route request to root resource if path starts with /  """        109                if "/" == self._request[v.uriPath][0]:110                    self._rootResource.routeRequest(self._request)111                else:112                    self.routeRequest(self._request)113            """ collect the results """114            for self._request in self._requests:115                request[v.response][v.payload].append(self._request[v.response][v.payload])116                if v.Success != self._request[v.response][v.status] and \117                        v.Created != self._request[v.response][v.status]:118                    request[v.response][v.status] = v.BadRequest119                request[v.response][v.code].append(self._request[v.response][v.code]) 120            return self._requests121        else:122            return None123    def _createSubresource(self, resourceLink, resourceItem=None):124        resourceName = resourceLink[v._href]125        self._subresources[resourceName] = \126            HypermediaCollection( self._rootResource, self._uriPath + [resourceName], resourceLink, resourceItem) ...PlainTextHandler.py
Source:PlainTextHandler.py  
1import MachineHypermediaToolkit.terms as v2from HypermediaResource import ContentHandler3import json4from Links import Links5class PlainTextHandler(ContentHandler):6    7    _contentFormat = v.plainTextType8    def _processRequest(self, request):9        if 0 == self._resource._unrouted:10            """ process collection URI, select by query parameters, 11                require only one resource link, item or subresource, 12                is selected for text/plain format """13            self._itemLinks = Links(self._resource._linkArray.get(request[v.uriQuery])).get({v._rel:v._item})14            self._subLinks = Links(self._resource._linkArray.get(request[v.uriQuery])).get({v._rel:v._sub})15            if 1 == len(self._itemLinks) and 0 == len(self._subLinks) :16                self._resourceName = self._itemLinks[0][v._href]17            elif 1 == len(self._subLinks) and 0 == len(self._itemLinks) :18                self._resourceName = self._subLinks[0][v._href]19            else:20                request[v.response][v.status] = v.NotFound21            """ clear query parameters when consumed at the path endpoint """22            request[v.uriQuery] = {}23        elif 1 == self._resource._unrouted :24            """ a local context item matched the last path element, _resourceName is set in the resource """25            self._resourceName = self._resource._resourceName26        else:27            request[v.response][v.status] = v.ServerError28            29        """ process item, resourceName is from request URI or from query filtering """30        if v.get == request[v.method] :31            if 1 == len(self._itemLinks) :            32                """ get item in local context """33                request[v.response][v.payload] = \34                        json.dumps(self._resource._itemArray.getValueByName(self._resourceName) )35                request[v.response][v.status] = v.Success36            elif 1 == len(self._subLinks) :37                """ get subresource item """38                request[v.uriPath] = self._resource._uriPath + [self._resourceName]39                request[v.uriQuery] = {v._href:v._null}40                self._resource._subresources[self._resourceName].routeRequest(request)41                """ send request and wait for response """                    42            else:43                request[v.response][v.status] = v.NotFound44                45        elif v.put == request[v.method] :46            if 1 == len(self._itemLinks) :            47                self._resource._itemArray.updateValueByName(self._resourceName, json.loads(request[v.payload]))48                request[v.response][v.status] = v.Success49            elif 1 == len(self._subLinks) :50                """ route a request URI made from the collection path + resource name """51                request[v.uriPath] = self._resource._uriPath + [self._resourceName]52                request[v.uriQuery] = {v._href:v._null}53                self._resource._subresources[self._resourceName].routeRequest(request)54            else:55                request[v.response][v.status] = v.NotFound56        else:...Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
