Skip to content

Commit 1cb4430

Browse files
committed
2 parents 102096b + f18bf54 commit 1cb4430

27 files changed

+6768
-375
lines changed

.github/workflows/build-test-deploy.yml

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,23 +107,41 @@ jobs:
107107
run: |
108108
python setup.py sdist bdist_wheel
109109
110-
- name: Archive artifacts
110+
- name: Extract Changes
111+
shell: python
112+
run: |
113+
import os, re
114+
tag_name = os.environ['GITHUB_REF'].replace('refs/tags/', '')
115+
changes = ''
116+
with open('CHANGELOG.md') as f:
117+
lines = f.read()
118+
match = re.search('%s [()\d\-\s]*' % tag_name, lines)
119+
if match:
120+
lines = lines[match.end():]
121+
changes = re.split('-----+', lines)[0].split('\n')
122+
changes = '\n'.join(changes[:-2])
123+
with open('release_notes.md', 'w') as f:
124+
f.write(changes)
125+
126+
- name: Archive distribution artifacts
111127
# Archive distribution files for use by auto (or manual) PyPI upload
112128
uses: actions/upload-artifact@v2
113129
with:
114130
name: pypi-dist
115131
path: ./dist
116132

133+
- name: Archive changelog artifacts
134+
uses: actions/upload-artifact@v2
135+
with:
136+
name: release_notes
137+
path: release_notes.md
138+
139+
117140
publish:
118141
name: "Publish"
119142
runs-on: ubuntu-latest
120143
needs: [gh-pages, build]
121144
steps:
122-
- name: Create Release
123-
uses: softprops/action-gh-release@v1
124-
with:
125-
draft: true
126-
body: "Test Release"
127145

128146
- name: Download documentation
129147
uses: actions/download-artifact@v2
@@ -137,9 +155,26 @@ jobs:
137155
name: pypi-dist
138156
path: ./dist
139157

158+
- name: Download release notes
159+
uses: actions/download-artifact@v2
160+
with:
161+
name: release_notes
162+
163+
- name: Zip Documentation
164+
run: zip -r documentation.zip ./html-docs
165+
140166
- name: Display structure of downloaded files
141167
run: ls -R
142168

169+
- name: Create Release
170+
id: create_release
171+
uses: softprops/action-gh-release@v1
172+
with:
173+
draft: true
174+
body_path: release_notes.md
175+
body: ""
176+
files: documentation.zip
177+
143178
- name: Deploy documentation
144179
uses: peaceiris/actions-gh-pages@v3
145180
with:
@@ -153,3 +188,10 @@ jobs:
153188
password: ${{ secrets.PYPI_API_TOKEN }}
154189
verbose: true
155190

191+
- name: Publish release
192+
uses: StuYarrow/publish-release@v1
193+
env:
194+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
195+
with:
196+
id: ${{ steps.create_release.outputs.id }}
197+

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ Unreleased
22
----------
33
-
44

5+
v1.6.2 (2021-09-09)
6+
-------------------
7+
**Bugfixes**
8+
- Fixed an issue with `register_model()` where random forest, gradient boosting, and SVM regression models with
9+
nominal inputs where incorrectly treated as classification models.
10+
511
v1.6.1 (2021-09-01)
612
-------------------
713
**Improvements**

doc/api/sasctl.pzmm.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
sasctl.pzmm package
2+
====================
3+
4+
Module contents
5+
---------------
6+
7+
.. automodule:: sasctl.pzmm
8+
:members:
9+
:undoc-members:
10+
:show-inheritance:

doc/api/sasctl.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ Subpackages
2323
.. toctree::
2424

2525
sasctl.services
26-
sasctl.utils
26+
sasctl.utils
27+
sasctl.pzmm

src/sasctl/__init__.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
55
# SPDX-License-Identifier: Apache-2.0
66

