Skip to content

Commit

Permalink
Merge pull request #752 from sirosen/add-experimental-scope-parsing
Browse files Browse the repository at this point in the history
Move all scope parsing to 'experimental'
  • Loading branch information
sirosen authored Aug 3, 2023
2 parents fc7ae8b + 928b774 commit e70cd23
Show file tree
Hide file tree
Showing 14 changed files with 1,028 additions and 599 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ repos:
rev: v2.2.5
hooks:
- id: codespell
args: ["--ignore-regex", "https://[^\\s]*"]

# custom local hooks
- repo: local
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* Introduce an experimental Globus Auth scope parser in
``globus_sdk.experimental.scope_parser``.
Full usage is documented in the SDK Experimental docs. (:pr:`NUMBER`)
1 change: 1 addition & 0 deletions docs/experimental/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ Globus SDK Experimental Components
:caption: Contents
:maxdepth: 1

scope_parser
35 changes: 35 additions & 0 deletions docs/experimental/scope_parser.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.. _experimental_scope_parser:

Scope Parser
============

.. currentmodule:: globus_sdk.experimental.scope_parser

This component defines a ``Scope`` object and exposes use of a parser which can
parse scope strings into trees of ``Scope`` objects.
It should be regarded as very similar to the existing ``MutableScope`` object in
``globus_sdk.scopes``. Key differences from ``MutableScope``:

- ``Scope.parse`` is available, for parsing scopes from strings
- The Globus SDK does not support using ``Scope`` in all of the locations where
``MutableScope`` is supported -- Scope objects must be stringified for use
- ``Scope`` objects define a ``__contains__`` method, allowing one to check if one scope
properly contains another

.. note::

The scope trees produced by parsing represent prospective consent structures as
would be produced by a single authentication flow in Auth. No external APIs (e.g.,
the Get Consents API) are used to validate or mutate the data.

Reference
---------

.. autoclass:: Scope
:members:
:member-order: bysource
:special-members:

.. autoclass:: ScopeParseError

.. autoclass:: ScopeCycleError
6 changes: 0 additions & 6 deletions src/globus_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

if t.TYPE_CHECKING:
from globus_sdk.scopes import MutableScope
from globus_sdk.scopes.scope_definition import Scope

# these types are aliases meant for internal use
IntLike = t.Union[int, str]
Expand All @@ -21,15 +20,10 @@

ScopeCollectionType = t.Union[
str,
"Scope",
"MutableScope",
t.Iterable[str],
t.Iterable["Scope"],
t.Iterable["MutableScope"],
t.Iterable[t.Union[str, "Scope", "MutableScope"]],
t.Iterable[t.Union[str, "Scope"]],
t.Iterable[t.Union[str, "MutableScope"]],
t.Iterable[t.Union["Scope", "MutableScope"]],
]


Expand Down
8 changes: 8 additions & 0 deletions src/globus_sdk/experimental/scope_parser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .errors import ScopeCycleError, ScopeParseError
from .scope_definition import Scope

__all__ = (
"Scope",
"ScopeParseError",
"ScopeCycleError",
)
99 changes: 99 additions & 0 deletions src/globus_sdk/experimental/scope_parser/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from __future__ import annotations

import sys
import timeit
import warnings

from ._parser import parse_scope_graph

warnings.warn(
"This is a test module for scope parsing. It is not intended for public use.",
stacklevel=1,
)


def timeit_test() -> None:
for size, num_iterations, style in (
(10, 1000, "deep"),
(100, 1000, "deep"),
(1000, 1000, "deep"),
(2000, 100, "deep"),
(3000, 100, "deep"),
(4000, 100, "deep"),
(5000, 100, "deep"),
(5000, 1000, "wide"),
(10000, 1000, "wide"),
):
if style == "deep":
setup = f"""\
from globus_sdk.experimental.scope_parser import Scope
big_scope = ""
for i in range({size}):
big_scope += f"foo{{i}}["
big_scope += "bar"
for _ in range({size}):
big_scope += "]"
"""
elif style == "wide":
setup = f"""\
from globus_sdk.experimental.scope_parser import Scope
big_scope = ""
for i in range({size}):
big_scope += f"foo{{i}} "
"""
else:
raise NotImplementedError

timer = timeit.Timer("Scope.parse(big_scope)", setup=setup)

raw_timings = timer.repeat(repeat=5, number=num_iterations)
best, worst, average, variance = _stats(raw_timings)
if style == "deep":
print(f"{num_iterations} runs on a deep scope, depth={size}")
elif style == "wide":
print(f"{num_iterations} runs on a wide scope, width={size}")
else:
raise NotImplementedError
print(f" best={best} worst={worst} average={average} variance={variance}")
print(f" normalized best={best / num_iterations}")
print()
print("The most informative stat over these timings is the min timing (best).")
print("Normed best is best/iterations.")
print(
"Max timing (worst) and dispersion (variance vis-a-vis average) indicate "
"how consistent the results are, but are not a report of speed."
)


def _stats(timing_data: list[float]) -> tuple[float, float, float, float]:
best = min(timing_data)
worst = max(timing_data)
average = sum(timing_data) / len(timing_data)
variance = sum((x - average) ** 2 for x in timing_data) / len(timing_data)
return best, worst, average, variance


def parse_test(scope_string: str) -> None:
parsed_graph = parse_scope_graph(scope_string)
print(
"top level scopes:",
", ".join([name for name, _optional in parsed_graph.top_level_scopes]),
)
print(parsed_graph)


def main() -> None:
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
print("This script supports two usage patterns:")
print(" python -m globus_sdk.experimental.scope_parser SCOPE_STRING")
print(" python -m globus_sdk.experimental.scope_parser --timeit")
sys.exit(0)

print()
if sys.argv[1] == "--timeit":
timeit_test()
else:
parse_test(sys.argv[1])


main()
Loading

0 comments on commit e70cd23

Please sign in to comment.