dataflow-core 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dataflow-core might be problematic. Click here for more details.

File without changes
@@ -0,0 +1,121 @@
1
+ from .package.configuration import ConfigurationManager
2
+ from .package.models.database import DatabaseManager
3
+ from .package.models import (
4
+ user as m_user,
5
+ session as m_session
6
+ )
7
+
8
+ from typing import Any, Callable
9
+ from airflow.www.security import FabAirflowSecurityManagerOverride
10
+
11
+ class DataflowAirflowAuthenticator(FabAirflowSecurityManagerOverride):
12
+ def __init__(self, wsgi_app: Callable) -> None:
13
+ self.wsgi_app = wsgi_app
14
+
15
+ # Dataflow database configuration
16
+ self.dataflow_config = ConfigurationManager('/dataflow/app/config/dataflow.cfg')
17
+ self.dataflow_database_url = self.dataflow_config.get_config_value('database', 'database_url')
18
+
19
+ self.dataflow_db_instance = DatabaseManager(self.dataflow_database_url)
20
+ self.dataflow_db = next(self.dataflow_db_instance.get_session())
21
+
22
+ # Airflow database configuration
23
+ self.airflow_config = ConfigurationManager('airflow.cfg')
24
+ self.airflow_database_url = self.airflow_config.get_config_value('database', 'sql_alchemy_conn')
25
+
26
+ self.airflow_db_instance = DatabaseManager(self.airflow_database_url)
27
+ self.airflow_db = next(self.airflow_db_instance.get_session())
28
+
29
+ m_user.Base.metadata.create_all(bind=self.dataflow_db_instance.get_engine())
30
+ m_session.Base.metadata.create_all(bind=self.dataflow_db_instance.get_engine())
31
+
32
+ def __call__(self, environ: dict, start_response: Callable) -> Any:
33
+
34
+ path = environ.get('PATH_INFO', '')
35
+ if not path == '/login/':
36
+ return self.wsgi_app(environ, start_response)
37
+
38
+ try:
39
+ # Extracting browser cookies
40
+ cookies = environ.get('HTTP_COOKIE', '')
41
+ user_session_id = None
42
+ parts = cookies.split('; ')
43
+ for part in parts:
44
+ if part.startswith('dataflow_session='):
45
+ user_session_id = part
46
+ break
47
+
48
+ if user_session_id is None:
49
+ raise Exception("No session id found")
50
+
51
+ user_session_id = user_session_id.split('=')[1]
52
+
53
+ # Retrieving user details
54
+ user_data = self.find_dataflow_user(user_session_id)
55
+
56
+ if user_data is None:
57
+ raise Exception("No user found for the dataflow_session id")
58
+
59
+ user = self.find_user(user_data.user_name)
60
+
61
+ if not user:
62
+ user_role = self.find_role(user_data.role.title())
63
+ user = self.add_user(username=user_data.user_name, first_name=self.not_none(user_data.first_name), last_name=self.not_none(user_data.last_name), email=self.not_none(user_data.email), role=user_role)
64
+
65
+ environ['REMOTE_USER'] = user.username
66
+ self.write_user_id(user_data.user_id)
67
+ return self.wsgi_app(environ, start_response)
68
+
69
+ except Exception as e:
70
+ return self.wsgi_app(environ, start_response)
71
+
72
+ def not_none(self, value):
73
+ return value if value is not None else ""
74
+
75
+ def find_dataflow_user(self, user_session_id):
76
+ """Find user by session_id in dataflow database."""
77
+ query = self.dataflow_db.query(m_session.Session_table)
78
+ session = query.filter(m_session.Session_table.session_id == user_session_id).first()
79
+ if session is None:
80
+ return None
81
+
82
+ user_data = self.dataflow_db.query(m_user.User).filter(m_user.User.user_id == session.user_id).first()
83
+ if user_data is None:
84
+ return None
85
+
86
+ return user_data
87
+
88
+ def find_user(self, username=None):
89
+ """Find user by username or email."""
90
+ return self.airflow_db.query(self.user_model).filter_by(username=username).one_or_none()
91
+
92
+ def find_role(self, role):
93
+ """Find a role in the database."""
94
+ return self.airflow_db.query(self.role_model).filter_by(name=role).one_or_none()
95
+
96
+ def add_user(self, username, first_name, last_name, email, role, password=""):
97
+ """Create a user."""
98
+ user = self.user_model()
99
+ user.first_name = first_name
100
+ user.last_name = last_name
101
+ user.username = username
102
+ user.email = email
103
+ user.active = True
104
+ user.roles = role if isinstance(role, list) else [role]
105
+ user.password = password
106
+ self.airflow_db.add(user)
107
+ self.airflow_db.commit()
108
+ return user
109
+
110
+ def write_user_id(self, user_id):
111
+ """
112
+ Write the given user_id to a file named dataflow_user_id.txt.
113
+
114
+ Args:
115
+ user_id (str): The user ID to be written to the file.
116
+ """
117
+ file_name = 'dataflow_user_id.txt'
118
+ with open(file_name, 'w') as file:
119
+ file.write(str(user_id))
120
+
121
+
@@ -0,0 +1,66 @@
1
+ from .package.configuration import ConfigurationManager
2
+ from .package.models.database import DatabaseManager
3
+ from .package.models import (
4
+ user as m_user,
5
+ session as m_session
6
+ )
7
+
8
+ import uuid
9
+ from jupyterhub.auth import Authenticator
10
+
11
+ class DataflowHubAuthenticator(Authenticator):
12
+ def __init__(self, **kwargs):
13
+ super().__init__(**kwargs)
14
+
15
+ self.dataflow_config = ConfigurationManager('/dataflow/app/config/dataflow.cfg')
16
+ self.database_url = self.dataflow_config.get_config_value('database', 'database_url')
17
+
18
+ self.db_instance = DatabaseManager(self.database_url)
19
+ self.db = next(self.db_instance.get_session())
20
+
21
+ m_user.Base.metadata.create_all(bind=self.db_instance.get_engine())
22
+ m_session.Base.metadata.create_all(bind=self.db_instance.get_engine())
23
+
24
+ def generate_session_id(self):
25
+ return str(uuid.uuid4())
26
+
27
+ async def authenticate(self, handler, data):
28
+ # get username and password
29
+ username = data["username"]
30
+ password = data["password"]
31
+
32
+ try:
33
+ # check if user exists
34
+ query = self.db.query(m_user.User)
35
+ user = query.filter(m_user.User.user_name == username).first()
36
+
37
+ if user is not None:
38
+ # check if password is correct
39
+ if user.password != password:
40
+ return None
41
+
42
+ # generate session_id
43
+ session_id = self.generate_session_id()
44
+ query = self.db.query(m_session.Session_table)
45
+ isSession = query.filter(m_session.Session_table.session_id == session_id).first()
46
+
47
+ # if session_id is already exists in the database, generate a new one
48
+ while isSession is not None:
49
+ session_id = self.generate_session_id()
50
+ isSession = query.filter(m_session.Session_table.session_id == session_id).first()
51
+
52
+ # add session_id to the database
53
+ db_item = m_session.Session_table(user_id=user.user_id, session_id=session_id)
54
+ self.db.add(db_item)
55
+ self.db.commit()
56
+ self.db.refresh(db_item)
57
+
58
+ # return user dictionary and set cookie
59
+ handler.set_cookie("dataflow_session", session_id)
60
+ user_dict = {"name": username, "session_id": session_id}
61
+ return user_dict
62
+ else:
63
+ return None
64
+
65
+ except Exception as e:
66
+ return None
File without changes
@@ -0,0 +1,27 @@
1
+ """configuration.py"""
2
+ import configparser
3
+ from configparser import NoOptionError, NoSectionError
4
+
5
+ class ConfigurationManager:
6
+ """
7
+ Configuration Manager
8
+ """
9
+
10
+ def __init__(self, config_file):
11
+
12
+ self.config_file = config_file
13
+ self.config = configparser.ConfigParser()
14
+ try:
15
+ self.config.read(self.config_file)
16
+
17
+ except Exception as e:
18
+ return None
19
+
20
+ def get_config_value(self, section, option):
21
+ """
22
+ Get configuration value
23
+ """
24
+ try:
25
+ return self.config.get(section, option)
26
+ except (NoOptionError, NoSectionError):
27
+ return None
File without changes
@@ -0,0 +1,32 @@
1
+ """models/database.py"""
2
+ from sqlalchemy.exc import SQLAlchemyError
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+ from sqlalchemy import create_engine
5
+ from sqlalchemy.orm import sessionmaker
6
+
7
+ class DatabaseManager:
8
+ def __init__(self, db_url):
9
+ self.db_url = db_url
10
+
11
+ def get_engine(self):
12
+ try:
13
+ engine = create_engine(self.db_url)
14
+ return engine
15
+ except SQLAlchemyError as e:
16
+ raise e
17
+
18
+ def get_session(self):
19
+ try:
20
+ engine = self.get_engine()
21
+ session = sessionmaker(autocommit=False, autoflush=False, bind=engine or create_engine(self.db_url))
22
+ db = session()
23
+ try:
24
+ yield db
25
+ finally:
26
+ db.close()
27
+
28
+ except SQLAlchemyError as e:
29
+ raise e
30
+
31
+ def get_base(self):
32
+ return declarative_base()
@@ -0,0 +1,20 @@
1
+ """models.py"""
2
+ from sqlalchemy import Column, Integer, String
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+
5
+ #instance for create declarative base
6
+ Base=declarative_base()
7
+
8
+ class Session_table(Base):
9
+ """
10
+ Table SESSIONS
11
+ """
12
+
13
+ __tablename__='SESSION'
14
+
15
+ id = Column(Integer, primary_key=True, index=True, unique=True, nullable=False, autoincrement=True)
16
+ session_id = Column(String, unique=True, nullable=False)
17
+ user_id = Column(String, nullable=False)
18
+
19
+
20
+
@@ -0,0 +1,23 @@
1
+ """models.py"""
2
+ from sqlalchemy import Column, Integer, String, LargeBinary, Enum
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+
5
+ #instance for create declarative base
6
+ Base=declarative_base()
7
+
8
+ class User(Base):
9
+ """
10
+ Table USER
11
+ """
12
+
13
+ __tablename__='USER'
14
+
15
+ user_id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
16
+ user_name = Column(String, unique=True, nullable=False)
17
+ first_name = Column(String)
18
+ last_name = Column(String)
19
+ email = Column(String, unique=True)
20
+ role = Column(Enum('admin', 'user', name='role_field'), nullable=False)
21
+ image = Column(LargeBinary)
22
+ active = Column(Enum('N', 'Y', name='active_field'), nullable=False, server_default=str("N"))
23
+ password = Column(String, nullable=False)
dataflow/__init__.py ADDED
File without changes
@@ -0,0 +1,27 @@
1
+ """configuration.py"""
2
+ import configparser
3
+ from configparser import NoOptionError, NoSectionError
4
+
5
+ class ConfigurationManager:
6
+ """
7
+ Configuration Manager
8
+ """
9
+
10
+ def __init__(self, config_file):
11
+
12
+ self.config_file = config_file
13
+ self.config = configparser.ConfigParser()
14
+ try:
15
+ self.config.read(self.config_file)
16
+
17
+ except Exception as e:
18
+ return None
19
+
20
+ def get_config_value(self, section, option):
21
+ """
22
+ Get configuration value
23
+ """
24
+ try:
25
+ return self.config.get(section, option)
26
+ except (NoOptionError, NoSectionError):
27
+ return None
dataflow/dataflow.py ADDED
@@ -0,0 +1,92 @@
1
+ import os
2
+ from .configuration import ConfigurationManager
3
+ from .models import (
4
+ session as m_session,
5
+ user as m_user,
6
+ )
7
+ from .models.database import DatabaseManager
8
+ from sqlalchemy.inspection import inspect
9
+ from .utils.aws_secrets_manager import SecretsManagerClient
10
+ import json
11
+
12
+
13
+ class Dataflow:
14
+ def __init__(self):
15
+ self.dataflow_config = ConfigurationManager('/dataflow/app/config/dataflow.cfg')
16
+ self.dataflow_database_url = self.dataflow_config.get_config_value('database', 'database_url')
17
+
18
+ self.dataflow_db_instance = DatabaseManager(self.dataflow_database_url)
19
+ self.dataflow_db = next(self.dataflow_db_instance.get_session())
20
+
21
+ self.secrets_manager = SecretsManagerClient('us-east-1')
22
+
23
+ m_user.Base.metadata.create_all(bind=self.dataflow_db_instance.get_engine())
24
+ m_session.Base.metadata.create_all(bind=self.dataflow_db_instance.get_engine())
25
+
26
+ def auth(self, session_id: str):
27
+ """Find user by session_id in dataflow database."""
28
+ try:
29
+ query = self.dataflow_db.query(m_session.Session_table)
30
+ session = query.filter(m_session.Session_table.session_id == session_id).first()
31
+ if session is None:
32
+ return False
33
+
34
+ user_data = self.dataflow_db.query(m_user.User).filter(m_user.User.user_id == session.user_id).first()
35
+ if user_data is None:
36
+ return False
37
+
38
+ user_dict = {"user_name": user_data.user_name, "name": f"{user_data.first_name} {user_data.last_name}", "email": user_data.email, "role": user_data.role}
39
+ return user_dict
40
+
41
+ except Exception as e:
42
+ return False
43
+
44
+ def variable(self, variable_name: str):
45
+ """Get variable value from secrets manager."""
46
+ try:
47
+ host_name = os.environ["HOSTNAME"]
48
+ user_name = host_name.replace("jupyter-","")
49
+
50
+ vault_path = "variable"
51
+ return self.secrets_manager.get_secret_by_key(vault_path, user_name, variable_name)
52
+
53
+ except Exception as e:
54
+ return None
55
+
56
+ def connection(self, conn_id: str):
57
+ """Get connection details from secrets manager."""
58
+ try:
59
+ host_name = os.environ["HOSTNAME"]
60
+ user_name=host_name.replace("jupyter-","")
61
+
62
+ vault_path = "connections"
63
+ secret = self.secrets_manager.get_secret_by_key(vault_path, user_name, conn_id)
64
+
65
+ conn_type = secret['conn_type'].lower()
66
+ username = secret['login']
67
+ password = secret.get('password', '')
68
+ host = secret['host']
69
+ port = secret['port']
70
+ database = secret.get('schemas', '')
71
+
72
+ user_info = f"{username}:{password}@" if password else f"{username}@"
73
+ db_info = f"/{database}" if database else ""
74
+
75
+ connection_string = f"{conn_type}://{user_info}{host}:{port}{db_info}"
76
+
77
+ extra = secret.get('extra', '')
78
+ if extra:
79
+ try:
80
+ extra_params = json.loads(extra)
81
+ if extra_params:
82
+ extra_query = "&".join(f"{key}={value}" for key, value in extra_params.items())
83
+ connection_string += f"?{extra_query}"
84
+ except json.JSONDecodeError:
85
+ # If 'extra' is not valid JSON, skip adding extra parameters
86
+ pass
87
+
88
+ connection_instance = DatabaseManager(connection_string)
89
+ return next(connection_instance.get_session())
90
+
91
+ except Exception as e:
92
+ return None
File without changes
@@ -0,0 +1,32 @@
1
+ """models/database.py"""
2
+ from sqlalchemy.exc import SQLAlchemyError
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+ from sqlalchemy import create_engine
5
+ from sqlalchemy.orm import sessionmaker
6
+
7
+ class DatabaseManager:
8
+ def __init__(self, db_url):
9
+ self.db_url = db_url
10
+
11
+ def get_engine(self):
12
+ try:
13
+ engine = create_engine(self.db_url)
14
+ return engine
15
+ except SQLAlchemyError as e:
16
+ raise e
17
+
18
+ def get_session(self):
19
+ try:
20
+ engine = self.get_engine()
21
+ session = sessionmaker(autocommit=False, autoflush=False, bind=engine or create_engine(self.db_url))
22
+ db = session()
23
+ try:
24
+ yield db
25
+ finally:
26
+ db.close()
27
+
28
+ except SQLAlchemyError as e:
29
+ raise e
30
+
31
+ def get_base(self):
32
+ return declarative_base()
@@ -0,0 +1,17 @@
1
+ """models.py"""
2
+ from sqlalchemy import Column, Integer, String
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+
5
+ #instance for create declarative base
6
+ Base=declarative_base()
7
+
8
+ class Session_table(Base):
9
+ """
10
+ Table SESSIONS
11
+ """
12
+
13
+ __tablename__='SESSION'
14
+
15
+ id = Column(Integer, primary_key=True, index=True, unique=True, nullable=False, autoincrement=True)
16
+ session_id = Column(String, unique=True, nullable=False)
17
+ user_id = Column(String, nullable=False)
@@ -0,0 +1,23 @@
1
+ """models.py"""
2
+ from sqlalchemy import Column, Integer, String, LargeBinary, Enum
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+
5
+ #instance for create declarative base
6
+ Base=declarative_base()
7
+
8
+ class User(Base):
9
+ """
10
+ Table USER
11
+ """
12
+
13
+ __tablename__='USER'
14
+
15
+ user_id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
16
+ user_name = Column(String, unique=True, nullable=False)
17
+ first_name = Column(String)
18
+ last_name = Column(String)
19
+ email = Column(String, unique=True)
20
+ role = Column(Enum('admin', 'user', name='role_field'), nullable=False)
21
+ image = Column(LargeBinary)
22
+ active = Column(Enum('N', 'Y', name='active_field'), nullable=False, server_default=str("N"))
23
+ password = Column(String, nullable=False)
File without changes
@@ -0,0 +1,64 @@
1
+ import boto3
2
+ from botocore.exceptions import ClientError, EndpointConnectionError, NoCredentialsError
3
+ import json
4
+
5
+ class SecretsManagerClient:
6
+ """
7
+ A class to interact with AWS Secrets Manager for managing secrets.
8
+
9
+ Args:
10
+ region_name (str): The AWS region name.
11
+
12
+ Attributes:
13
+ client: The Boto3 client for Secrets Manager.
14
+ json_handler: An instance of JsonHandler for handling JSON operations.
15
+ """
16
+ def __init__(self, region_name: str):
17
+ try:
18
+ self.client = boto3.client('secretsmanager', region_name=region_name)
19
+ except EndpointConnectionError as e:
20
+ self.logger.error(f"Failed to initialize SecretsManagerClient: {e}")
21
+ raise Exception(f"Failed to initialize SecretsManagerClient: Unable to connect to the endpoint. {e}")
22
+ except NoCredentialsError as e:
23
+ self.logger.error(f"Failed to initialize SecretsManagerClient: {e}")
24
+ raise Exception(f"Failed to initialize SecretsManagerClient: No AWS credentials found. {e}")
25
+
26
+
27
+ def get_secret_by_key(self, vault_path, user_name, secret_key: str):
28
+ """
29
+ Get information about a specific secret.
30
+
31
+ Args:
32
+ vault_path (str): The vault path.
33
+ user_name (str): The user name.
34
+ secret_key (str): The key of the secret to retrieve.
35
+
36
+ Returns:
37
+ str: Information about the secret in JSON format.
38
+
39
+ Raises:
40
+ Exception: If the operation fails.
41
+ """
42
+ try:
43
+ if not user_name:
44
+ raise Exception("user_name is required when secret_key is provided")
45
+
46
+ secret_name = f"{user_name}/{vault_path}/{secret_key}"
47
+ response = self.client.get_secret_value(SecretId=secret_name)
48
+ secret_metadata = self.client.describe_secret(SecretId=secret_name)
49
+ secret_data = json.loads(response.get('SecretString'))
50
+
51
+ if secret_data.get('is_active') == 'Y':
52
+ secret_info={
53
+ "Name": secret_key,
54
+ "Description": secret_metadata.get('Description')
55
+ }
56
+ secret_info.update(secret_data)
57
+ return secret_info
58
+ else:
59
+ raise Exception(f"Secret named '{secret_key}' is not active")
60
+ except ClientError as e:
61
+ if e.response['Error']['Code'] == 'ResourceNotFoundException':
62
+ raise Exception(f"Secret named '{secret_key}' not found")
63
+ else:
64
+ raise Exception(f"Failed to get secret '{secret_key}': {e}")
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.1
2
+ Name: dataflow-core
3
+ Version: 1.0.0
4
+ Summary: Dataflow core package
5
+ Home-page: UNKNOWN
6
+ Author: Dataflow
7
+ Author-email: UNKNOWN
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
10
+ Requires-Dist: sqlalchemy
11
+ Requires-Dist: boto3
12
+
13
+ UNKNOWN
14
+
15
+
@@ -0,0 +1,23 @@
1
+ authenticator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ authenticator/dataflowairflowauthenticator.py,sha256=gOmRbNbHAINfeVuE-cHDuk7mbo5q7K1wqRT0B20g6PA,4746
3
+ authenticator/dataflowhubauthenticator.py,sha256=jsxpB_NZz3n_FGFJzW0yfXR3i-KK1dsVVIQVIN84Pfc,2540
4
+ authenticator/package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ authenticator/package/configuration.py,sha256=7To6XwH1eESiYp39eqPcswXWwrdBUdPF6xN6WnazOF0,663
6
+ authenticator/package/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ authenticator/package/models/database.py,sha256=y09pqnglsBBtDZlyhvqDAlpUSFovwAzBAi6jOYl_XNk,896
8
+ authenticator/package/models/session.py,sha256=j6PhbrTMJxEkeDT4Vf5SqGtM_LI_vZy9O4vxn6LtIbc,495
9
+ authenticator/package/models/user.py,sha256=IYogp_vt0yDBG5i936uNPjgTis77VYPzITn9XpQUIyw,788
10
+ dataflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ dataflow/configuration.py,sha256=7To6XwH1eESiYp39eqPcswXWwrdBUdPF6xN6WnazOF0,663
12
+ dataflow/dataflow.py,sha256=tczUTSLQgSVxfYwzzB5lk_J3Yu51a0UtoE5-5L6zxPQ,3673
13
+ dataflow/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ dataflow/models/database.py,sha256=y09pqnglsBBtDZlyhvqDAlpUSFovwAzBAi6jOYl_XNk,896
15
+ dataflow/models/session.py,sha256=C9crPh6ZDFuL27hZ_zhUXDZZ0ZiIDE8ZD19O_4WPw-I,488
16
+ dataflow/models/user.py,sha256=IYogp_vt0yDBG5i936uNPjgTis77VYPzITn9XpQUIyw,788
17
+ dataflow/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ dataflow/utils/aws_secrets_manager.py,sha256=FqHm3YRynv580FpFsS0RfI1MSGY5aq-V7t4blpiYsS4,2588
19
+ dataflow_core-1.0.0.dist-info/METADATA,sha256=KC7hRTfyRuONXqiHxyA7wYM0a5Wl0QV1wRZxaxprYeQ,239
20
+ dataflow_core-1.0.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
21
+ dataflow_core-1.0.0.dist-info/entry_points.txt,sha256=lDLG2MMWlKfkqsVWFghF7sx-yEvM2xqMmHE7rMTysE4,118
22
+ dataflow_core-1.0.0.dist-info/top_level.txt,sha256=SZsUOpSCK9ntUy-3Tusxzf5A2e8ebwD8vouPb1dPt_8,23
23
+ dataflow_core-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.44.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [jupyterhub.authenticators]
2
+ dataflow_authenticator = authenticator.dataflowhubauthenticator:DataflowHubAuthenticator
3
+
@@ -0,0 +1,2 @@
1
+ authenticator
2
+ dataflow