7-
__version__ = '1.6.1'
7+
__version__ = '1.6.2'
88
__author__ = 'SAS'
99
__credits__ = ['Yi Jian Ching', 'Lucas De Paula', 'James Kochuba', 'Peter Tobac',
1010
'Chris Toth', 'Jon Walker', 'Scott Lindauer']
@@ -13,11 +13,28 @@
1313
'Cary, NC, USA. All Rights Reserved.'
1414

1515
import logging
16+
import warnings
1617

1718

18-
from .core import current_session, delete, get, get_link, platform_version, post, put, request_link
19+
from .core import (
20+
current_session,
21+
delete,
22+
get,
23+
get_link,
24+
platform_version,
25+
post,
26+
put,
27+
request_link,
28+
)
1929
from .core import RestObj, Session, HTTPError
20-
from .tasks import register_model, publish_model, update_model_performance
30+
from .tasks import (
31+
publish_model,
32+
register_model,
33+
update_model_performance,
34+
)
35+
36+
# Ensure deprecation warnings are shown to users.
37+
warnings.filterwarnings('always', category=DeprecationWarning, module=r'^sasctl\.')
2138

2239

2340
# Prevent package from emitting log records unless consuming

src/sasctl/_services/cas_management.py

Lines changed: 96 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
# Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
55
# SPDX-License-Identifier: Apache-2.0
66

7+
import os
8+
79
from .service import Service
810

11+
DEFAULT_SERVER = 'cas-shared-default'
12+
DEFAULT_CASLIB = 'casuser'
13+
914

