aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas "Cakeisalie5" Touhey <thomas@touhey.fr>2018-10-26 21:53:32 +0200
committerThomas "Cakeisalie5" Touhey <thomas@touhey.fr>2018-10-26 21:53:32 +0200
commitdcc2b5509386aac74d02563abd51818d15e7e22e (patch)
tree90bd3704f0822410afaa5e6dac6d0d240d774ed5
parenta4f7679dcef512e5a80bddab4591894ed85c51dc (diff)
Corrected the manager's session management and session's get_page.
-rw-r--r--docs/index.rst1
-rw-r--r--docs/intranet/adherents.rst3
-rw-r--r--docs/status.rst62
-rwxr-xr-xsgdfi/__init__.py2
-rwxr-xr-xsgdfi/_decode.py16
-rwxr-xr-xsgdfi/_intranet.py115
-rwxr-xr-xsgdfi/_manager.py276
-rwxr-xr-xsgdfi/_repr.py11
8 files changed, 375 insertions, 111 deletions
diff --git a/docs/index.rst b/docs/index.rst
index 4df2c38..e5b8730 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -14,6 +14,7 @@ provide machine access to the websites in this digital environment.
usage/index
environment
+ todo
.. _SGDF: https://www.sgdf.fr/
.. _Scoutisme Français: https://www.scoutisme-francais.fr/
diff --git a/docs/intranet/adherents.rst b/docs/intranet/adherents.rst
index fc0afce..fe5e454 100644
--- a/docs/intranet/adherents.rst
+++ b/docs/intranet/adherents.rst
@@ -70,3 +70,6 @@ Paths
``/Competences/ResumeCompetencePersonne.aspx``
A form for giving one's skills.
+
+``/WebServices/AutoCompleteCodePostalVille.asmx/GetCompletionList``
+ A web service for getting data
diff --git a/docs/status.rst b/docs/status.rst
new file mode 100644
index 0000000..53e4ad4
--- /dev/null
+++ b/docs/status.rst
@@ -0,0 +1,62 @@
+Project status
+==============
+
+The project is currently actively in development.
+
+To-do list
+----------
+
+General observations:
+
+- explicit the “undefined” value and add a “deleted” value for representations.
+- add “unique” and “rare” fields for managing the knowledges.
+
+Questions
+---------
+
+There are a lot of questions that, answered, could help to the development of
+the project. Don't hesitate to answer to any if you know how to!
+
+- In the case of several parents for a child, who is the legal representative?
+ Any of the parents? All of the parents?
+- What is the “responsabilité chef de famille” and is it really required?
+- Is it possible to join the organization for several years in one go?
+- Shall one be a member of the organization **before** holding a position for
+ which they have been elected, e.g. treasurer or secretary?
+- What are the permissions associated with the functions on the intranet?
+- What are “private” codes for structures and adherents?
+- Are invitations valid for several seasons in one go?
+- What does “ff” mean in functions such as “ff trésorier de groupe” (code
+ 391), “ff responsable de groupe” (code 399) or “ff trésorier territorial”
+ (code 691)?
+- What does the “éclaireur” (code 150) function corresponds to?
+- What does the “membre associé” (codes 180L, 180N and 180T) correspond to?
+ (I guess L, N and T mean Local, National and Territorial)
+- Is the organization's pilot group (not the delegated group) on the intranet?
+- How does the delegation system work (combobox/select on the left of most
+ pages beneath the name, and in the extractions)?
+- Is it technically possible to be nominated at two functions, e.g. head for
+ a unit and leader in another?
+- Can someone get several adherent codes?
+- Why is the hemisphere given on structure pages?
+- Can a unit depend on anything else than a group?
+- Does a group always have a place (“local”) attributed to it?
+- When was the intranet launched?
+
+Tickets
+-------
+
+With this project, several tickets have been created on
+`support.sgdf.fr <https://support.sgdf.fr/>`_:
+
+- `Ticket #999
+ <https://support.sgdf.fr/issues/8b4d82c2c001e03b5df5a1b8d858f081616f0bf2/>`_:
+ asking several questions for SDY/SGDFi (deleted and unanswered).
+- `Ticket #1311
+ <https://support.sgdf.fr/issues/78376ea2a985b38f7bc5fbd1173672f1061873b4/>`_:
+ publicly accessible web service divulgating potentially sensitive
+ information.
+- `Ticket #1370
+ <https://support.sgdf.fr/issues/fd53a610c1bd0d82bbecbee88576863140c4a18f/>`_:
+ form for getting a mail when the adherent code has been forgotten cannot
+ be validated because of a missing parameter.
diff --git a/sgdfi/__init__.py b/sgdfi/__init__.py
index db40661..0f59164 100755
--- a/sgdfi/__init__.py
+++ b/sgdfi/__init__.py
@@ -6,7 +6,7 @@
""" SGDFi allows you to interact with SGDF's intranet. """
from ._version import version
-from ._manager import Manager, ALL
+from ._manager import Manager
from ._repr import Structure, Adherent, RallyRegistration, Camp, Place, \
Operation, OperationType, Function, Event, StructureType, \
StructureStatus, AllocationsRegime
diff --git a/sgdfi/_decode.py b/sgdfi/_decode.py
index 5dd8872..628a65e 100755
--- a/sgdfi/_decode.py
+++ b/sgdfi/_decode.py
@@ -155,14 +155,12 @@ class Decoder:
if hint is not None and not _validsym(hint):
raise ValueError("invalid hint format")
- # FIXME: save?
# FIXME: in case of a stream, we want to seek back to where we are.
- if self.__save:
- self.save_dump(content, hint, type)
-
if hint == 'raw':
- return content
+ if isinstance(inp, _IOBase):
+ inp = inp.read()
+ return inp
# FIXME: check if the document is actually an error?
# FIXME: get request data somehow, such as iid?
@@ -183,7 +181,7 @@ class Decoder:
elif type == 'application/x-microsoft-ajax':
# The input might be a stream.
- if isinstance(inp, _RawIOBase):
+ if isinstance(inp, _IOBase):
if _type(inp.read(0)) == bytes:
inp = _TextIOWrapper(inp)
@@ -243,7 +241,7 @@ class Decoder:
elif type == 'text/csv':
# The input might be of different type, we want a text stream.
- if isinstance(inp, _RawIOBase):
+ if isinstance(inp, _IOBase):
if _type(inp.read(0)) == bytes:
inp = _TextIOWrapper(inp)
elif _type(inp) == bytes:
@@ -271,7 +269,7 @@ class Decoder:
elif type == 'application/vnd.ms-excel': # XLS document.
# The input might be of different type, we want a text stream.
- if isinstance(inp, _RawIOBase) and _type(inp.read(0)) == bytes:
+ if isinstance(inp, _IOBase) and _type(inp.read(0)) == bytes:
pass # ok!
elif _type(inp) == bytes:
inp = _BytesIO(inp)
@@ -319,7 +317,7 @@ class Decoder:
elif type == 'application/json':
# The input might be of different type, we want a text stream.
- if isinstance(inp, _RawIOBase):
+ if isinstance(inp, _IOBase):
if _type(inp.read(0)) == bytes:
inp = _TextIOWrapper(inp)
elif _type(inp) == bytes:
diff --git a/sgdfi/_intranet.py b/sgdfi/_intranet.py
index 1d76a9e..17fba0b 100755
--- a/sgdfi/_intranet.py
+++ b/sgdfi/_intranet.py
@@ -78,34 +78,59 @@ class IntranetSession:
return self.__pw
+ @pw.setter
+ def pw(self, value):
+ # Use another password instead.
+
+ if type(value) != str:
+ raise ValueError("expected a string")
+
+ new_session = _Session()
+ self.login(pw = value, session = new_session)
+ self.__pw = value
+ self.__session = new_session
+
@property
def password(self):
""" Alias for `pw`. """
return self.pw
+ @password.setter
+ def password(self, value):
+ self.pw = value
+
# ---
# Base utilities.
# ---
METHOD_BASIC = 1
- METHOD_FORM = 2
- METHOD_AJAX = 3
+ METHOD_POST = 2
+ METHOD_FORM = 3
+ METHOD_AJAX = 4
- def get_page(self, path, args = {}, method = METHOD_BASIC, hint = None):
+ def get_page(self, path, args = {}, method = METHOD_BASIC, hint = None,
+ session = None):
""" Gather a page:
- as a normal page using GET (`METHOD_BASIC`).
- - as a form using POST (`METHOD_FORM`).
+ - as a POST page (`METHOD_POST`).
+ - as a form using POST and predefined fields (`METHOD_FORM`).
- as a page fragment using POST with AJAX (`METHOD_AJAX`).
The hint is for decoding the content, according to the
- context. """
+ context. The `session` argument is `None` if we shall use the
+ object's requests Session object, or the requests Session object
+ to use. """
- if not method in (self.METHOD_BASIC, self.METHOD_FORM, \
- self.METHOD_AJAX):
+ if not method in (self.METHOD_BASIC, self.METHOD_POST,
+ self.METHOD_FORM, self.METHOD_AJAX):
raise ValueError("invalid method")
+ if session is None:
+ session = self.__session
+ elif not isinstance(session, _Session):
+ raise ValueError("expected a Session object")
- mname = ("basic", "form", "ajax")[method - 1]
+ mname = ("basic", "post", "form", "ajax")[method - 1]
self.__log(f"Get a page using {mname} method")
def ret(r):
@@ -135,28 +160,29 @@ class IntranetSession:
if ct == 'text/plain':
self.__log(f"Decoding AJAX response{htext}.")
- return self.feed(r.text, 'ajax', hint)
+ return self.__mgr.feed(r.text, 'application/x-microsoft-ajax',
+ hint)
elif ct == 'text/csv':
self.__log(f"Decoding CSV content{htext}.")
- return self.feed(r.text, 'csv', hint)
+ return self.__mgr.feed(r.text, 'text/csv', hint)
elif ct == 'text/html':
self.__log(f"Decoding HTML content{htext}.")
- return self.feed(r.text, 'html', hint)
+ return self.__mgr.feed(r.text, 'text/html', hint)
elif ct == 'text/xml':
self.__log(f"Decoding XML content{htext}.")
- return self.feed(r.text, 'xml', hint)
+ return self.__mgr.feed(r.text, 'text/xml', hint)
elif ct == 'application/json':
self.__log(f"Decoding JSON content{htext}.")
- return self.feed(r.text, 'json', hint)
+ return self.__mgr.feed(r.text, 'application/json', hint)
self.__log(f"Unmanaged Content-Type {repr(ct)}")
self.__log(f"Falling back on HTML decoding.")
- return self.feed(r.text, 'html', hint)
+ return self.__mgr.feed(r.text, 'text/html', hint)
# Définition des headers.
@@ -170,39 +196,41 @@ class IntranetSession:
}
# ---
- # Récupération de la version GET de la page.
+ # Récupération de la version GET de la page pour les champs
+ # prédéfinis.
# ---
- # Récupération à proprement parler.
+ df = {}
- tm = _monotime()
- r = self.__session.get(self.__base + path, headers = headers)
- self.__log(f"Page GET request delay: {_monotime() - tm}s")
+ if method != self.METHOD_POST:
+ # Récupération à proprement parler.
- # Si la méthode est un GET basique, on peut s'arrêter là.
+ tm = _monotime()
+ r = self.__session.get(self.__base + path, headers = headers)
+ self.__log(f"Page GET request delay: {_monotime() - tm}s")
- if method == self.METHOD_BASIC:
- return ret(r)
+ # Si la méthode est un GET basique, on peut s'arrêter là.
- # Décodage du contenu récupéré et récupération des valeurs par
- # défaut de l'ensemble des champs, dont `__EVENTVALIDATION`
- # qui sert de cookie CSRF.
+ if method == self.METHOD_BASIC:
+ return ret(r)
- df = {}
+ # Décodage du contenu récupéré et récupération des valeurs par
+ # défaut de l'ensemble des champs, dont `__EVENTVALIDATION`
+ # qui sert de cookie CSRF.
- it = _BeautifulSoup(r.text, 'lxml').find_all('input',
- recursive = True)
- for elt in it:
- try:
- name = elt['name']
- except:
- continue
- try:
- value = elt['value']
- except:
- value = ''
+ it = _BeautifulSoup(r.text, 'lxml').find_all('input',
+ recursive = True)
+ for elt in it:
+ try:
+ name = elt['name']
+ except:
+ continue
+ try:
+ value = elt['value']
+ except:
+ value = ''
- df[name] = value
+ df[name] = value
# ---
# Préparation des paramètres POST (payload).
@@ -264,19 +292,24 @@ class IntranetSession:
# Usage.
# ---
- def login(self):
+ def login(self, pw = None, session = None):
""" Gather session data (cookies) within the internal session. """
+ if pw is None:
+ pw = self.__pw
+
try:
temp = self.get_page('/Default.aspx', {
'ctl00': {
'MainContent': {
'login': self.__user,
- 'password': self.__pw}}},
+ 'password': pw}}},
method = self.METHOD_FORM,
- hint = 'ignore')
+ hint = 'ignore',
+ session = session)
except RedirectError:
# We have successfully been redirected!
+
return
# FIXME: the credentials were invalid.
diff --git a/sgdfi/_manager.py b/sgdfi/_manager.py
index e7846e2..8efabe8 100755
--- a/sgdfi/_manager.py
+++ b/sgdfi/_manager.py
@@ -10,24 +10,36 @@ import os.path as _path
from os import makedirs as _makedirs, open as _open, fdopen as _fdopen, \
O_WRONLY as _O_WRONLY, O_CREAT as _O_CREAT, O_EXCL as _O_EXCL
+from io import RawIOBase as _RawIOBase, TextIOWrapper as _TextIOWrapper
from sys import stdout as _stdout
+from string import ascii_letters as _ascii_letters
from shutil import copyfile as _copyfile
from datetime import datetime as _datetime, date as _date
from appdirs import user_cache_dir as _user_cache_dir
-from ._repr import Structure as _Structure, Adherent as _Adherent, \
- Place as _Place, RallyRegistration as _RallyRegistration, Camp as _Camp, \
+from ._repr import Base as _Base, Structure as _Structure, \
+ Adherent as _Adherent, Place as _Place, \
+ RallyRegistration as _RallyRegistration, Camp as _Camp, \
Operation as _Operation, FunctionRawData as _FunctionRawData
-from ._intranet import IntranetSession as _Session
+from ._intranet import IntranetSession as _IntranetSession
from ._decode import Decoder as _Decoder
-__all__ = ["Manager", "ALL"]
+__all__ = ["Manager"]
-class AllType:
- pass
+def _isany(value):
+ """ Check if a value can be considered as any. """
-ALL = AllType()
+ flt = lambda x: ''.join(c for c in x.casefold() if c in _ascii_letters)
+ return bool(value is any or 'any' in map(filt, (value.__name__,
+ value.__class__.__name__)))
+
+def _isall(value):
+ """ Check if a value can be considered as all. """
+
+ flt = lambda x: ''.join(c for c in x.casefold() if c in _ascii_letters)
+ return bool(value is all or 'all' in map(filt, (value.__name__,
+ value.__class__.__name__)))
# ---
# Utilities.
@@ -76,7 +88,82 @@ def _validsym(s):
# Management classes.
# ---
-class _StructureManager:
+class _IntranetSessionsManager:
+ """ Intranet sessions manager. """
+
+ def __init__(self, mgr):
+ self.__mgr = mgr
+ self.__sessions = []
+ pass
+
+ def __getitem__(self, key):
+ if _isany(key):
+ if self.__sessions:
+ return self.__sessions[0]
+ raise KeyError("no session available")
+
+ try:
+ s = next(s for s in self.__sessions if s.user == key)
+ except StopIteration:
+ # Try and find it using an index.
+
+ try:
+ i = int(key)
+ except ValueError:
+ raise KeyError("invalid key format")
+
+ return self.__sessions[key]
+
+ # Return the found session.
+
+ return s
+
+ def login(self, user, pw):
+ """ Add intranet credentials. """
+
+ try:
+ idx = next(i for i, s in enumerate(self.__sessions) \
+ if s.user == user)
+ except StopIteration:
+ # No session found, we shall make a new one.
+
+ s = None
+ else:
+ s = self.__sessions.pop(idx)
+ self.__sessions.append(s)
+
+ if s is None:
+ s = _IntranetSession(self.__mgr, user = user, pw = pw)
+ self.__sessions.append(s)
+ return
+
+ if s.pw == pw:
+ return
+
+ # Different password than the one we know, we should try to
+ # use the new password instead.
+
+ s.pw = pw
+
+class _SessionsManager:
+ """ Sessions manager. """
+
+ def __init__(self, mgr):
+ self.__mgr = mgr
+ self.__i = _IntranetSessionsManager(mgr)
+
+ @property
+ def intranet(self):
+ """ The intranet sessions manager. """
+
+ return self.__i
+
+ def login(self, user, pw):
+ """ Add centralized (intranet) credentials. """
+
+ self.intranet.login(user, pw)
+
+class _StructuresManager:
""" Structure access class. """
def __init__(self, mgr):
@@ -85,7 +172,7 @@ class _StructureManager:
def __getitem__(self, key):
raise NotImplementedError
-class _AdherentManager:
+class _AdherentsManager:
""" Adherent access class. """
def __init__(self, mgr):
@@ -94,7 +181,7 @@ class _AdherentManager:
def __getitem__(self, key):
raise NotImplementedError
-class _RallyRegistrationManager:
+class _RallyRegistrationsManager:
""" Rally registration access class. """
def __init__(self, mgr):
@@ -103,7 +190,7 @@ class _RallyRegistrationManager:
def __getitem__(self, key):
raise NotImplementedError
-class _CampManager:
+class _CampsManager:
""" Camp access class. """
def __init__(self, mgr):
@@ -112,7 +199,7 @@ class _CampManager:
def __getitem__(self, key):
raise NotImplementedError
-class _PlaceManager:
+class _PlacesManager:
""" Place access class. """
def __init__(self, mgr):
@@ -121,7 +208,7 @@ class _PlaceManager:
def __getitem__(self, key):
raise NotImplementedError
-class _EventStructureManager:
+class _StructureEventsManager:
""" Structure event access class. """
def __init__(self, mgr, st):
@@ -147,25 +234,16 @@ class _EventStructureManager:
raise KeyError("Should only use slice or date key.")
-class _EventManager:
+class _EventsManager:
""" Event access class. """
def __init__(self, mgr):
self.__mgr = mgr
def __getitem__(self, key):
- if key is ALL:
- return _EventStructureManager(self.__mgr, ALL)
-
- raise NotImplementedError
-
-class _OperationManager:
- """ Operation access class. """
-
- def __init__(self, mgr):
- self.__mgr = mgr
+ if _isall(key):
+ return _StructureEventsManager(self.__mgr, all)
- def __getitem__(self, key):
raise NotImplementedError
# ---
@@ -180,7 +258,7 @@ class Manager(_Decoder):
self.__save = save
self.__folder = folder
- self.__sessions = []
+ self.__sessions = _SessionsManager(self)
# Work out the folder, and make sure it exists.
@@ -194,13 +272,18 @@ class Manager(_Decoder):
# Make the data manager.
- self.__sts = _StructureManager(self)
- self.__ads = _AdherentManager(self)
- self.__rrs = _RallyRegistrationManager(self)
- self.__cps = _CampManager(self)
- self.__pls = _PlaceManager(self)
- self.__evs = _EventManager(self)
- self.__ops = _OperationManager(self)
+ self.__sts = _StructuresManager(self)
+ self.__ads = _AdherentsManager(self)
+ self.__rrs = _RallyRegistrationsManager(self)
+ self.__cps = _CampsManager(self)
+ self.__pls = _PlacesManager(self)
+ self.__evs = _EventsManager(self)
+
+ @property
+ def sessions(self):
+ """ Managed sessions. """
+
+ return self.__sessions
@property
def structures(self):
@@ -238,12 +321,6 @@ class Manager(_Decoder):
return self.__evs
- @property
- def operations(self):
- """ The operations manager. """
-
- return self.__ops
-
# ---
# Dump management.
# ---
@@ -268,32 +345,67 @@ class Manager(_Decoder):
return self.feed(open(path, "rb"), type = type, **kwargs)
- def load_dump(self, time, id = 0):
+ def load_dump(self, time, id = None):
""" Read from a saved dump (with headers). """
- if isinstance(time, _datetime):
- time = time.timestamp()
- elif isinstance(time, _date):
- time = _datetime.fromordinal(time.toordinal()).timestamp()
+ # Args format:
+ # - `mgr.load_dump(<string>)`
+ # - `mgr.load_dump(_datetime(2018, 10, 26, 21, 7, 20))`
+ # - `mgr.load_dump(_datetime(2018, 10, 26, 21, 7, 20), 5)`
+ #
+ # Out of these, a filename is deduced.
+
+ if type(time) == str and id is None:
+ fname = time
+ if not fname.endswith('.dump'):
+ fname += '.dump'
+ elif isinstance(time, _datetime) or isinstance(time, _date):
+ if isinstance(time, _datetime):
+ time = time.timestamp()
+ else:
+ time = _datetime.fromordinal(time.toordinal()).timestamp()
+
+ if id is None:
+ id = 0
+ elif type(id) == str:
+ try:
+ id = int(id)
+ except ValueError:
+ raise ValueError("expected an integer as an identifier")
+ elif type(id) != int:
+ raise ValueError("expected an integer as an identifier")
+ if id < 0 or id > 99:
+ raise ValueError("expected an integer between 0 and 99.")
+
+ fname = f"{int(time)}{id:02d}.dump"
else:
- time = int(time)
+ raise ValueError("unknown args format")
+
+ # Load the file.
kwargs = {}
- fp = open(_path.join(self.__folder, f"{time}{id:02d}.dump"), 'rb')
+ fp = open(_path.join(self.__folder, fname), 'rb')
while True:
line = fp.readline().decode('UTF-8').splitlines()[0]
if not line:
break
- kw, *value = line.split(':')
+ kw, *value = line.split(': ')
if not _validsym(kw) or kw == 'inp' or not value:
continue
- value = ':'.join(value)
+ value = ': '.join(value)
kwargs[kw] = value
- return self.decode(fp, **kwargs)
+ st = fp
+ if kwargs.get('ctnt') == 'text':
+ del kwargs['ctnt']
+ st = _TextIOWrapper(fp)
+
+ result = self.feed(st, **kwargs)
+ fp.close()
+ return result
def save_dump(self, inp, time = _datetime.now(), **kwargs):
""" Save a file. """
@@ -305,6 +417,15 @@ class Manager(_Decoder):
else:
time = int(time)
+ # Sanitize the keywords.
+ # `ctnt` is used for saying that the file is to be decoded as text
+ # instead of bytes.
+
+ try:
+ del kwargs["ctnt"]
+ except KeyError:
+ pass
+
# The save process will be slightly different if we have a bytes
# or a text content.
@@ -319,26 +440,32 @@ class Manager(_Decoder):
else:
raise ValueError("Could not save this content")
+ if not 'b':
+ kwargs['ctnt'] = 'text'
+
# Open the file.
for idx in range(100):
try:
- filename = f"{time}{idx:02d}.dump"
+ filename = f"{int(time)}{idx:02d}.dump"
fd = _open(_path.join(self.__folder, filename),
_O_WRONLY | _O_CREAT | _O_EXCL)
except FileExistsError:
continue
- f = _fdopen(fd, fdmode)
+ f = _fdopen(fd, mode)
break
else:
raise ValueError("Could not find a suitable index…")
# Write the headers.
- for kw, value in kwargs.items():
- print(f"{kw}: {value}", end = '\r\n', file = f)
- print("", end = '\r\n', file = f)
+ hdr = "\r\n".join(f"{kw}: {value}" for kw, value in kwargs.items())
+ hdr += "\r\n\r\n"
+
+ if 'b' in mode:
+ hdr = hdr.encode('utf-8')
+ f.write(hdr)
# Then write the content.
@@ -347,6 +474,7 @@ class Manager(_Decoder):
else:
f.write(inp)
+ f.close()
return time, idx
# ---
@@ -354,9 +482,45 @@ class Manager(_Decoder):
# ---
def login(self, user, pw):
- """ Add credentials to the mix. """
+ """ Add centralized credentials to the mix. """
- raise NotImplementedError
+ self.sessions.login(user, pw)
+
+ # ---
+ # Knowledge management.
+ # ---
+
+ def feed(self, inp, type = 'text/html', hint = None, **kwargs):
+ """ Decode a document using the `decode` method, and add the
+ obtained results to our knowledge. """
+
+ # Save the dump.
+
+ if self.__save:
+ # Warning: if it's a stream, we're about to empty it and make
+ # it empty for the decoder, which is not what we want.
+ # So we make a bytes or str object out of the stream in order
+ # to be sure to be able to read it twice (seeking might not work).
+
+ if isinstance(inp, _RawIOBase):
+ inp = inp.read()
+
+ # Call the function.
+
+ self.save_dump(inp, type = type, hint = hint)
+
+ # Decode the result using the method inherited from the decoder,
+ # and feed the knowledge using what has been returned by it.
+
+ result = self.decode(inp, type = type, hint = hint, **kwargs)
+
+ if isinstance(result, _Base) or _type(result) == list:
+ # TODO: go through the result(s), feed our knowledge and
+ # complete it with it.
+
+ pass
+
+ return result
# ---
# Export dynamically gathered data such as functions as Python files
diff --git a/sgdfi/_repr.py b/sgdfi/_repr.py
index 359cfa7..153c853 100755
--- a/sgdfi/_repr.py
+++ b/sgdfi/_repr.py
@@ -8,7 +8,7 @@
import regex as _re
from ._util import IID, Enum as _Enum, \
- Base as _Base, Property as _Property, \
+ Base, Property as _Property, \
IIDProperty as _IIDProperty, DateProperty as _DateProperty, \
BoolProperty as _BoolProperty, EnumProperty as _EnumProperty, \
ArrayProperty as _ArrayProperty, TextProperty as _TextProperty, \
@@ -19,9 +19,12 @@ from ._dbs import OperationType, OperationTypeData as _OperationTypeData, \
StructureStatusData as _StructureStatusData, \
AllocationsRegime, EventTypeData as _EventTypeData, Code as _Code
-__all__ = ["IID", "Title", "Structure", "Adherent", "RallyRegistration",
- "Camp", "Place", "Operation", "OperationType", "Function", "Event",
- "StructureType", "StructureStatus", "AllocationsRegime"]
+__all__ = ["Base", "IID", "Title", "Structure", "Adherent",
+ "RallyRegistration", "Camp", "Place", "Operation", "OperationType",
+ "Function", "Event", "StructureType", "StructureStatus",
+ "AllocationsRegime"]
+
+_Base = Base
# ---
# Internal classes.