From a1212a8a96232b264e56650516740db1737dfd7f Mon Sep 17 00:00:00 2001 From: "A. Karl Kornel" Date: Mon, 5 Jul 2021 16:50:50 -0700 Subject: [PATCH] account: Cleanup in code docs --- src/stanford/mais/account/__init__.py | 32 ++++++++++----------- src/stanford/mais/account/account.py | 40 ++++++++++++++++++--------- src/stanford/mais/account/service.py | 12 +++++--- src/stanford/mais/account/validate.py | 35 ++++++++++++++++------- 4 files changed, 76 insertions(+), 43 deletions(-) diff --git a/src/stanford/mais/account/__init__.py b/src/stanford/mais/account/__init__.py index 0f6c18e..2c9bf25 100644 --- a/src/stanford/mais/account/__init__.py +++ b/src/stanford/mais/account/__init__.py @@ -50,7 +50,7 @@ class AccountClient(): Once you have a client instantiated, you can use :meth:`get` to fetch an account. For your convenience, instances of this class also implement - `__getitem__`, so instead of doing… + ``__getitem__``, so instead of doing… .. code-block:: default @@ -70,7 +70,7 @@ class AccountClient(): .. code-block:: default aclient = AccountClient(...) - lelandjr_exists = (True if 'lelandjr' in client else False) + lelandjr_exists = (True if 'lelandjr' in aclient else False) Through the use of caching, if you then decide to fetch the account after confirming its existance, the entry will be served from cache @@ -84,7 +84,7 @@ class AccountClient(): client: stanford.mais.client.MAISClient """A :class:`~stanford.mais.client.MAISClient` instance. - This configures the API endpoint (accessed via `client.urls['account']`) + This configures the API endpoint (accessed via ``client.urls['account']``) and client key/cert to use. It must be provided when calling the class constructor. @@ -100,8 +100,9 @@ class AccountClient(): Normally, this should not be set, and a new session will be requested from the client. But if you would like to use a custom - :class:`requests.Session` instance (such as for mock testing), provide it - as the ``custom_session`` and it will be used for all requests. + :class:`~requests.Session` instance (such as for mock testing), provide it + to the constructor as the ``custom_session`` and it will be used for all + requests. """ _cache: MutableMapping[str, 'Account'] = dataclasses.field(repr=False, default_factory=dict) @@ -141,12 +142,11 @@ def get( ) -> 'Account': """Fetch an Account. - This is a convenience wrapper around :meth:`Account.get`, which - provides this client as the `client`. All other parameters provided - are passed through to :meth:`Account.get`, and the resulting instance - is returned. + This is a convenience wrapper around :meth:`Account.get`. All other + parameters provided are passed through to :meth:`~Account.get`, and the + resulting instance is returned. - Refer to :meth:`~Account.get` for details on parameters, exceptions, + Refer to :meth:`Account.get` for details on parameters, exceptions, etc.. """ return Account.get( @@ -186,7 +186,7 @@ def __contains__( def only_active( self, ) -> 'AccountView': - """Create a modified ``AccountClient`` that can only see active + """Create a modified :class:``AccountClient`` that can only see active accounts. The returned client instance has been modified so that @@ -220,7 +220,7 @@ def only_active( def only_inactive( self, ) -> 'AccountView': - """Create a modified ``AccountClient`` that can only see inactive + """Create a modified :class:``AccountClient`` that can only see inactive accounts. The returned client instance has been modified so that @@ -242,8 +242,8 @@ def only_inactive( def only_people( self, ) -> 'AccountView': - """Create a modified ``AccountClient`` that can only see accounts of - people. + """Create a modified :class:``AccountClient`` that can only see + accounts of people. The returned client instance has been modified so that :meth:`~AccountClient.get` only returns the accounts of people. If you @@ -287,8 +287,8 @@ def only_people( def only_functional( self, ) -> 'AccountView': - """Create a modified ``AccountClient`` that can only see functional - accounts. + """Create a modified :class:``AccountClient`` that can only see + functional accounts. The returned client instance has been modified so that :meth:`~AccountClient.get` only returns functional accounts. If you diff --git a/src/stanford/mais/account/account.py b/src/stanford/mais/account/account.py index 8369393..975c1a9 100644 --- a/src/stanford/mais/account/account.py +++ b/src/stanford/mais/account/account.py @@ -137,6 +137,18 @@ class Account(): This contains the services currently associated with the account. Each service has a service name, and the value is a dataclass which contains status and service-specific information. + + It is a :class:`~collections.abc.Mapping` of :class:`str` (the service + name) to subclasses of + :class:`~stanford.mais.account.service.AccountService`. To learn the key + name for each service, refer to the documentation for that subclass. + + .. note:: + From time to time, new services are defined. Those services will + **not** appear in this mapping until a software update is released, + defining a new subclass for that service. If you need to access the + service's data before that time, refer to the `services` key in the + parsed JSON. """ last_updated: datetime.datetime @@ -155,10 +167,10 @@ class Account(): separator. * If the account is for a person (that is, `type` is "self"), then this - string will be `person/` followed by the RegID of the person. + string will be ``person/`` followed by the RegID of the person. * If the account is for a functional account (`type` is "functional"), - then this string will be `organization/` followed by the RegID of the + then this string will be ``organization/`` followed by the RegID of the Org which owns the functional account. * **statusDate**: The date when this account was last changed, in the @@ -197,27 +209,29 @@ def get( for the same input will return the same result instance, thanks to the use of a cache. - *WARNING*: This will looks up accounts of all types, both accounts for - people and also functional accounts. Check the :meth:`type` before - assuming you are working with a SUNetID. + .. warning:: + This will looks up accounts of all types, both accounts for + people and also functional accounts. Check :meth:`is_person` before + assuming you are working with a SUNetID. - *WARNING*: This memoization means that, should an account change status - after lookup, that status change will not be noticed until after the - module is reloaded. That means this code should *not* be used by - long-running client code. + .. warning:: + This memoization means that, should an account change status + after lookup, that status change will not be noticed until after the + module is reloaded. That means this code should *not* be used by + long-running client code. :param client: An :class:`AccountClient` representing our API endpoint. - :param sunetid: The to look up. This must be an actual id, not an alias. + :param sunetid: The ID to look up. This must be an actual id, not an alias. :raises ChildProcessError: Something went wrong on the server side (a 400 or 500 error was returned). - :raises IndexError: The input is too long. Maybe the input is an alias? - - :raises KeyError: The given SUNetID does not exist. + :raises KeyError: The given ID does not exist. Maybe it was an alias? :raises PermissionError: You did not use a valid certificate, or do not have permissions to perform the operation. + :raises UnicodeEncodeError: The ID you provided included non-ASCII characters. + :raises ValueError: The input contains non-ASCII characters. :raises requests.Timeout: The MaIS Workgroup API did not respond in time. diff --git a/src/stanford/mais/account/service.py b/src/stanford/mais/account/service.py index 53ea095..0bc5a4d 100644 --- a/src/stanford/mais/account/service.py +++ b/src/stanford/mais/account/service.py @@ -244,7 +244,7 @@ class AccountServiceSEAS(AccountService): """``seas`` service for an Account. The "Stanford Electronic Alias Service". If this service is active, then - the account has an associated `@stanford.edu` email address, even if they + the account has an associated *@stanford.edu* email address, even if they don't have a Stanford email box. .. note:: @@ -427,7 +427,7 @@ def _from_json( class AccountServiceLeland(AccountService): """``leland`` service for an Account. - This represents the Stanford `Shared Computing`_ environment, originally + This represents the Stanford `Shared Computing`_ environment, once known as `Leland`_ and known today as `FarmShare`_. If active, users are able to log in to FarmShare. @@ -517,12 +517,16 @@ class AccountServiceAFS(AccountService): The path to the account's home directory. .. note:: - This is used as the account's `homeDirectory` in LDAP. As such, you + This is used as the account's ``homeDirectory`` in LDAP. As such, you will probably want to override it. .. note:: This setting assumes that AFS is mounted at path ``/afs`` on a ssytem. - This is normally, but not always, the case. + This is normally, but not always, the case. This setting also assumes + that your system has an up-to-date copy of the + `CellServDB `_ + file, which should be the case if you are using a packaged OpenAFS + client. """ @classmethod diff --git a/src/stanford/mais/account/validate.py b/src/stanford/mais/account/validate.py index ea3eb7a..14fcd15 100644 --- a/src/stanford/mais/account/validate.py +++ b/src/stanford/mais/account/validate.py @@ -40,6 +40,11 @@ class AccountValidationResults(NamedTuple): This class contains the results of an account validation, called via :func:`validate`. + + .. note:: + Many of these properties are collections. That means, if you want to + use things like subscripting, you will want to convert to a + more-specific type (such as :class:`list` before using it). """ raw: Optional[str] @@ -55,28 +60,28 @@ class AccountValidationResults(NamedTuple): collection. Otherwise, this will be the original list/set/tuple that was provided to :func:`validate`, but made into a set. - The set union of `full`, `base`, `inactive`, and `unknown` should equal + The set union of `full`, `base`, `inactive`, and `unknown` is equal to this list. """ full: Collection[str] """ - The list of active, full (or full-sponsored) SUNetIDs found in `raw_set`. + The set of active, full (or full-sponsored) SUNetIDs found in `raw_set`. """ base: Collection[str] """ - The list of active, base (or base-sponsored) SUNetIDs found in `raw_set`. + The set of active, base (or base-sponsored) SUNetIDs found in `raw_set`. """ inactive: Collection[str] """ - The list of inactive SUNetIDs found in `raw_set`. + The set of inactive SUNetIDs found in `raw_set`. """ unknown: Collection[str] """ - The list of entries from `raw_set` that are not SUNetIDs. + The set of entries from `raw_set` that are not SUNetIDs. """ @functools.singledispatch @@ -84,8 +89,8 @@ def validate( raw: str, client: stanford.mais.account.AccountClient, ) -> AccountValidationResults: - """Given a list of SUNetIDs, in string or collection form, validate and - check status. + """Given a list of SUNetIDs; as a string or a list, tuple, or set; validate + and check status. This takes a list of SUNetIDs, and returns a list of SUNetIDs which have been checked against the Accounts API for both activity and service level. @@ -93,9 +98,16 @@ def validate( full-sponsored), active base (or base-sponsored), or inactive. If the input is a string, then the input string may be separated by commas, - and/or any kind of whitespace. If the input is some other form of - collection (list, tuple, or set), the function assumes that all whitespace - etc. have been removed. + and/or whitespace, (where "whitespace" is a space, newline/linefeed, form + feed, carriage return, tab/horizontal tab, or vertical tab character). If + the input is a list, tuple, or set; the function assumes that all + whitespace etc. have been removed. + + .. note:: + "List, tuple, or set" is used instead of the generic "collection" + because a :class:`str` is also a collection (of other :class:`str`). + See `typing issue #256 `_ + for the discussion around this issue. This is designed to catch most exceptions. Exceptions related to validation (for example, attempting to validate an obviously-invalid @@ -180,6 +192,9 @@ def _( unknown.add(sunetid) continue + # Overwrite the input SUNetID with the normalized one. + sunetid = account.sunetid + # Next, catch inactives if account.is_active is False: debug(f"Account {sunetid} NOT active.")