1015
class CASManagement(Service):
1116
"""The CAS Management service provides the ability to manage and perform
@@ -16,14 +21,15 @@ class CASManagement(Service):
1621

1722
list_servers, get_server, _, _ = Service._crud_funcs('/servers', 'server')
1823

19-
def list_caslibs(self, server, filter=None):
24+
@classmethod
25+
def list_caslibs(cls, server, filter_=None):
2026
"""List caslibs available on a server.
2127
2228
Parameters
2329
----------
2430
server : str or dict
2531
Name, ID, or dictionary representation of the server.
26-
filter : str, optional
32+
filter_ : str, optional
2733
A `formatted <https://developer.sas.com/reference/filtering>`_
2834
filter string.
2935
@@ -33,10 +39,12 @@ def list_caslibs(self, server, filter=None):
3339
A collection of :class:`.RestObj` instances.
3440
3541
"""
36-
return self._get_rel(server, 'caslibs', func=self.get_server,
37-
filter=filter) or []
42+
return (
43+
cls._get_rel(server, 'caslibs', func=cls.get_server, filter_=filter_) or []
44+
)
3845

39-
def get_caslib(self, name, server=None):
46+
@classmethod
47+
def get_caslib(cls, name, server=None):
4048
"""Get a caslib by name.
4149
4250
Parameters
@@ -51,14 +59,15 @@ def get_caslib(self, name, server=None):
5159
RestObj
5260
5361
"""
54-
server = server or 'cas-shared-default'
55-
caslibs = self.list_caslibs(server,
56-
filter='eq($primary,name, "%s")' % name)
62+
server = server or DEFAULT_SERVER
63+
caslibs = cls.list_caslibs(server, filter_='eq($primary,name, "%s")' % name)
5764

5865
if caslibs:
5966
return caslibs[0]
67+
return None
6068

61-
def list_tables(self, caslib, server=None, filter=None):
69+
@classmethod
70+
def list_tables(cls, caslib, server=None, filter_=None):
6271
"""List tables available in a caslib.
6372
6473
Parameters
@@ -67,7 +76,7 @@ def list_tables(self, caslib, server=None, filter=None):
6776
Name, ID, or dictionary representation of the caslib.
6877
server : str, optional
6978
Server where the `caslib` is registered.
70-
filter : str, optional
79+
filter_ : str, optional
7180
Filter string in the `https://developer.sas.com/reference/filtering
7281
/` format.
7382
@@ -77,18 +86,19 @@ def list_tables(self, caslib, server=None, filter=None):
7786
A collection of :class:`.RestObj` instances.
7887
7988
"""
80-
return self._get_rel(caslib, 'tables', self.get_caslib, filter,
81-
server) or []
89+
return cls._get_rel(caslib, 'tables', server, func=cls.get_caslib, filter_=filter_) or []
8290

83-
def get_table(self, name, caslib, server=None):
91+
@classmethod
92+
def get_table(cls, name, caslib=None, server=None):
8493
"""Get a table by name.
8594
8695
Parameters
8796
----------
8897
name : str
8998
Name of the table.
90-
caslib : str or dict
91-
Name, ID, or dictionary representation of the caslib.
99+
caslib : str or dict, optional
100+
Name, ID, or dictionary representation of the caslib. Defaults to
101+
CASUSER.
92102
server : str, optional
93103
Server where the `caslib` is registered.
94104
@@ -97,9 +107,77 @@ def get_table(self, name, caslib, server=None):
97107
RestObj
98108
99109
"""
100-
tables = self.list_tables(caslib,
101-
server=server,
102-
filter='eq($primary,name, "%s")' % name)
110+
caslib = caslib or DEFAULT_CASLIB
111+
tables = cls.list_tables(
112+
caslib, server=server, filter_='eq($primary,name, "%s")' % name
113+
)
103114

104115
if tables:
105116
return tables[0]
117+
return None
118+
119+
@classmethod
120+
def upload_file(
121+
cls, file, name, caslib=None, server=None, header=None, format_=None
122+
):
123+
"""Upload a file to a CAS table.
124+
125+
Uploads the contents of a CSV, XLS, XLSX, SAS7BDT or SASHDAT file to a
126+
newly created CAS table.
127+
128+
Parameters
129+
----------
130+
file : str or file-like object
131+
File containing data to upload or path to the file.
132+
name : str
133+
Name of the table to create
134+
caslib : str, optional
135+
caslib in which the table will be created. Defaults to CASUSER.
136+
server : str, optional
137+
CAS server on which the table will be created. Defaults to
138+
cas-shared-default.
139+
header : bool, optional
140+
Whether the first row of data contains column headers. Defaults to
141+
True.
142+
format_ : {"csv", "xls", "xlsx", "sas7bdat", "sashdat"}, optional
143+
File of input `file`. Not required if format can be discerned from
144+
the file path.
145+
146+
Returns
147+
-------
148+
RestObj
149+
Table reference
150+
151+
"""
152+
name = str(name)
153+
caslib = caslib or DEFAULT_CASLIB
154+
server = server or DEFAULT_SERVER
155+
header = True if header is None else bool(header)
156+
157+
# Not a file-like object, assuming it's a file path
158+
if not hasattr(file, 'read'):
159+
path = os.path.abspath(os.path.expanduser(file))
160+
format_ = os.path.splitext(path)[-1].lstrip('.').lower()
161+
162+
# Extension should be supported & needs to be explicitly set in
163+
# the "format" parameter to avoid errors.
164+
if format_ not in ('csv', 'xls', 'xlsx', 'sas7bdat', 'sashdat'):
165+
raise ValueError("File '%s' has an unsupported file type." % file)
166+
167+
with open(path, 'rb') as f:
168+
file = f.read()
169+
170+
data = {
171+
'tableName': name,
172+
'containsHeaderRow': header,
173+
}
174+
175+
if format_ is not None:
176+
data['format'] = format_
177+
178+
tbl = cls.post(
179+
'/servers/%s/caslibs/%s/tables' % (server, caslib),
180+
data=data,
181+
files={name: file},
182+
)
183+
return tbl

src/sasctl/_services/concepts.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ class Concepts(Service):
1515

1616
_SERVICE_ROOT = '/concepts'
1717

18+
@classmethod
1819
def assign_concepts(
19-
self,
20+
cls,
2021
documents,
2122
caslib=None,
2223
id_column=None,
@@ -123,4 +124,4 @@ def assign_concepts(
123124
'Accept': 'application/vnd.sas.text.concepts.job+json',
124125
}
125126

126-
return self.post(url, json=data, headers=headers)
127+
return cls.post(url, json=data, headers=headers)

0 commit comments

Comments
 (0)